leetcode 經典二分查找算法題目(思路、方法、code)

leetcode 經典二分查找算法題目(思路、方法、code)

35. 搜索插入位置

給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。

你可以假設數組中無重複元素。

示例 1:
輸入: [1,3,5,6], 5
輸出: 2
    
示例 2:
輸入: [1,3,5,6], 2
輸出: 1

分析:由於數組是一個有序數組,故可以採用二分搜索找位置,可以在 O(logn)O(logn) 的時間複雜度內找到。

  • 若target在數組中出現,則典型的二分查找方法即可
  • 若target在數組中沒有出現
    • 若target<nums[mid] && target>nums[mid-1] ,說明返回的下標應該是 mid
    • 若target>nums[mid] && target<nums[mid+1], 說明返回的下標應該是mid+1
    • 若mid==0 || mid==size-1 ,也就是說target應該的位置在於數組外
      • 若target<nums[0],返回0
      • 若target>nums[size-1],返回size
//暴力的方法:
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]>=target)
                return i;
        }
        return nums.size();
    }
};
//二分查找的方法:
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
       int index=-1;
       int begin=0,end=nums.size()-1;
       while(index==-1) //未找到位置
       {
           int mid=(begin+end)/2;
           if(target==nums[mid])
                index=mid;
           else if(target<nums[mid])  //mid處值大於target
           {
               if(mid==0||target>nums[mid-1]) //短路,不用判斷mid-1非法
                    index=mid;   //確定了target的位置
               end=mid-1; //因爲已經判斷過mid的位置,直接mid-1即可
           }
           else if(target>nums[mid])
           {
               if(mid==nums.size()-1||target<nums[mid+1])
                        index=mid+1;
                begin=mid+1;
           }
       }
       return index;
    }
};

69. x 的平方根

實現 int sqrt(int x) 函數。

計算並返回 x 的平方根,其中 x 是非負整數。

由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。

示例 1:
輸入: 4
輸出: 2

示例 2:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842..., 
     由於返回類型是整數,小數部分將被捨去。

分析:x的平方根,在此處是指最大的滿足 r*r<=x的r值,故可以用二分查找,因爲該值一定在0~x間,故可以逐步判斷每次減少一半區間,在 O(logn)O(logn) 的時間複雜度內找到其應該在的位置

class Solution {
public:
    int mySqrt(int x) 
    {
        int left = 0, right = x, ans = -1;
        while (left <= right) 
        {
            int mid = (left+right)/ 2;
            if ((long long)mid * mid <= x)  //避免溢出
            {
                ans = mid;
                left = mid + 1;
            }
            else 
            {
                right = mid - 1;
            }
        }
        return ans;
    }
};

34. 在排序數組中查找元素的第一個和最後一個位置

給定一個按照升序排列的整數數組 nums,和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。

你的算法時間複雜度必須是 O(log n) 級別。

如果數組中不存在目標值,返回 [-1, -1]。

示例 1:
輸入: nums = [5,7,7,8,8,10], target = 8
輸出: [3,4]
示例 2:
輸入: nums = [5,7,7,8,8,10], target = 6
輸出: [-1,-1]

分析:實際上,要找的位置,可以這樣理解:如果target沒有出現,則返回[-1,-1],否則,返回target的最左索引和target的最右索引。由於數組是排序的,因此採用二分查找,思路如下:

  • 首先利用二分查找最左索引,最左索引的特徵是該座標左側應當不是該值,該座標處是該值
  • 然後同樣方法查找最右索引,最右索引的特徵是該座標右側不是該值,該座標處是該值
  • 返回即可
class Solution {
public:
    int left_bound(vector<int> & nums,int target)
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid]) //相等時候還要找到最左側的該值
            {
                if(mid==0||nums[mid-1]<target) //說明是最左的索引了
                    return mid;
                end=mid-1; //否則令end左移
            }
            else if(target<nums[mid])
                end=mid-1;
            else if(target>nums[mid])
                begin=mid+1;
        }
        return -1;
    }
    int right_bound(vector<int> & nums,int target)
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid]) //相等時候還要找到最右側的該值
            {
                if(mid==nums.size()-1||nums[mid+1]>target) //說明是最右的索引了
                    return mid;
                begin=mid+1; //否則令end左移
            }
            else if(target<nums[mid])
                end=mid-1;
            else if(target>nums[mid])
                begin=mid+1;
        }
        return -1;
    }

    vector<int> searchRange(vector<int>& nums, int target) 
    {
        vector<int> result;
        int left=left_bound(nums,target);
        int right=0;
        if(left==-1)
            right=-1;
        else
            right=right_bound(nums,target);
        result.push_back(left);
        result.push_back(right);
        return result;

    }
};

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,2,3,4,5
旋轉後可以爲  3,4,5,1,2
因此可以看出   3,4,5    1,2 分別是非遞減序列

因此根據該性質,易知可用二分查找

  • 找到區域邊界兩個位置 iijj
  • 如果 左<右 ,很顯然數組沒有發生旋轉,否則一定是左<=右
  • 找到中間位置 midmid
    • 如果中間位置的值小於右側,說明最小值在右側,令 i=mid+1i=mid+1 ,返回最初循環
    • 如果中間位置的值大於右側,說明最小值在左側,令 j=mid1j=mid-1 ,返回最初循環
    • 如果中間位置的值等於右側,則沒辦法確定最小值的位置,此時令 ii++ 繼續推進循環即可
  • 直至i=j,即找到最小值
class Solution {
public:
    int findMin(vector<int>& nums) 
    {
        int size=nums.size();
        int i=0,j=size-1;
        while(i<j)
        {
   		    if(nums[i]<nums[j]) //說明數組沒有旋轉 
		         return nums[i];
		    int mid=(i+j)>>1;
		    if(nums[mid]>nums[j])
		    {
			    i=mid+1;continue;  //注意這裏i=mid+1 
		    }
		    if(nums[mid]<nums[j])	
		    {
			    j=mid;           //j=mid  主要是讓i和j必須保持相近,避免二者相鄰時出現死循環 
			    continue;
		    }
		    if(nums[mid]==nums[j])//兩個值相等的話沒辦法二分,只能逐步右移
		    {
			    i++;
		    }
        }
        return nums[i];
    }
};

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

分析:爲了達到 O(logn)O(logn) 的時間複雜度,因此需要採用二分查找。該題採用二分查找時需要注意的是,每次找到中點後,不應該直接去比較中點與target的值,而應該首先確定左右兩個區間是否是完全遞增的。完全遞增的區間和兩個分別遞增的區間的判斷方式不一樣。

  • 每次找到中點位置
  • 如果target等於中點處的值,返回即可
  • 如果target小於中點處的值
    • 如果左側是遞增區間
      • target>=nums[begin] ,則在[begin,mid-1]中查找
      • 否則在[mid+1,end]中查找
    • 如果左側是旋轉區間
      • 因爲target小於中點處的值,故一定在旋轉區間,在[begin,mid-1]中查找
  • 如果target大於中點處的值
    • 如果左側是遞增區間,
      • 則直接在[mid+1,end]中查找
    • 如果左側是旋轉區間
      • 如果target>=nums[begin],則在[begin,mid-1]中查找
      • 否則在[mid+1,end]中查找
class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid])
                return mid;
            else if(target<nums[mid])
            {
                if(nums[begin]<nums[mid])//左側遞增
                {
                    if(target>=nums[begin])
                        end=mid-1;
                    else
                        begin=mid+1;
                }
                else if(nums[begin]>nums[mid])//左側旋轉
                {
                    end=mid-1;
                }
                else if(nums[begin]==nums[mid]) //只剩下兩個元素
                {
                    begin=mid+1;
                }
            }
            else if(target>nums[mid])
            {
                if(nums[begin]<nums[mid])//左側遞增
                {
                    begin=mid+1;
                }
                else if(nums[begin]>nums[mid]) //左側旋轉
                {
                    if(target>=nums[begin])
                        end=mid-1;
                    else
                        begin=mid+1;
                }
                else if(nums[begin]==nums[mid]) //只剩兩個元素
                    begin=mid+1;
            }
        }
        return -1;    
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章