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