天天看點

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

題目描述

題目連結:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

解題思路

思路1:找最小值

因為遞增的特性,找到第一個比數組首元素小的數就将它傳回就可以了。

解答代碼如下:

class Solution {
public:
    int minArray(vector<int>& numbers) 
    {
        int min = numbers[0];
        for(int i=0; i<numbers.size(); ++i)
        { 
            if(numbers[i]<min)
                return numbers[i]; // 找到第一個比它小的就行,因為後面也遞增
        }

        // 沒有找到的話說明數組本身已經是遞增了(沒有旋轉或者全是相同元素)
        return min; 
    }
};
           

思路2:二分法

雪菜大神說過:二分不一定要有單調性,二分的本質是尋找某種性質的分界點。隻要可以找到某種性質,使得區間的前半部分滿足,後半部分不滿足,那麼就可以用二分把這個分界點找到。

是以,此題就相當于找出第一個比numbers[0]小的數。

我們假設

target = numbers[0]

我們拿官方的圖來舉例;

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

去掉末尾與numbers[0]相等的數。

去重之後,最右邊的數應該小于target,如果依舊大于,說明此時數組依舊是單調遞增的,畫圖舉例如下:

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

那麼此時最小值就是數組的第一個值,直接傳回target。

否則的話,就是以下的情況。分成了兩個區間,左邊的都大于等于target,右邊的都小于target。

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

雪菜大神提供了兩個二分模闆如下:

第一個模闆用于求

邊界(例如找到第一個小于等于x的數)

劃分區間為

[left, mid]

和[mid+1, right] (把mid和left分在一起是因為任何時候mid都可以是答案)

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路

第二個模闆用于求

邊界(例如找到最後一個小于等于x的數)劃分區間為 [left, mid-1]和

[mid, right]

(把mid和right分在一起是因為任何時候mid都可以是答案

劍指刷題日記劍指 Offer 11. 旋轉數組的最小數字題目描述解題思路
bool check(int x) {/* ... */} // 檢查x是否滿足某種性質
==========================================================
模闆一,找左邊界
// 區間[l, r]被劃分成[l, mid]和[mid + 1, r]時使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判斷mid是否滿足性質
        else l = mid + 1;
    }
    return l;
}

=========================================================
模闆二,找右邊界
// 區間[l, r]被劃分成[l, mid - 1]和[mid, r]時使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;   // 比上面多加了1
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;  
    // return r也行,因為while終止條件為 l==r,此時就一個數
}

作者:yxc
連結:https://www.acwing.com/blog/content/277/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

如果是找某個值是否存在,那就更簡單了


此時區間劃分為 [l, mid-1] mid [mid+1, r]
while(l<r)
{
	int mid = l + r >> 1;
	if(arr[mid]==target)
		return mid;
	else if(arr[mid]<target)
		left = mid+1;
	else
		right = mid-1;
}

           

此題也就可以轉換為找到第一個小于target的數,因為可以采用二分模闆中求左邊界的模闆。由于if和else本身就是互斥的。由于我們區間被分成分成了兩個區,左邊的都大于等于target,右邊的都小于target。是以可以靈活選擇check(mid)為

numbers[mid]<target

或者

numbers[mid]>=target

。如果選擇

numbers[mid]<target

,那麼表明答案一定在左邊,或者mid本身,是以壓縮右邊——

right=mid

,将區間劃分為

[l, mid]

if else本身就是互斥的,是以可以靈活調整,不要太死闆。

解答代碼如下:

class Solution {
public:
    int minArray(vector<int>& numbers) 
    {
        int target = numbers[0];
        int left = 0, right = numbers.size()-1;

		// right>0不能丢,因為隻有一個數的話,去重之後 right=-1
        while(right>0 && numbers[right]==target)
            --right;   
        
        if(numbers[right]>target)   
            return target;

        // 開始二分
        while(left<right)
        {
            // 區間劃分 [left, mid] 和 [mid+1, right];
            // 因為mid也可能為答案
            int mid = (left+right)>>1;
            if(numbers[mid]<target)
                right = mid;  // 答案就在左手邊,包括mid本身
            else 
                left = mid+1;
			
			/*
			// 下面也行。因為if else本身就是互斥的,誰前誰後無所謂
            if(numbers[mid]>=target)
                left = mid+1;  // 答案肯定在右手邊,壓縮左邊
            else 
                right = mid;
            */

        }
        // 結束時left = right,隻有一個數了
        return numbers[left];
    }
};
           

繼續閱讀