1. 程式人生 > >leetcode中的旋轉排序陣列問題:

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”的對映關係,就可以轉化為普通二分查詢搜尋問題。