數(shù)據(jù)結(jié)構(gòu)也不難:二分查找模版與例題

原文鏈接: 點(diǎn)這里
更多內(nèi)容就在我的個(gè)人博客 BlackBlog.tech 歡迎關(guān)注涝影!
謝謝大家!

本文源自LeetCode二分查找章節(jié)争占,通過(guò)本章節(jié)的練習(xí)對(duì)二分查找的基礎(chǔ)知識(shí)進(jìn)行掌握燃逻,同時(shí)能夠利用二分查找解決部分問(wèn)題。二分查找是一個(gè)效率非常高的算法臂痕,它充分利用了元素間的次序關(guān)系伯襟,采用分治策略,可在最壞的情況下用O(log n)完成搜索任務(wù)握童。掌握二分查找對(duì)于提升代碼效率很有幫助逗旁。
LeetCode二分查找地址:https://leetcode-cn.com/explore/learn/card/binary-search/208/background/832/
本篇文章將介紹3個(gè)二分模版,同時(shí)完成十個(gè)例題舆瘪。在閱讀過(guò)程中,如果對(duì)三個(gè)模板的區(qū)別難以理解红伦,請(qǐng)務(wù)必完全閱讀完英古,這是一個(gè)需要逐步思考的過(guò)程。
給個(gè)目錄:
LeetCode704 二分查找
LeetCode69 x 的平方根
LeetCode374 猜數(shù)字大小
LeetCode33 搜索旋轉(zhuǎn)排序數(shù)組
LeetCode278 第一個(gè)錯(cuò)誤的版本
LeetCode75 尋找峰值
LeetCode159 尋找旋轉(zhuǎn)排序數(shù)組中的最小值
LeetCode34 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置
LeetCode658 找到 K 個(gè)最接近的元素

二分查找基礎(chǔ)知識(shí)

什么是二分查找
二分查找是計(jì)算機(jī)科學(xué)中最基本昙读、最有用的算法之一召调。 它描述了在有序集合中搜索特定值的過(guò)程。

二分查找中使用的術(shù)語(yǔ):

目標(biāo) Target —— 你要查找的值
索引 Index —— 你要查找的當(dāng)前位置
左蛮浑、右指示符 Left唠叛,Right —— 我們用來(lái)維持查找空間的指標(biāo)
中間指示符 Mid —— 我們用來(lái)應(yīng)用條件來(lái)確定我們應(yīng)該向左查找還是向右查找的索引

二分查找原理

二分查找是一種在每次比較之后將查找空間一分為二的算法。每次需要查找集合中的索引或元素時(shí)沮稚,都應(yīng)該考慮二分查找艺沼。如果集合是無(wú)序的,我們可以總是在應(yīng)用二分查找之前先對(duì)其進(jìn)行排序蕴掏。

二分查找一般由三個(gè)主要部分組成:

預(yù)處理 —— 如果集合未排序障般,則進(jìn)行排序。
二分查找 —— 使用循環(huán)或遞歸在每次比較后將查找空間劃分為兩半盛杰。
后處理 —— 在剩余空間中確定可行的候選者挽荡。

二分查找是一個(gè)效率非常高的算法,它充分利用了元素間的次序關(guān)系即供,采用分治策略定拟,可在最壞的情況下用O(log n)完成搜索任務(wù)。掌握二分查找對(duì)于提升代碼效率很有幫助逗嫡。

復(fù)雜度
二分查找的時(shí)間復(fù)雜度:O(log n)青自,因?yàn)槎植檎沂峭ㄟ^(guò)對(duì)查找空間中間的值應(yīng)用一個(gè)條件來(lái)操作的株依,并因此將查找空間折半,在更糟糕的情況下性穿,我們將不得不進(jìn)行 O(log n) 次比較勺三,其中 n 是集合中元素的數(shù)目。
二分查找的空間復(fù)雜度:O(1)需曾,不需要任何其他額外空間吗坚。

LeetCode704 二分查找

我們先來(lái)一道最為基礎(chǔ)的二分查找題目,理解一下二分查找呆万。
本題為L(zhǎng)eetCode704 二分查找商源,屬于二分查找中最為基礎(chǔ)的練習(xí)。二分查找谋减,顧名思義牡彻,就是將數(shù)組一分為二,在左右兩邊查找出爹,確定元素區(qū)間之后再次一分為二庄吼,直至確定元素。

題目

給定一個(gè) n 個(gè)元素有序的(升序)整型數(shù)組 nums 和一個(gè)目標(biāo)值 target 严就,寫一個(gè)函數(shù)搜索 nums 中的 target总寻,如果目標(biāo)值存在返回下標(biāo),否則返回 -1梢为。

示例 1:

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現(xiàn)在 nums 中并且下標(biāo)為 4

示例 2:

輸入: nums = [-1,0,3,5,9,12], target = 2
輸出: -1
解釋: 2 不存在 nums 中因此返回 -1

提示:

你可以假設(shè) nums 中的所有元素是不重復(fù)的渐行。
n 將在 [1, 10000]之間。
nums 的每個(gè)元素都將在 [-9999, 9999]之間铸董。

C++代碼

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        while(left<=right){
            int mid = (left+right)/2;  //計(jì)算計(jì)算中間值
            if(nums[mid]==target) return mid; //如果找到了target祟印,返回當(dāng)前的索引
            if(nums[mid]<target) left = mid+1; //如果nums[mid]<target,證明正確的值在當(dāng)前位置的右邊
            if(nums[mid]>target) right = mid-1; //如果nums[mid]>target粟害,證明正確的值在當(dāng)前位置的左邊
        }
        return -1;
    }
};

體會(huì)

本題是二分查找最為基礎(chǔ)的題目蕴忆,對(duì)于理解二分查找非常重要。

二分查找 模版#1

模板 #1 是二分查找的最基礎(chǔ)和最基本的形式悲幅。這是一個(gè)標(biāo)準(zhǔn)的二分查找模板孽文,是非常基礎(chǔ)簡(jiǎn)單的二分查找夺艰。模板 #1 用于查找可以通過(guò)訪問(wèn)數(shù)組中的單個(gè)索引來(lái)確定的元素或條件芋哭。模版#1 不需要后處理,因?yàn)槊恳徊街杏舾保愣荚跈z查是否找到了元素减牺。如果到達(dá)末尾,則知道未找到該元素。

模版#1 對(duì)應(yīng)的例題為:
LeetCode69 x 的平方根
LeetCode374 猜數(shù)字大小
LeetCode33 搜索旋轉(zhuǎn)排序數(shù)組

模版#1 C++代碼

int binarySearch( vector<int> & nums, int target )
{
    if ( nums.size() == 0 )
        return(-1);
    int left = 0, right = nums.size() - 1;
    while ( left <= right )
    {
        /* Prevent (left + right) overflow */
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        else if ( nums[mid] < target ) left = mid + 1;
        else right = mid - 1;
    }
    /* End Condition: left > right */
    return(-1);
}

語(yǔ)法關(guān)鍵

初始條件:left = 0, right = length-1
終止:left > right
向左查找:right = mid-1
向右查找:left = mid+1

LeetCode69 x 的平方根

題目

實(shí)現(xiàn) int sqrt(int x) 函數(shù)拔疚。

計(jì)算并返回 x 的平方根肥隆,其中 x 是非負(fù)整數(shù)。

由于返回類型是整數(shù)稚失,結(jié)果只保留整數(shù)的部分栋艳,小數(shù)部分將被舍去。

示例 1:

輸入: 4
輸出: 2

示例 2:

輸入: 8
輸出: 2
說(shuō)明: 8 的平方根是 2.82842..., 
     由于返回類型是整數(shù)句各,小數(shù)部分將被舍去吸占。

C++代碼

class Solution {
public:
    int mySqrt(int x){
        int left = 0;
        int right = x;
        if (x <= 1) return x;
        int ans= 0;
        while(left<=right){
            int mid = (right+left) /2; //計(jì)算中間值
            if(x/mid >= mid ) {
                left = mid+1; //如果mid*mid<=x 證明mid小了 left = mid+1
                ans = mid; //當(dāng)前的mid作為ans
            }
            else right = mid-1; //否則right = mid-1
        }
        return ans;
    }
};

體會(huì)

上述代碼采用二分查找的思路,與模版完全一致凿宾,注意每次計(jì)算的過(guò)程中要將mid存儲(chǔ)在ans中矾屯,因?yàn)槲覀儫o(wú)法通過(guò)mid*mid==x去判斷mid是否是正確值,所以我們將mid存儲(chǔ)在ans中初厚,最后返回件蚕。

該題還可以使用牛頓迭代法的思路進(jìn)行求解。
附代碼:

class Solution {
public:
    int mySqrt(int x) {
        if (x == 0) return 0;
        double res = 1, pre = 0;
        while (abs(res - pre) > 1e-6) {
            pre = res;
            res = (res + x / res) / 2;
        }
        return int(res);
    }
};

LeetCode374 猜數(shù)字大小

題目

我們正在玩一個(gè)猜數(shù)字游戲产禾。 游戲規(guī)則如下:
我從 1 到 n 選擇一個(gè)數(shù)字排作。 你需要猜我選擇了哪個(gè)數(shù)字。
每次你猜錯(cuò)了亚情,我會(huì)告訴你這個(gè)數(shù)字是大了還是小了妄痪。
你調(diào)用一個(gè)預(yù)先定義好的接口 guess(int num),它會(huì)返回 3 個(gè)可能的結(jié)果(-1势似,10):

-1 : 我的數(shù)字比較小
 1 : 我的數(shù)字比較大
 0 : 恭喜!你猜對(duì)了僧著!

示例 :

輸入: n = 10, pick = 6
輸出: 6

C++代碼

int guess(int num);
class Solution {
public:
    int guessNumber(int n) {
        long left = 0;
        long right = n;
        while(left<=right){
            long mid = (left+right)/2;
            if(guess(mid)==0) return mid;
            if(guess(mid)==1) left = mid+1;
            if(guess(mid)==-1) right = mid-1;
        }
        return left;
    }
};

體會(huì)

這個(gè)題完全就是大一的課后題履因,基礎(chǔ)二分查找問(wèn)題,與模版#1思路完全一致盹愚,比較大小的部分已經(jīng)在guess中封裝了栅迄,注意判斷好guess最后的返回值。

LeetCode33 搜索旋轉(zhuǎn)排序數(shù)組

題目

假設(shè)按照升序排序的數(shù)組在預(yù)先未知的某個(gè)點(diǎn)上進(jìn)行了旋轉(zhuǎn)皆怕。
( 例如毅舆,數(shù)組 [0,1,2,4,5,6,7] 可能變?yōu)?[4,5,6,7,0,1,2] )。
搜索一個(gè)給定的目標(biāo)值愈腾,如果數(shù)組中存在這個(gè)目標(biāo)值憋活,則返回它的索引,否則返回 -1虱黄。
你可以假設(shè)數(shù)組中不存在重復(fù)的元素悦即。
你的算法時(shí)間復(fù)雜度必須是 O(log n) 級(jí)別。

示例 1:

輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4

示例 2:

輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1

C++代碼

class Solution {
public:
    int search(vector<int> nums, int target) {
        //對(duì)于特殊情況的判斷
        if(nums.size()==0) return -1;
        if(nums.size()==1 && nums[0]==target) return 0;
        if(nums.size()==1 && nums[0]!=target) return -1;

        int index= 0 ; //記錄軸點(diǎn)
        int size = nums.size();//記錄下初始數(shù)組的長(zhǎng)度
        for(int i=0;i<nums.size();i++){
            if(nums[i]<nums[i-1]){
                index = i; //找到軸點(diǎn)后結(jié)束循環(huán)
                break;
            }
            nums.push_back(nums[i]); //在找到軸點(diǎn)之前,將軸點(diǎn)前數(shù)字依次放在數(shù)組尾部
        }
        int left = index; //left = 軸點(diǎn)
        int right = nums.size()-1; //right = 數(shù)組當(dāng)前長(zhǎng)度-1 
        int ans = -1;//ans表示最終的位置
        while(left<=right){
            int mid = (left+right)/2; //計(jì)算中點(diǎn)
            if(nums[mid]==target){
                ans = mid; //如果nums[mid] == target 
                break;
            }
            if(nums[mid]<target) left = mid+1; //如果nums[mid]<target 修改left
            if(nums[mid]>target) right = mid-1; //如果 nums[mid]>target 修改 right
            if(nums[right] == target ) ans = right; //如果 nums[right] == target 將 ans修改為right 否則ans為-1
            else ans = -1;
        }
        return ans % size;
    }
};

體會(huì)

依舊是一個(gè)模版題辜梳,難度較低粱甫。我們之前說(shuō)過(guò),二分查找的三個(gè)步驟作瞄,第一步是預(yù)處理 即如果集合未排序茶宵,則進(jìn)行排序。這個(gè)題將數(shù)組對(duì)某一軸點(diǎn)進(jìn)行了旋轉(zhuǎn)宗挥,需要進(jìn)行預(yù)處理乌庶。我們尋找到軸點(diǎn),將數(shù)組修改為升序后属韧,預(yù)處理完成安拟,后續(xù)進(jìn)行二分即可。該題最后的處理有些類似模板2宵喂。

二分查找 模版#2

模板 #2 是二分查找的高級(jí)模板糠赦。它用于查找需要訪問(wèn)數(shù)組中當(dāng)前索引及其直接右鄰居索引的元素或條件。
查找條件需要訪問(wèn)元素的直接右鄰居锅棕。
使用元素的右鄰居來(lái)確定是否滿足條件拙泽,并決定是向左還是向右。
保證查找空間在每一步中至少有 2 個(gè)元素裸燎。
需要進(jìn)行后處理顾瞻。 當(dāng)你剩下 1 個(gè)元素時(shí),循環(huán) / 遞歸結(jié)束德绿。 需要評(píng)估剩余元素是否符合條件荷荤。

模版#2 對(duì)應(yīng)的例題為:
LeetCode278 第一個(gè)錯(cuò)誤的版本
LeetCode75 尋找峰值
LeetCode159 尋找旋轉(zhuǎn)排序數(shù)組中的最小值

模版#2 C++代碼

int binarySearch(vector<int>& nums, int target){
   if(nums.size() == 0)
       return -1;

   int left = 0, right = nums.size();
   while(left < right){
       // Prevent (left + right) overflow
       int mid = left + (right - left) / 2;
       if(nums[mid] == target){ return mid; }
       else if(nums[mid] < target) { left = mid + 1; }
       else { right = mid; }
   }

   // Post-processing:
   // End Condition: left == right
   if(left != nums.size() && nums[left] == target) return left;
   return -1;
}

語(yǔ)法關(guān)鍵

初始條件:left = 0, right = length
終止:left == right
向左查找:right = mid
向右查找:left = mid+1

LeetCode278 第一個(gè)錯(cuò)誤的版本

題目

你是產(chǎn)品經(jīng)理,目前正在帶領(lǐng)一個(gè)團(tuán)隊(duì)開(kāi)發(fā)新的產(chǎn)品移稳。不幸的是蕴纳,你的產(chǎn)品的最新版本沒(méi)有通過(guò)質(zhì)量檢測(cè)。由于每個(gè)版本都是基于之前的版本開(kāi)發(fā)的个粱,所以錯(cuò)誤的版本之后的所有版本都是錯(cuò)的古毛。

假設(shè)你有 n 個(gè)版本 [1, 2, ..., n],你想找出導(dǎo)致之后所有版本出錯(cuò)的第一個(gè)錯(cuò)誤的版本都许。

你可以通過(guò)調(diào)用 bool isBadVersion(version) 接口來(lái)判斷版本號(hào) version 是否在單元測(cè)試中出錯(cuò)稻薇。實(shí)現(xiàn)一個(gè)函數(shù)來(lái)查找第一個(gè)錯(cuò)誤的版本。你應(yīng)該盡量減少對(duì)調(diào)用 API 的次數(shù)胶征。

示例:

給定 n = 5塞椎,并且 version = 4 是第一個(gè)錯(cuò)誤的版本。

調(diào)用 isBadVersion(3) -> false
調(diào)用 isBadVersion(5) -> true
調(diào)用 isBadVersion(4) -> true

所以睛低,4 是第一個(gè)錯(cuò)誤的版本忱屑。 

C++代碼

class Solution {
public:
    int firstBadVersion(int n) {
        long left = 0;
        long right = n;
        while(left<right){
            long mid = (left+right)/2; //計(jì)算中點(diǎn)
            if(isBadVersion(mid)==false) left = mid+1; //如果mid是正常產(chǎn)品蹬敲,證明第一個(gè)錯(cuò)誤產(chǎn)品在右側(cè)
            if(isBadVersion(mid)==true) right = mid; //如果mid是錯(cuò)誤產(chǎn)品,證明第一個(gè)錯(cuò)誤產(chǎn)品在是自己或者在左側(cè)
        }
        return left;
    }
};

體會(huì)

題目非常簡(jiǎn)單莺戒,直接套用模板#2即可伴嗡。此題與模板#1的最大區(qū)別就在于,right不能修改為mid-1从铲,而必須修改為mid瘪校。因?yàn)楫?dāng)如果mid是錯(cuò)誤產(chǎn)品,無(wú)法判斷第一個(gè)錯(cuò)誤版本在mid之前名段,還是就是當(dāng)前mid阱扬。這是模板#2與模板#1的最大不同绘沉。

LeetCode75 尋找峰值

題目

峰值元素是指其值大于左右相鄰值的元素砸彬。
給定一個(gè)輸入數(shù)組 nums掏击,其中 nums[i] ≠ nums[i+1]灌曙,找到峰值元素并返回其索引。
數(shù)組可能包含多個(gè)峰值奸攻,在這種情況下准潭,返回任何一個(gè)峰值所在位置即可她君。
你可以假設(shè) nums[-1] = nums[n] = -∞静稻。

示例 1:

輸入: nums = [1,2,3,1]
輸出: 2
解釋: 3 是峰值元素警没,你的函數(shù)應(yīng)該返回其索引 2。

示例 2:

輸入: nums = [1,2,1,3,5,6,4]
輸出: 1 或 5 
解釋: 你的函數(shù)可以返回索引 1振湾,其峰值元素為 2杀迹;
     或者返回索引 5, 其峰值元素為 6押搪。

說(shuō)明:
你的解法應(yīng)該是 O(logN) 時(shí)間復(fù)雜度的树酪。

C++代碼

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        if(nums.size()==1) return 0;
        long left = 0;
        long right = nums.size()-1;
        while(left<right){
            long mid = (left+right)/2; //計(jì)算中間值
            if(nums[mid]>nums[mid+1]) right = mid; //如果當(dāng)前值大于后一個(gè)值 證明當(dāng)前點(diǎn)可能是峰值點(diǎn)
            if(nums[mid]<nums[mid+1]) left = mid+1; //如果當(dāng)前值小于后一個(gè)值 證明在這之后沒(méi)有峰值點(diǎn)
        }
        return left;
    }
};

體會(huì)

直接套用模板2,此題與上一道題非常類似大州,如果當(dāng)前值大于后一個(gè)值 證明當(dāng)前點(diǎn)可能是峰值點(diǎn) right修改為mid续语,如果當(dāng)前值小于后一個(gè)值 證明在這之后沒(méi)有峰值點(diǎn)left修改為mid+1,最后返回left摧茴。
PS:如果此題要求求解最值绵载,則需要使用三分法埂陆。

LeetCode159 尋找旋轉(zhuǎn)排序數(shù)組中的最小值

題目

假設(shè)按照升序排序的數(shù)組在預(yù)先未知的某個(gè)點(diǎn)上進(jìn)行了旋轉(zhuǎn)苛白。
( 例如,數(shù)組 [0,1,2,4,5,6,7] 可能變?yōu)?[4,5,6,7,0,1,2] )焚虱。
請(qǐng)找出其中最小的元素购裙。
你可以假設(shè)數(shù)組中不存在重復(fù)元素。
示例 1:

輸入: [3,4,5,1,2]
輸出: 1

示例 2:

輸入: [4,5,6,7,0,1,2]
輸出: 0

C++代碼

class Solution {
public:
    int findMin(vector<int> nums) {
        if(nums.size()==1) return nums[0];
        int left = 0;
        int right = nums.size()-1;
        while(left<right && nums[left]>nums[right] ){
            int mid = (left+right)/2;
            if(nums[left]<=nums[mid]) left = mid+1; //如果nums[left]<=nums[mid]鹃栽,即mid前的序列是遞增的躏率,則軸值一定不在mid之前,修改left=mid+1
            else if(nums[left]>nums[mid]) right = mid;//如果nums[left]>nums[mid],mid前的序列不是遞增的薇芝,則可能存在軸值蓬抄,切mid有可能是軸值,修改right=mid夯到。
        }
        return nums[left];
    }
};

體會(huì)

依舊是一道模版題嚷缭。在確定mid之后,需要比對(duì)left與mid之間的關(guān)系耍贾,如果nums[left]<=nums[mid]阅爽,即mid前的序列是遞增的,則軸值一定不在mid之前荐开,修改left=mid+1付翁;如果nums[left]>nums[mid],mid前的序列不是遞增的晃听,則可能存在軸值百侧,切mid有可能是軸值,修改right=mid杂伟。注意確定好邊界即可移层。

二分查找 模版#3

模板 #3 是二分查找的另一種獨(dú)特形式。它用于搜索需要訪問(wèn)當(dāng)前索引及其在數(shù)組中的直接左右鄰居索引的元素或條件赫粥。
搜索條件需要訪問(wèn)元素的直接左右鄰居观话。
使用元素的鄰居來(lái)確定它是向右還是向左。
保證查找空間在每個(gè)步驟中至少有 3 個(gè)元素越平。
需要進(jìn)行后處理频蛔。 當(dāng)剩下 2 個(gè)元素時(shí),循環(huán) / 遞歸結(jié)束秦叛。 需要評(píng)估其余元素是否符合條件晦溪。

模版#3 對(duì)應(yīng)的例題為:
LeetCode34 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置
LeetCode658 找到 K 個(gè)最接近的元素

模版#3 C++代碼

int binarySearch(vector<int>& nums, int target){
    if (nums.size() == 0)
        return -1;

    int left = 0, right = nums.size() - 1;
    while (left + 1 < right){
        // Prevent (left + right) overflow
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid;
        } else {
            right = mid;
        }
    }
    // Post-processing:
    // End Condition: left + 1 == right
    if(nums[left] == target) return left;
    if(nums[right] == target) return right;
    return -1;
}

語(yǔ)法關(guān)鍵

初始條件:left = 0, right = length-1
終止:left + 1 == right
向左查找:right = mid
向右查找:left = mid

LeetCode34 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置

題目

給定一個(gè)按照升序排列的整數(shù)數(shù)組 nums,和一個(gè)目標(biāo)值 target挣跋。找出給定目標(biāo)值在數(shù)組中的開(kāi)始位置和結(jié)束位置三圆。

你的算法時(shí)間復(fù)雜度必須是 O(log n) 級(jí)別。

如果數(shù)組中不存在目標(biāo)值避咆,返回 [-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]

C++代碼

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0) return {-1,-1};
        int left = 0;
        int right = nums.size()-1;
        while(left<right){
            int mid = (left+right)/2;
            if(nums[mid]==target) { //如果找到target 只需要向前向后搜索這個(gè)序列 找到起點(diǎn)和終點(diǎn)即可
                int I=mid;
                int j=mid;
                while(nums[i+1]==target && i<nums.size()-1) i=i+1; 
                while(nums[j-1]==target && j>0) j = j-1;
                return{j,i};
            }
            if(nums[mid]<target) left = mid+1; //如果nums[mid]<target 說(shuō)明target在mid的右側(cè)
            else if(nums[mid]>target) right = mid-1; //如果nums[mid]>target 說(shuō)明target在mid的左側(cè)
        }
        return{-1,-1};
    }
};

體會(huì)

做這個(gè)題的時(shí)候很想將模版#3套用進(jìn)去,但是沒(méi)有成功查库。這個(gè)題使用的是比較經(jīng)典的二分思路路媚。本次需要尋找對(duì)應(yīng)序列的起點(diǎn)與終點(diǎn),所以當(dāng)我們找到一個(gè)target的值時(shí)樊销,要向前向后進(jìn)行搜索確定序列的起點(diǎn)終點(diǎn)整慎。

LeetCode658 找到 K 個(gè)最接近的元素

題目

給定一個(gè)排序好的數(shù)組脏款,兩個(gè)整數(shù) k 和 x,從數(shù)組中找到最靠近 x(兩數(shù)之差最锌阍啊)的 k 個(gè)數(shù)撤师。返回的結(jié)果必須要是按升序排好的。如果有兩個(gè)數(shù)與 x 的差值一樣拧揽,優(yōu)先選擇數(shù)值較小的那個(gè)數(shù)丈氓。
示例 1:

輸入: [1,2,3,4,5], k=4, x=3
輸出: [1,2,3,4]

示例 2:

輸入: [1,2,3,4,5], k=4, x=-1
輸出: [1,2,3,4]

說(shuō)明:
k 的值為正數(shù),且總是小于給定排序數(shù)組的長(zhǎng)度强法。
數(shù)組不為空万俗,且長(zhǎng)度不超過(guò) 104
數(shù)組里的每個(gè)元素與 x 的絕對(duì)值不超過(guò) 104

C++代碼

class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        int left =0;
        int right = arr.size()-k; //為了防止越界,將right定為arr.size()-k饮怯,做預(yù)處理闰歪。
        while(left<right){
            int mid = (left+right)/2;
            if(abs(arr[mid]-x)<=abs(arr[mid+k]-x)){ //比較arr[mid]與x的差值 與 arr[mid+k]與x的差值,如果abs(arr[mid]-x)<=abs(arr[mid+k]-x)蓖墅,證明左側(cè)離x更接近库倘,否則右側(cè)與x更接近。
                right = mid;
            }
            else left = mid+1;
        }
        return vector<int>(arr.begin()+left, arr.begin() + left+k); //返回結(jié)果的時(shí)候同樣要做處理论矾。
    }
};

體會(huì)

本題依舊沒(méi)有用到模版#3(尷尬=挑妗),采用類似模版#2的思路贪壳,但本題的特殊之處在于預(yù)處理和后處理饱亿。預(yù)處理為了防止越界,將將right定為arr.size()-k闰靴,返回結(jié)果時(shí)返回的事[left,left+k]這一段序列彪笼。
本題使用二分的思路,我們要找與x最接近的k個(gè)數(shù)蚂且,每次計(jì)算出mid后配猫,比較arr[mid]與x的差值 與 arr[mid+k]與x的差值,如果abs(arr[mid]-x)<=abs(arr[mid+k]-x)杏死,證明左側(cè)離x更接近泵肄,更新right;否則右側(cè)與x更接近淑翼,更新left腐巢。

總結(jié)

模版對(duì)比

(圖片源自LeetCode)
三個(gè)模版的主要差異在于左、中窒舟、右索引的分配系忙,循環(huán)或遞歸終止條件诵盼,后處理惠豺。
二分查找是非常常用的算法银还,真實(shí)應(yīng)用中,我們并不需要死記硬背這三個(gè)模版洁墙,更重要的是通過(guò)這三個(gè)模版理解二分搜索蛹疯,在實(shí)際應(yīng)用中,仔細(xì)確定查找邊界热监、遞歸范圍捺弦,一般便可以解決問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孝扛,一起剝皮案震驚了整個(gè)濱河市列吼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苦始,老刑警劉巖寞钥,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異陌选,居然都是意外死亡理郑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門咨油,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)您炉,“玉大人,你說(shuō)我怎么就攤上這事役电∽簦” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵法瑟,是天一觀的道長(zhǎng)囱晴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瓢谢,這世上最難降的妖魔是什么畸写? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮氓扛,結(jié)果婚禮上枯芬,老公的妹妹穿的比我還像新娘。我一直安慰自己采郎,他們只是感情好千所,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蒜埋,像睡著了一般淫痰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上整份,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天待错,我揣著相機(jī)與錄音籽孙,去河邊找鬼。 笑死火俄,一個(gè)胖子當(dāng)著我的面吹牛犯建,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓜客,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼适瓦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谱仪?” 一聲冷哼從身側(cè)響起玻熙,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疯攒,沒(méi)想到半個(gè)月后揭芍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卸例,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年称杨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筷转。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姑原,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呜舒,到底是詐尸還是另有隱情锭汛,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布袭蝗,位于F島的核電站唤殴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏到腥。R本人自食惡果不足惜朵逝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乡范。 院中可真熱鬧配名,春花似錦、人聲如沸晋辆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瓶佳。三九已至芋膘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背为朋。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工臂拓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人潜腻。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像器仗,于是被迫代替她去往敵國(guó)和親融涣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • <center>#1 Two Sum</center> link Description:Given an arr...
    鐺鐺鐺clark閱讀 2,154評(píng)論 0 3
  • 算法思想貪心思想雙指針排序快速選擇堆排序桶排序荷蘭國(guó)旗問(wèn)題二分查找搜索BFSDFSBacktracking分治動(dòng)態(tài)...
    第六象限閱讀 3,095評(píng)論 0 0
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)精钮。 張土汪:刷leetcod...
    土汪閱讀 12,745評(píng)論 0 33
  • 本文首發(fā)于我的個(gè)人博客:尾尾部落 二分查找法作為一種常見(jiàn)的查找方法威鹿,將原本是線性時(shí)間提升到了對(duì)數(shù)時(shí)間范圍,大大縮短...
    繁著閱讀 29,453評(píng)論 3 9
  • 昨天蔡依林一身紅色運(yùn)動(dòng)套裝外搭了一件做舊牛仔外衣出現(xiàn)在北京機(jī)場(chǎng),實(shí)話講臂容,蔡依林還真的抓住了2018年的一個(gè)潮流科雳,運(yùn)...
    蹦豆啊閱讀 404評(píng)論 0 0