旋转排序数组系列题详解
旋轉(zhuǎn)排序數(shù)組系列題詳解
文章目錄
- 旋轉(zhuǎn)排序數(shù)組系列題詳解
- 一、問(wèn)題描述:旋轉(zhuǎn)數(shù)組的最小數(shù)字
- 二、分析:二分查找
- 三、代碼
- 四、問(wèn)題描述:尋找旋轉(zhuǎn)排序數(shù)組中的最小值
- 五、分析:二分搜索
- 六、代碼
- 七、問(wèn)題描述:尋找旋轉(zhuǎn)排序數(shù)組中的最小值 II
- 八、分析:二分查找
- 九、代碼
- 十、問(wèn)題描述:搜索旋轉(zhuǎn)排序數(shù)組
- 十一、分析:二分搜索
- 十二、代碼
- 十三、問(wèn)題描述:搜索旋轉(zhuǎn)排序數(shù)組II
- 十四、代碼
一、問(wèn)題描述:旋轉(zhuǎn)數(shù)組的最小數(shù)字
二、分析:二分查找
一個(gè)包含重復(fù)元素的升序數(shù)組在經(jīng)過(guò)旋轉(zhuǎn)之后,可以得到下面可視化的折線圖:
-
其中橫軸表示數(shù)組元素的下標(biāo),縱軸表示數(shù)組元素的值。圖中標(biāo)出了最小值的位置,是我們需要旋轉(zhuǎn)的目標(biāo)。
-
我們考慮數(shù)組中的最后一個(gè)元素 x:在最小值右側(cè)的元素,它們的值一定都小于等于 x;而在最小值左側(cè)的元素,它們的值一定都大于等于 x。因此,我們可以根據(jù)這一條性質(zhì),通過(guò)二分查找的方法找出最小值。
-
在二分查找的每一步中,左邊界為 low,右邊界為high,區(qū)間的中點(diǎn)為 pivot,最小值就在該區(qū)間內(nèi)。我們將中軸元素numbers[pivot] 與右邊界元素 numbers[high] 進(jìn)行比較,可能會(huì)有以下的三種情況:
-
第一種情況是numbers[pivot]<numbers[high]。
如下圖所示,這說(shuō)明numbers[pivot] 是最小值右側(cè)的元素,因此我們可以忽略二分查找區(qū)間的右半部分。
- 第二種情況是numbers[pivot]>numbers[high]。
如下圖所示,這說(shuō)明 numbers[pivot] 是最小值左側(cè)的元素,因此我們可以忽略二分查找區(qū)間的左半部分。
- 第三種情況是numbers[pivot]==numbers[high]。
如下圖所示,由于重復(fù)元素的存在,我們并不能確定 numbers[pivot] 究竟在最小值的左側(cè)還是右側(cè),因此我們不能莽撞地忽略某一部分的元素。
我們唯一可以知道的是,由于它們的值相同,所以無(wú)論numbers[high] 是不是最小值,都有一個(gè)它的「替代品numbers[pivot],因此我們可以忽略二分查找區(qū)間的右端點(diǎn)。
- 當(dāng)二分查找結(jié)束時(shí),我們就得到了最小值所在的位置。
三、代碼
class Solution { public:int minArray(vector<int>& numbers) {int low = 0;int high = numbers.size() - 1;while (low < high) {int pivot = low + (high - low) / 2;if (numbers[pivot] < numbers[high]) {high = pivot;}else if (numbers[pivot] > numbers[high]) {low = pivot + 1;}else {high -= 1;}}return numbers[low];} };四、問(wèn)題描述:尋找旋轉(zhuǎn)排序數(shù)組中的最小值
五、分析:二分搜索
一種暴力的解法是搜索整個(gè)數(shù)組,找到其中的最小元素,這樣的時(shí)間復(fù)雜度是 O(N)其中 N 是給定數(shù)組的大小。
-
一個(gè)非常棒的解決該問(wèn)題的辦法是使用二分搜索。在二分搜索中,我們找到區(qū)間的中間點(diǎn)并根據(jù)某些條件決定去區(qū)間左半部分還是右半部分搜索。
-
由于給定的數(shù)組是有序的,我們就可以使用二分搜索。然而,數(shù)組被旋轉(zhuǎn)了,所以簡(jiǎn)單的使用二分搜索并不可行。
-
在這個(gè)問(wèn)題中,我們使用一種改進(jìn)的二分搜索,判斷條件與標(biāo)準(zhǔn)的二分搜索有些不同。
-
我們希望找到旋轉(zhuǎn)排序數(shù)組的最小值,如果數(shù)組沒(méi)有被旋轉(zhuǎn)呢?如何檢驗(yàn)這一點(diǎn)呢?
-
如果數(shù)組沒(méi)有被旋轉(zhuǎn),是升序排列,就滿足 last element > first element。
-
上圖例子中 7>2 。說(shuō)明數(shù)組仍然是有序的,沒(méi)有被旋轉(zhuǎn)。
-
上面的例子中 3 < 4,因此數(shù)組旋轉(zhuǎn)過(guò)了。這是因?yàn)樵鹊臄?shù)組為 [2, 3, 4, 5, 6, 7],通過(guò)旋轉(zhuǎn)較小的元素 [2, 3] 移到了后面,也就是 [4, 5, 6, 7, 2, 3]。因此旋轉(zhuǎn)數(shù)組中第一個(gè)元素 [4] 變得比最后一個(gè)元素大。
-
這意味著在數(shù)組中你會(huì)發(fā)現(xiàn)一個(gè)變化的點(diǎn),這個(gè)點(diǎn)會(huì)幫助我們解決這個(gè)問(wèn)題,我們稱其為變化點(diǎn)。
-
在這個(gè)改進(jìn)版本的二分搜索算法中,我們需要找到這個(gè)點(diǎn)。下面是關(guān)于變化點(diǎn)的特點(diǎn):
-
所有變化點(diǎn)左側(cè)元素 > 數(shù)組第一個(gè)元素
-
所有變化點(diǎn)右側(cè)元素 < 數(shù)組第一個(gè)元素
-
算法
-
找到數(shù)組的中間元素 mid。
-
如果中間元素 > 數(shù)組第一個(gè)元素,我們需要在 mid 右邊搜索變化點(diǎn)。
-
如果中間元素 < 數(shù)組第一個(gè)元素,我們需要在 mid 做邊搜索變化點(diǎn)。
-
上面的例子中,中間元素 6 比第一個(gè)元素 4 大,因此在中間點(diǎn)右側(cè)繼續(xù)搜索。
-
當(dāng)我們找到變化點(diǎn)時(shí)停止搜索,當(dāng)以下條件滿足任意一個(gè)即可:
-
nums[mid] > nums[mid + 1],因此 mid+1 是最小值。
-
nums[mid - 1] > nums[mid],因此 mid 是最小值。
- 在上面的例子中,標(biāo)記左右區(qū)間端點(diǎn)。中間元素為 2,之后的元素是 7 滿足 7 > 2 也就是 nums[mid - 1] > nums[mid]。因此找到變化點(diǎn)也就是最小元素為 2。
六、代碼
class Solution { public:int findMin(vector<int>& nums) {if (nums.size() == 1) {return nums[0];}int left = 0, right = nums.size() - 1;if (nums[right] > nums[0]) {return nums[0];}while (right >= left) {int mid = left + (right - left) / 2;if (nums[mid] > nums[mid + 1]) {return nums[mid + 1];}if (nums[mid - 1] > nums[mid]) {return nums[mid];}if (nums[mid] > nums[0]) {left = mid + 1;} else {right = mid - 1;}}return -1;} };七、問(wèn)題描述:尋找旋轉(zhuǎn)排序數(shù)組中的最小值 II
八、分析:二分查找
-
一個(gè)包含重復(fù)元素的升序數(shù)組在經(jīng)過(guò)旋轉(zhuǎn)之后,可以得到下面可視化的折線圖:
-
其中橫軸表示數(shù)組元素的下標(biāo),縱軸表示數(shù)組元素的值。圖中標(biāo)出了最小值的位置,是我們需要旋轉(zhuǎn)的目標(biāo)。
-
我們考慮數(shù)組中的最后一個(gè)元素 x:在最小值右側(cè)的元素,它們的值一定都小于等于 x;而在最小值左側(cè)的元素,它們的值一定都大于等于 x。因此,我們可以根據(jù)這一條性質(zhì),通過(guò)二分查找的方法找出最小值。
-
在二分查找的每一步中,左邊界為low,右邊界為high,區(qū)間的中點(diǎn)為pivot,最小值就在該區(qū)間內(nèi)。
-
我們將中軸元素nums[pivot] 與右邊界元素nums[high] 進(jìn)行比較,可能會(huì)有以下的三種情況:
-
第一種情況是nums[pivot]<nums[high]。
如下圖所示,這說(shuō)明nums[pivot] 是最小值右側(cè)的元素,因此我們可以忽略二分查找區(qū)間的右半部分。
- 第二種情況是nums[pivot]>nums[high]。
如下圖所示,這說(shuō)明 nums[pivot] 是最小值左側(cè)的元素,因此我們可以忽略二分查找區(qū)間的左半部分。
- 第三種情況是 nums[pivot]==nums[high]。
如下圖所示,由于重復(fù)元素的存在,我們并不能確定nums[pivot] 究竟在最小值的左側(cè)還是右側(cè),因此我們不能莽撞地忽略某一部分的元素。
我們唯一可以知道的是,由于它們的值相同,所以無(wú)論 nums[high] 是不是最小值,都有一個(gè)它的「替代品」nums[pivot],因此我們可以忽略二分查找區(qū)間的右端點(diǎn)。
- 當(dāng)二分查找結(jié)束時(shí),我們就得到了最小值所在的位置。
九、代碼
class Solution { public:int findMin(vector<int>& nums) {int low = 0;int high = nums.size() - 1;while (low < high) {int pivot = low + (high - low) / 2;if (nums[pivot] < nums[high]) {high = pivot;}else if (nums[pivot] > nums[high]) {low = pivot + 1;}else {high -= 1;}}return nums[low];} };十、問(wèn)題描述:搜索旋轉(zhuǎn)排序數(shù)組
十一、分析:二分搜索
題目要求算法時(shí)間復(fù)雜度必須是 O(logn)O(logn)O(logn) 的級(jí)別,這提示我們可以使用二分搜索的方法。
- 但是數(shù)組本身不是有序的,進(jìn)行旋轉(zhuǎn)后只保證了數(shù)組的局部是有序的,這還能進(jìn)行二分搜索嗎?答案是可以的。
- 可以發(fā)現(xiàn)的是,我們將數(shù)組從中間分開成左右兩部分的時(shí)候,一定有一部分的數(shù)組是有序的。
- 拿示例來(lái)看,我們從 6 這個(gè)位置分開以后數(shù)組變成了 [4, 5, 6] 和 [7, 0, 1, 2] 兩個(gè)部分,其中左邊 [4, 5, 6]這個(gè)部分的數(shù)組是有序的,其他也是如此。
- 這啟示我們可以在常規(guī)二分搜索的時(shí)候查看當(dāng)前 mid 為分割位置分割出來(lái)的兩個(gè)部分 [l, mid] 和 [mid + 1, r]
- 看哪個(gè)部分是有序的,并根據(jù)有序的那個(gè)部分確定我們?cè)撊绾胃淖兌炙阉鞯纳舷陆?#xff0c;因?yàn)槲覀兡軌蚋鶕?jù)有序的那部分判斷出 target 在不在這個(gè)部分:
- 如果 [l, mid - 1] 是有序數(shù)組,且 target 的大小滿足 [nums[l],nums[mid]),則我們應(yīng)該將搜索范圍縮小至 [l, mid - 1],否則在 [mid + 1, r] 中尋找。
- 如果 [mid, r] 是有序數(shù)組,且 target 的大小滿足(nums[mid+1],nums[r]],則我們應(yīng)該將搜索范圍縮小至 [mid + 1, r],否則在 [l, mid - 1] 中尋找。
- 需要注意的是,二分的寫法有很多種,所以在判斷 target 大小與有序部分的關(guān)系的時(shí)候可能會(huì)出現(xiàn)細(xì)節(jié)上的差別。
十二、代碼
class Solution { public:int search(vector<int>& nums, int target) {int n = (int)nums.size();if (!n) return -1;if (n == 1) return nums[0] == target ? 0 : -1;//左右邊界int l = 0, r = n - 1;while (l <= r) {//中間的值int mid = (l + r) >> 1;//相等直接返回if (nums[mid] == target) return mid;//代表【l,mid】是有序的if (nums[0] <= nums[mid]) {//如果目標(biāo)值target滿足【nums[0],nums[mid])代表在//左半?yún)^(qū)間查找if (nums[0] <= target && target < nums[mid]) {r = mid - 1;} //反之右半?yún)^(qū)間查找else {l = mid + 1;}} //代表(mid,r】是有序的else {//在右半?yún)^(qū)間查找if (nums[mid] < target && target <= nums[n - 1]) {l = mid + 1;} //在左半?yún)^(qū)間查找else {r = mid - 1;}}}return -1;} };十三、問(wèn)題描述:搜索旋轉(zhuǎn)排序數(shù)組II
十四、代碼
class Solution { public:bool search(vector<int>& nums, int target) {int left = 0,right = nums.size() - 1;while(left <= right){while(left != right && nums[left] == nums[right]) right--; //無(wú)重復(fù)值的解法中添加這行int mid = (left + right) / 2;if(nums[mid] == target) return true;else if(nums[mid] > target){if(nums[mid] > nums[right] && target < nums[left]) left = mid + 1;else right = mid - 1;}else{if(nums[mid] < nums[left] && target > nums[right]) right=mid-1;else left = mid + 1;}}return false;} };總結(jié)
以上是生活随笔為你收集整理的旋转排序数组系列题详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 旋转图像
- 下一篇: 几种常见的Web攻击