LeetCode4道搜索旋轉數組題—33、81、153、154

LeetCode有4道搜索旋轉數組的題目,這裏一起說了。

33. 搜索旋轉排序數組

假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。
你可以假設數組中不存在重複的元素。
你的算法時間複雜度必須是 O(log n) 級別。
示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例 2:輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
———————————————————————————————————————————————
看到log n就知道要用二分法,我先把最常見的二分法放進來:

public static int binarySort(int[] nums,int t){
        int i=0,j=nums.length-1;
        int mid=i+(j-i)/2;
        while(i<=j){
            if(nums[mid]==target){
                return mid;
            }
            if(nums[mid]>target){
                j=mid-1;
            }else{
                i=mid+1;
            }
            mid=i+(j-i)/2;
        }
        return -1;
    }

常見的二分法是完全有序,這題雖然旋轉了,但是不影響有序性,只是選擇區間的方式不同。
選擇區間的方式是:
1.首先比較nums[i]和nums[mid]的大小關係
2.如果nums[i]>nums[mid],則說明拐點一定在i和mid之間
在這裏插入圖片描述
這時如果
(1)target>=nums[i],則target一定在i~mid這段範圍內
(2)targer<nums[mid],則targer也一定在i~mid這段範圍內
(3)nums[mid]<targer<nums[i],則target一定在mid~j這段範圍內

3.如果nums[i]<nums[mid],則說明從i到mid這段區域一定是升序的
在這裏插入圖片描述
這時如果
(1)target>nums[mid],則target一定在mid~j這段範圍內
(2)targer<nums[i],則targer也一定在mid~j這段範圍內
(3)nums[i]<=targer<nums[mid],則target一定在i~mid這段範圍內

總結上述的原理,對二分查找進行改造:

 public int search(int[] nums, int target) {
        int i=0,j=nums.length-1;
        int mid=i+(j-i)/2;
        while(i<=j){
            if(target==nums[mid]){
                return mid;
            }
            if(nums[i]>nums[mid]) {
                if (target < nums[i] && target > nums[mid]) {
                    i = mid + 1;
                } else {
                    j = mid - 1;
                }
            }else{
                if(target>=nums[i] && target<nums[mid]){
                    j=mid-1;
                }else{
                    i=mid+1;
                }
            }
            mid=i+(j-i)/2;
        }
        return -1;
    }

81. 搜索旋轉排序數組 II

假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,0,1,2,2,5,6] 可能變爲 [2,5,6,0,0,1,2] )。
編寫一個函數來判斷給定的目標值是否存在於數組中。若存在返回 true,否則返回 false。
示例 1:
輸入: nums = [2,5,6,0,0,1,2], target = 0
輸出: true
示例 2:
輸入: nums = [2,5,6,0,0,1,2], target = 3
輸出: false
———————————————————————————————————————————————
81題跟33題的差別在於數組內可以有重複的數字,但是主題思想還是用二分查找,只是根據實際情況有所調整。
選擇區間的方式是:
1.首先比較nums[i]和nums[mid]的大小關係
2.如果nums[i]>nums[mid],則說明拐點一定在i和mid之間,因此決策策略和之間是完全一模一樣的
在這裏插入圖片描述
這時如果
(1)target>=nums[i],則target一定在i~mid這段範圍內
(2)targer<nums[mid],則targer也一定在i~mid這段範圍內
(3)nums[mid]<targer<nums[i],則target一定在mid~j這段範圍內

3.如果nums[i]<nums[mid],則說明從i到mid這段區域一定是升序的,決策策略也和之前是一模一樣的
在這裏插入圖片描述
這時如果
(1)target>nums[mid],則target一定在mid~j這段範圍內
(2)targer<nums[i],則targer也一定在mid~j這段範圍內
(3)nums[i]<=targer<nums[mid],則target一定在i~mid這段範圍內

4.特殊的情況是如果nums[i]=nums[mid],這時我們無法判斷該往哪邊選區間
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這時我們只需要捨棄掉i這個點即可,也就是直接i++
因爲既然nums[i]=nums[mid]≠target,那i點對我們來說就無用了,直接捨棄掉i,直到i和mid有大小關係時就可以用前2個方法來判斷。
修改一下上題的代碼:

public boolean search(int[] nums, int target) {
        int i=0,j=nums.length-1;
        int mid=i+(j-i)/2;
        while(i<=j){
            if(nums[mid]==target){
                return true;
            }
            if(nums[i]>nums[mid]){
                if(target>nums[mid] && target<nums[i]){
                    i=mid+1;
                }else{
                    j=mid-1;
                }
            }else if(nums[i]<nums[mid]){
                if(target>=nums[i] && target<nums[mid]){
                    j=mid-1;
                }else {
                    i=mid+1;
                }
            }else{
                i++;
            }
            mid=i+(j-i)/2;
        }
        return false;
    }

153. 尋找旋轉排序數組中的最小值

假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
請找出其中最小的元素。
你可以假設數組中不存在重複元素。
示例 1:
輸入: [3,4,5,1,2]
輸出: 1
示例 2:
輸入: [4,5,6,7,0,1,2]
輸出: 0
———————————————————————————————————————————————
這時33題的變種,其實按照前2題的思路,就大概可以知道,把最小值當做一次targer查找,只是這個target永遠比我們找到的數還要小而已。

選擇區間的方式是:
1.首先比較nums[i]和nums[mid]的大小關係
2.如果nums[i]>nums[mid],記錄此時獲得的最小數nums[mid]
如果還存在更小值,那隻能在i~mid這段區間內
在這裏插入圖片描述

3.如果nums[i]<nums[mid],記錄此時的最小數nums[i],如果還存在更小值,則只能在mid~j這段區間內
在這裏插入圖片描述
這時如果
(1)target>nums[mid],則target一定在mid~j這段範圍內
(2)targer<nums[i],則targer也一定在mid~j這段範圍內
(3)nums[i]<=targer<nums[mid],則target一定在i~mid這段範圍內

4.如果nums[i]=nums[mid],這是一個特殊情況,因爲不存在重複的數,所以如果i和mid相等的話,只能說明只剩兩個數了,這時可以直接讓i++,讓i和j都變成j對應的位置,這樣就可以把i和j都比較一遍,不然會錯過j這個數。
改造一下代碼:

public int findMin(int[] nums) {
		int i=0,j=nums.length-1;
        int mid=i+(j-i)/2;
        int min=nums[0];
        while(i<=j){
            if(nums[i]<nums[mid]){
                min=Math.min(min,nums[i]);
                i=mid+1;
            }else if(nums[i]>nums[mid]){
                min=Math.min(min,nums[mid]);
                j=mid-1;
            }else{
                min=Math.min(min,nums[mid]);
                i++;
            }
            mid=i+(j-i)/2;
        }
        return min;
    }

154. 尋找旋轉排序數組中的最小值 II

假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
請找出其中最小的元素。
注意數組中可能存在重複的元素。
示例 1:
輸入: [1,3,5]
輸出: 1
示例 2:
輸入: [2,2,2,0,1]
輸出: 0
———————————————————————————————————————————————
先說結論,這題的代碼和上一題一模一樣,完全一模一樣。
選擇區間的方式是:
1.首先比較nums[i]和nums[mid]的大小關係
2.如果nums[i]>nums[mid],記錄此時的最小值nums[mid],此時若存在更小值,則一定在i~mid之間
在這裏插入圖片描述
3.如果nums[i]<nums[mid],記錄此時的最小值nums[i],若存在更小值,則只能在mid~j這段區間在這裏插入圖片描述
4.特殊的情況是如果nums[i]=nums[mid],首先記錄下此時的最小值nums[i],這時我們無法判斷最小值到底該往哪邊選區間
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這時我們只需要捨棄掉i這個點即可,也就是仍然直接i++,在剩下的區間中再去找最小值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章