leetcode中的旋轉排序陣列問題:
關於旋轉排序陣列leetcode中共有4道題目,思路都是基於二分查詢。
什麼是旋轉排序陣列?
假設按照升序排序的陣列在預先未知的某個點上進行了旋轉。
( 例如,陣列 [0,1,2,4,5,6,7] 可能變為 [4,5,6,7,0,1,2] )。
具體問題
找最小值和普通搜尋兩種
找最小值問題
1.假設陣列中不存在相同元素(153題 中等)
示例:
輸入: [3,4,5,1,2]
輸出: 1
思路:
每次將mid和hi位置的數比較即可判斷最小值所在的區間
程式碼:
public int findMin(int[] nums) { int l=0,h=nums.length-1; while (l<h){ int mid=l+(h-l)/2; if (nums[mid]>nums[h]){ l=mid+1; }else { h=mid; } } return nums[l]; }
2.假設陣列中存在相同元素(154題 困難)
示例:
輸入: [1,1,1,0,1]
輸出: 0
思路:
如果陣列元素允許重複的話,那麼就會出現一個特殊的情況:nums[l] == nums[m] == nums[h],那麼此時無法確定解在哪個區間,需要切換到順序查詢。例如對於陣列 {1,1,1,0,1},l、m 和 h 指向的數都為 1,此時無法知道最小數字 0 在哪個區間。
程式碼:
public int findMin(int[] nums) { int l=0,h=nums.length-1; while (l<h){ int mid=l+(h-l)/2; if (nums[l]==nums[mid]&&nums[mid]==nums[h]){ return find(nums,l,h);//切換到順序查詢 } else if (nums[mid]<=nums[h]){ h=mid; }else { l=mid+1; } } return nums[l]; } public int find(int[] nums,int l,int h){ for (int i = l; i <h ; i++) { if (nums[i]>nums[i+1]){ return nums[i+1]; } } return nums[l]; }
搜尋問題
搜尋一個給定的目標值,如果陣列中存在這個目標值,則返回它的索引,否則返回 -1
1.假設陣列中不存在相同元素(33題 中等)
例項:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
思路:
可以基於153題,第一次二分查詢找到最小值的位置(翻轉點的位置)。有了最小值的索引便有個一個對映關係,mid和realmid。第二遍的二分查詢就是一個常規的二分查詢,但要考慮對映關係,即每次拿realmid的值比較而不是mid位置的值。
程式碼:
public int search(int[] nums, int target) { int l=0,h=nums.length-1; //用二分查詢找到最小值的索引 while (l<h){ int mid=l+(h-l)/2; if (nums[mid]<=nums[h]){ h=mid; }else { l=mid+1; } }//l=h 為最小值的索引 也就是翻轉的位置 int rot=l; l=0; h=nums.length-1; //第二遍二分查詢 就是正常的二分查詢但要考慮翻轉點 while (l<=h){ int mid=l+(h-l)/2; int realmid=(mid+rot)%nums.length; //通過對映關係找到真實的中間點 if (nums[realmid]==target){ return realmid; }else if (nums[realmid]>target){ h=mid-1; }else { l=mid+1; } } return -1; }
2.假設陣列中存在相同元素(81題 中等)
示例:
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
思路:
這題相當於在154題的基礎上,
第一步,用154題的函式找到存在重複元素陣列中最小值(翻轉點)的位置;
第二步,參考1的做法,通過對映關係完成一次常規二分查詢。
程式碼:
public boolean search(int[] nums, int target) {
//呼叫函式找到最小值的索引
int rot=findMinIndex(nums);
int l=0,h=nums.length-1;
//二分查詢 就是正常的二分查詢但要考慮翻轉點
while (l<=h){
int mid=l+(h-l)/2;
int realmid=(mid+rot)%nums.length; //通過對映關係找到真實的中間點
if (nums[realmid]==target){
return true;
}else if (nums[realmid]>target){
h=mid-1;
}else {
l=mid+1;
}
}
return false;
}
public int find(int[] nums,int l,int h){ //順序查詢
for (int i = l; i < h; i++) {
if (nums[i]>nums[i+1]){
return i+1;
}
}
return l;
}
public int findMinIndex(int[] nums) {
int l=0,h=nums.length-1;
while (l<h){
int mid=l+(h-l)/2;
if (nums[l]==nums[mid]&&nums[mid]==nums[h]){
return find(nums,l,h);//切換到順序查詢
}
else if (nums[mid]<=nums[h]){
h=mid;
}else {
l=mid+1;
}
}
return l;
}
總結:
旋轉排序陣列中的問題無非就是查詢問題,而排序陣列中的查詢問題採用二分查詢效率最高,所以要儘量利用其中的順序關係轉化為二分查詢來解決。
對於找最小值(旋轉點)問題,又可以分為無重複元素和有重複元素;對於無重複元素找最小值問題,每次將mid位置元素和h位置元素比較;對於有重複,注意特例類似[1,1,1,0,1,1],出現特例時就要區域性利用順序查詢來繼續查找了,所以複雜度會由O(logN)退化到O(N)。
對於搜尋問題,第一步都是解決找旋轉點的問題,根據有無重複元素採用不同函式。第二步,在找到旋轉點後,便有了“mid”和“realmid”的對映關係,就可以轉化為普通二分查詢搜尋問題。