problem k: 查找某一个数_quot;细节魔鬼quot; 二分查找
二分查找,是一個(gè)高效,實(shí)用,且易理解的一個(gè)查找算法, 通常時(shí)間復(fù)雜度為O(lgn)。局限性在于,待查找對象必須為有序的數(shù)組;數(shù)據(jù)量過小,優(yōu)勢不明顯,數(shù)據(jù)量過大,數(shù)組大小受限于內(nèi)存。
除此之外,二分查找是面試中比較高頻考察的算法。
高頻題目清單:
二分查找應(yīng)用(簡單)
- 374 猜數(shù)字大小
- 375 猜數(shù)字大小II
- 35 搜索插入位置
- 278 第一個(gè)錯(cuò)誤的版本
- 367 有效的完全平方數(shù)
- 69 x的平方根
- 441 排列硬幣
二分查找應(yīng)用(中等)
- 50 Pow(x,n)
- 29 兩數(shù)相除
- 34 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置
- 540 有序數(shù)組中的單一元素
- 275 H指數(shù)II
- 436 尋找右區(qū)間
- 300 最長上升子序列
- 718 最長重復(fù)子數(shù)組
- 354 俄羅斯套娃信封問題
- 658 找到K個(gè)最接近的元素
- 162 尋找峰值
- 4 尋找兩個(gè)正序數(shù)組的中位數(shù)
- 209 長度最小的子數(shù)組
- 222 完全二叉樹的節(jié)點(diǎn)個(gè)數(shù)
- 287 尋找重復(fù)數(shù)
二分查找與旋轉(zhuǎn)數(shù)組
- 153 尋找旋轉(zhuǎn)排序數(shù)組中的最小值
- 154 尋找旋轉(zhuǎn)排序數(shù)組中的最小值II
- 33 搜索旋轉(zhuǎn)排序數(shù)組
- 81 搜素旋轉(zhuǎn)排序數(shù)組II
- 74 搜索二維矩陣
二分答案法
- 378 有序矩陣中第K小的元素
- 668 乘法表中第K小第數(shù)
- 410 分割數(shù)組的最大值
- 483 最小好進(jìn)制
刷題技巧
關(guān)于二分查找,有個(gè)說法 “思路簡單,細(xì)節(jié)是魔鬼”,相關(guān)的刷題技巧和注意事項(xiàng),網(wǎng)上大神們總結(jié)了很多,自己加工總結(jié)后的筆記:
1. 題目出現(xiàn)排序數(shù)組,優(yōu)先思考是否可以用二分查找。
2. 找mid值,記住無腦寫 int mid = (left + right) >>> 1
當(dāng)然 int mid = left + (right - left) / 2 ?或者 int mid = left + ((right - left) >> 1) 也是對的.
但是萬萬不能寫int mid = (left + right) / 2 ,原因是,如果left + right 有可能造成整型溢出,而left + (right - left),因?yàn)閞ight和left一般是索引值,所以基本不會(huì)溢出。
至于推薦寫int mid = (left + right) >>> 1 ,除了裝逼外,還是jdk官方的標(biāo)準(zhǔn)寫法,也是因?yàn)槲贿\(yùn)算更快的原因。
3. int mid = (left + right) >>> 1,對于偶數(shù)數(shù)組長度來說,找的是左中點(diǎn),int mid = (left + right + 1) >>> 1,找的是右中點(diǎn)。
找?guī)讉€(gè)case測試?yán)斫夂?#xff0c;牢牢背住就行,不加一是左中點(diǎn),加一是右中點(diǎn)。
4. 循環(huán)中的終止條件,寫while(left < right) 還是while (left <= right) ,大有乾坤。
left < right 和left <= right 區(qū)別在于,如果左右邊界一樣時(shí),前者會(huì)漏掉一個(gè)數(shù)的查找。不過這也取決于最開始,right的值是否為nums.length -1。使用left < right的好處在,退出循環(huán)時(shí),left一定等于right,不用額外考慮返回值。不過需要額外處理最后一個(gè)未查找的值。
這兩部篇文章分析了兩種條件的影響和處理調(diào)整:
https://leetcode-cn.com/problems/binary-search/solution/er-fen-cha-zhao-xiang-jie-by-labuladong/
https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
這篇文章極力安利了第一種寫法:https://zhuanlan.zhihu.com/p/123863325
所有的文章,包括你我自己寫的,都只是個(gè)人的見解。
我認(rèn)為必須了解兩種的寫法具體意義和影響,不同題目下才能具體情況具體處理。
高頻題目
704.二分查找
題目描述:
給定一個(gè) n 個(gè)元素有序的(升序)整型數(shù)組 nums 和一個(gè)目標(biāo)值 target ?,寫一個(gè)函數(shù)搜索 nums 中的 target,如果目標(biāo)值存在返回下標(biāo),否則返回 -1。
示例:
輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現(xiàn)在 nums 中并且下標(biāo)為 4
解題思路:
最最基礎(chǔ)的二分查找題目,考慮以上技巧,分別使用left < right 和left <= right 兩種循環(huán)條件寫出代碼,感受邊界處理。
????if?(nums?==?null?||?nums.length?<=?0)?{
????????return?-1;
????}
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????//?左閉右閉區(qū)間,搜索不會(huì)漏掉值
????while?(left?<=?right)?{
????????mid?=?(right?+?left)?>>>?1;
????????if?(nums[mid]?==?target)?{
????????????return?mid;
????????}?else?if?(nums[mid]?>?target)?{
????????????//?因?yàn)檠h(huán)條件中是右閉區(qū)間,
????????????//所以要跳過mid值,mid得-1
????????????right?=?mid?-?1;
????????}else?{
????????????left?=?mid?+?1;
????????}
????}
????return?-1;
}
2.left < right 左閉右開寫法
public?int?search2(int[]?nums,?int?target)?{????if?(nums?==?null?||?nums.length?<=?0)?{
????????return?-1;
????}
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?????????mid?=?(right?+?left)?>>>?1;
????????if?(nums[mid]?==?target)?{
????????????return?mid;
????????}else?if?(nums[mid]?>?target)?{
????????//因?yàn)檠h(huán)條件為右開區(qū)間
????????//所以跳過mid值,右區(qū)間應(yīng)為mid
????????????right?=?mid;
????????}else?{
????????????left?=?left?+?1;
????????}
????}
????//?處理最后沒查找的元素,
????//最后一次left==right一定成立,
????//所以這塊left或者right都可以
????return?nums[left]?==?target???left?:?-1;
}
最后,附上,jdk中的寫法:
34.在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置
題目描述:
給定一個(gè)按照升序排列的整數(shù)數(shù)組 nums,和一個(gè)目標(biāo)值 target。找出給定目標(biāo)值在數(shù)組中的開始位置和結(jié)束位置。
你的算法時(shí)間復(fù)雜度必須是 O(log n) 級別。
如果數(shù)組中不存在目標(biāo)值,返回?[-1, -1]。
示例:
輸入: nums = [5,7,7,8,8,10], target = 8
輸出: [3,4]
解題思路:
先簡單二分查找到target值后,然后向前向后遍歷,找到第一個(gè)不重復(fù)的和最后一個(gè)不重復(fù)的值,這樣做的話,如果數(shù)組元素全是同一個(gè)target值,那么時(shí)間復(fù)雜度就是O(n)了,不符合題目要求了。
所以先解決 如何在O(log n)時(shí)間內(nèi)找到元素第一次出現(xiàn)的位置 :
和基礎(chǔ)二分查找不同的是,在nums[mid] == target后,不能直接返回mid,而是應(yīng)該讓縮小右邊界,繼續(xù)查找。在退出循環(huán)后,對整個(gè)數(shù)組沒有target值的情況做處理。
public?int?searchFirst(int[]?nums,?int?target)?{????if?(nums?==?null?||?nums.length?<=?0)?{
????????return?-1;
????}
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?left?+?(right?-?left)?/?2;
????????//?當(dāng)中點(diǎn)值和target相等時(shí),更新右邊界
????????//?不和下面分支判斷合一塊,是為了邏輯清晰
????????if?(nums[mid]?==?target)?{
????????????right?=?mid?-?1;
????????}else?if?(nums[mid]?>?target)?{
????????????right?=?mid?-?1;
????????}else?{
????????????left?=?mid?+?1;
????????}
????}
????//?不用nums[right]?和target比較,
????//是因?yàn)樵谥悬c(diǎn)相等時(shí),跳過了中點(diǎn),
????//且right=mid-1。
????//?而left總和mid + 1。?
????//?所以用nums[left]和target比較。
????//?當(dāng)最后一次查找,left?=?nums.length?-?1時(shí),
????//?left有可能+1,等于nums.length
????// nums[left]就有可能下標(biāo)越界。
????return?left?>=?nums.length?||?nums[left]?!=?target???-1?:?left;
}
查找目標(biāo)元素的最后一次出現(xiàn)的位置:
同樣的,在中點(diǎn)值等于target時(shí),不要返回mid,而是更新左邊界。記得最后在退出循環(huán)后,對整個(gè)數(shù)組沒有target值的情況做處理。
????if?(nums?==?null?||?nums.length?<=?0)?{
????????return?-1;
????}
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?left?+?(right?-?left)?/?2;
????????if?(nums[mid]?==?target)?{
????????????left?=?mid?+?1;
????????}else?if?(nums[mid]?>?target)?{
????????????right?=?mid?-?1;
????????}else?{
????????????left?=?mid?+?1;
????????}
????}
????//?和找第一個(gè)元素位置一樣
????//?選nums[right]和target比較是因?yàn)?#xff1a;
????//?當(dāng)中點(diǎn)和target相等時(shí),跳過mid,left = mid + 1了。
????//?當(dāng)數(shù)組只有一個(gè)元素時(shí),right有可能-1,變成-1,造成數(shù)組下標(biāo)越界。
????//?當(dāng)然更好一點(diǎn),直接?return?left?-?1;
????//?因?yàn)閘eft初始值是0,如果數(shù)組不存在target元素,即返回-1.
????return?right?}
最后,解決這道題,只需:
public?int[]?searchRange(int[]?nums,?int?target)?{????//?這塊處理邊界條件后,剩下兩個(gè)函數(shù)可去掉。
????if?(nums?==?null?||?nums.length?<=?0)?{
????????return?new?int[]{-1,-1};
????}
????return?new?int[]?{searchFirst(nums,?target),?searchLast(nums,?target)};
}
367.有效的完全平方數(shù)
題目描述:
給定一個(gè)正整數(shù) num,編寫一個(gè)函數(shù),如果 num 是一個(gè)完全平方數(shù),則返回 True,否則返回 False。
示例一:
輸入:16
輸出:true
示例二:
輸入:14
輸出: false
解題思路:
這是一道簡單題,我們每次取中點(diǎn)值,然后判斷中點(diǎn)值自乘是否等于num,如果等于,證明該num是完全平方數(shù),否則更新左右指針。
注意學(xué)習(xí)點(diǎn):
一個(gè)數(shù)的平方根最大不會(huì)超過它本身,再細(xì)點(diǎn),基本不會(huì)超過它的一半。, ?解不等式,得到 或者。所以,當(dāng)a =0,1,2,3時(shí),它們的平方根是超過它們自身一半的,對應(yīng)的平方根解分別是,0,1,1,1。所以,為了處理這4個(gè)特殊情況,初始值left = 0, right = x/2 + 1。
public?boolean?isPerfectSquare(int?num)?{????int?left?=?0,?right?=?num?/?2?+?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?mid)?>>>?1;
????????if?(mid?*?mid?==?num)?{
????????????return?true;
????????}else?if?(mid?*?mid?>?num)?{
????????????right?=?mid?-?1;
????????}else?{
????????????left?=?mid?+?1;
????????}
????}
????return?false;
}
69.x的平方根-sqrt(x)
題目描述:
實(shí)現(xiàn) int sqrt(int x)?函數(shù)。
計(jì)算并返回 x 的平方根,其中 x 是非負(fù)整數(shù)。
由于返回類型是整數(shù),結(jié)果只保留整數(shù)的部分,小數(shù)部分將被舍去。
示例:輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842..., 由于返回類型是整數(shù),小數(shù)部分將被舍去。
解題思路:
該題最快的解法并不是二分查找,有O(1)的袖珍計(jì)算器算法和同是O(lgn)卻更快的牛頓迭代法。
這兩種數(shù)學(xué)算法,具體思路參考官方題解:
https://leetcode-cn.com/problems/sqrtx/solution/x-de-ping-fang-gen-by-leetcode-solution/
二分查找的具體做法,和上題差不多,初始右邊界,選擇right = x/2 + 1。注意在退出循環(huán)后,如果使用while(left <= right),得考慮返回值如何選擇。
如case 8 的平方根是 2.82842...,應(yīng)該返回2。left在非return正常退出循環(huán)時(shí),可能會(huì)mid + 1,不合適,right退出循環(huán)時(shí),是mid-1,符合題目取整數(shù)的要求。
????//?用long?防止int*int后溢出
????long?left?=?0,?right?=?x?/?2?+?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?right)?>>>?1;
????????long?square?=?mid?*?mid;
????????if?(square?==?x)?{
????????????return?(int)mid;
????????}else?if?(square?>?x)?{
????????????right?=?mid?-?1;
????????}else??{
????????????left?=?mid?+?1;
????????}
????}
????//?按要求,比如8,平方根2.82842,所求解為2.
????//?對于left?<=?right,在正常退出時(shí),left?=?right?+?1
????// right每次更新都是mid - 1,所以直接返回right即可,或者left - 1。
????return?(int)right;
}
50 Pow(x,n)
題目說明:
實(shí)現(xiàn) pow(x, n) ,即計(jì)算 x 的 n 次冪函數(shù)。
示例1:
輸入: 2.00000, 10
輸出: 1024.00000
示例2:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
說明:-100.0 < x < 100.0
n 是 32 位有符號整數(shù),其數(shù)值范圍是 [?231, 231 ? 1]
解題思路:
最壞的解法,就是遍歷一遍,不斷相乘。二分法的思想在這題上,比如要算2的10次方,只要求得2的5次方即可;2的5次方只要求2的2次方,然后結(jié)果自乘即可. ?自然想到也要使用遞歸。
可以看到,需要注意的是,當(dāng)n為奇數(shù)時(shí),中間結(jié)果還需要再乘以當(dāng)前數(shù)。除此之外,還應(yīng)該考慮x為負(fù)數(shù)的情況,x為負(fù)數(shù),所求結(jié)果要被1除,即倒數(shù)。
public?double?myPow(double?x,?int?n)?{????long?N?=?n;
????return?N?>=?0???quickMul(x,?N)?:?1.0?/?quickMul(x,?-N);
}
public?double?quickMul(double?x,?long?n)?{
????if?(n?==?0)?{
????????return?x;
????}
????double?y?=?quickMul(x,?n?/?2);
????return?n?%?2?==?0???y?*?y?:?y?*?y?*?x;
}
153.尋找旋轉(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] )。
請找出其中最小的元素。你可以假設(shè)數(shù)組中不存在重復(fù)元素。
示例:
輸入: [4,5,6,7,0,1,2]
輸出: 0
解題思路
主要思路就是通過判斷中點(diǎn)值與邊值的大小來縮小查找的左右區(qū)間。
本題有3個(gè)關(guān)鍵點(diǎn):
1好理解,使用left
2.3的考慮,二分查找題目分析一定要考慮數(shù)組只有兩個(gè)元素的case,就能確定到底使用左中點(diǎn)合適還是右中點(diǎn)合適。
此題,如果case為[1,3],如果選左中點(diǎn),用左邊界比較,就會(huì)很麻煩:如果nums[mid] = num[left],left肯定不能mid+1跳過,但是如果left=mid,就死循環(huán)了。
如果選右中點(diǎn),用左邊界比較,也會(huì)有問題,正常情況nums[mid] > num[left],肯定右半邊是較小的區(qū)間,left跳過mid,但是case[1,3]來說,跳過1就有問題。
所以,求旋轉(zhuǎn)數(shù)組最小值,因?yàn)樾D(zhuǎn)點(diǎn)的右區(qū)間是小區(qū)間,所以用左中值和右邊值比較,當(dāng)左中值和右值相等時(shí),右邊值由可能是最小值,注意右邊值不能跳過mid。
public?int?findMin(int[]?nums)?{????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?????????mid?=?(left?+?right)?>>>?1;
????????if?(nums[mid]?>?nums[right])?{
????????????left?=?mid?+?1;
????????}else?{
????????????right?=?mid;
????????}
????}
????return?nums[mid];
}
154.尋找旋轉(zhuǎn)排序數(shù)組中的最小值II
題目描述:
假設(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]?)。
請找出其中最小的元素。
注意數(shù)組中可能存在重復(fù)的元素。
示例 1:
輸入: [3,3,1,3]
輸出: 1
示例 2:
輸入: [2,2,2,0,1]
輸出: 0
解題思路:
基于上題,如果沒有重復(fù)元素,如果左中點(diǎn)值和右值相等時(shí)nums[mid] = nums[right],右值有可能是小值,不能跳過,就更新右邊值right = mid。
如果有重復(fù)元素后,處理也簡單,當(dāng)左中值和右值相等,讓右指針往左滑動(dòng),right = right - 1, 跳過迷惑值。
public?int?findMin(int[]?nums)?{????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?????????mid?=?(left?+?right)?>>>?1;
????????//?相等時(shí),跳過右邊這個(gè)迷惑值
????????if?(nums[mid]?==?nums[right])?{
????????????right?=?right?-?1;
????????}
????????if?(nums[mid]?>?nums[right])?{
????????????left?=?mid?+?1;
????????}
????????if?(nums[mid]?????????????right?=?mid;
????????}
????}
????return?nums[left];
}
33 搜索旋轉(zhuǎn)排序數(shù)組
題目描述:
給你一個(gè)升序排列的整數(shù)數(shù)組 nums ,和一個(gè)整數(shù) target 。
假設(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] )。
請你在數(shù)組中搜索 target ,如果數(shù)組中存在這個(gè)目標(biāo)值,則返回它的索引,否則返回?-1 。
示例 1:
輸入:nums=[4,5,6,7,0,1,2], target = 0
輸出:4
解題思路:
和上面找最小值一樣,通過中值和邊值的比較,先確定有序的數(shù)組,然后通過判斷target是否在區(qū)間內(nèi),來更新左右邊值。
使用左中點(diǎn)的話,
- 如果nums[mid] >= nums[left], 可以證明[left,mid]左半段是有序的。當(dāng)target在[left,mid)區(qū)間內(nèi),更新右邊值right = mid - 1,否則更新左邊值left = mid + 1。
?使用>=是因?yàn)槭怯玫氖亲笾悬c(diǎn),得考慮只有兩個(gè)元素的case。 - nums[mid] < nums[left], 右半段[mid,right]是有序的
當(dāng)target在(mid,right],更新左邊值left = mid + 1, 否則更新右邊值right = mid - 1
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?right)?>>>?1;
????????if?(nums[mid]?==?target)?{
????????????return?mid;
????????}
????????//?如果中點(diǎn)值大于左值,說明左半邊是有序的
????????if?(nums[mid]?>=?nums[left])?{
????????????//?如果目標(biāo)值在左區(qū)間,更新右邊界
????????????if?(target?>=?nums[left]?&&?target?????????????????right?=?mid?-?1;
????????????}else?{
????????????????left?=?mid?+?1;
????????????}
????????}else?{
????????????//?右半邊是有序的,如果目標(biāo)值在右半邊區(qū)間,更新左邊界????????????????????????????????????????????
????????????if?(target?>?nums[mid]?&&?target?<=?nums[right])?{
????????????????left?=?mid?+?1;
????????????}else?{
????????????????right?=?mid?-?1;
????????????}
????????}
????}
????return?-1;
}
81.搜索旋轉(zhuǎn)排序數(shù)組II
題目描述:
假設(shè)按照升序排序的數(shù)組在預(yù)先未知的某個(gè)點(diǎn)上進(jìn)行了旋轉(zhuǎn)。
編寫一個(gè)函數(shù)來判斷給定的目標(biāo)值是否存在于數(shù)組中。若存在返回 true,否則返回 false。
進(jìn)階:
這是 搜索旋轉(zhuǎn)排序數(shù)組 的延伸題目,本題中的 nums 可能包含重復(fù)元素。這會(huì)影響到程序的時(shí)間復(fù)雜度嗎?會(huì)有怎樣的影響,為什么?
示例:
輸入:nums=[2,5,6,0,0,1,2], target = 0 輸出: true
解題思路:
和154.尋找旋轉(zhuǎn)排序數(shù)組中的最小值II題一樣,當(dāng)nums[mid] == nums[left]時(shí),需要滑動(dòng)left邊值指針,跳過迷惑項(xiàng)。
如果這么處理,當(dāng)待排序數(shù)組全是一樣的重復(fù)元素,時(shí)間復(fù)雜度會(huì)退化為O(n)。
????int?left?=?0,?right?=?nums.length?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?right)?>>>?1;
????????if?(nums[mid]?==?target)?{
????????????return?true;
????????}
????????//?跳過迷惑值
????????if?(nums[mid]?==?nums[left])?{
????????????left?++;
????????}else?if?(nums[mid]?>?nums[left])?{
????????????if?(target?>=?nums[left]?&&?target?????????????????right?=?mid?-?1;
????????????}else?{
????????????????left?=?mid?+?1;
????????????}
????????}else?{
????????????if?(target?>?nums[mid]?&&?target?<=?nums[right])?{
????????????????left?=?mid?+?1;
????????????}else?{
????????????????right?=?mid?-?1;
????????????}
????????}
????}
????return?false;
}
74.搜索二維矩陣
題目描述:
編寫一個(gè)高效的算法來判斷 m x n 矩陣中,是否存在一個(gè)目標(biāo)值。該矩陣具有如下特性:
每行中的整數(shù)從左到右按升序排列。
每行的第一個(gè)整數(shù)大于前一行的最后一個(gè)整數(shù)。
示例:
輸入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3
輸出:true
解題思路:
從示例上就能看到,講該二維矩陣按行依次展開,就是一個(gè)升序的一維數(shù)組。
然后就能按標(biāo)準(zhǔn)的二分查找來做題了。
此題關(guān)鍵技巧,如果展開后的一維數(shù)組的下班為i。
和m(row length) * n(col length)二維數(shù)組存在以下關(guān)系:
- row = i / n
- col = i % n
????if?(matrix?==?null?||?matrix.length?<=?0)?{
????????return?false;
????}
????int?m?=?matrix.length;
????int?n?=?matrix[0].length;
????if?(n?<=?0)?{
????????return?false;
????}
????int?left?=?0,?right?=?m?*?n?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?right)?>>>?1;
????????int?val?=?matrix[mid?/?n][mid?%?n];
????????if?(val?==?target)?{
????????????return?true;
????????}else?if?(val?>?target)?{
????????????right?=?mid?-?1;
????????}else?{
????????????left?=?mid?+?1;
????????}
????}
????return?false;
}
240.搜素二維矩陣II
題目描述:
編寫一個(gè)高效的算法來搜索 m x n 矩陣 matrix 中的一個(gè)目標(biāo)值 target。該矩陣具有以下特性:
每行的元素從左到右升序排列。
每列的元素從上到下升序排列。?
示例:
現(xiàn)有矩陣 matrix 如下:
[1, ? 4, ?7, 11, 15],
[2, ? 5, ?8, 12, 19],
[3, ? 6, ?9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
給定 target = 5,返回 true。
給定 target = 20,返回 false。
解題思路:
和上題不一樣的是,下一行的起始值不一定會(huì)比上一行末尾值大。但是每一行和每一列是各自升序的。所以,對于m*n的矩陣,遍歷i次,i取m和n的最小值,分別在第i列和第i行進(jìn)行二分查找。
時(shí)間復(fù)雜度分析:
循環(huán)次數(shù)i = min(m,n), 每次循環(huán)分別二分查找列和行,時(shí)間復(fù)雜度O(lg(m-i)) + O(lg(n-i))。近似O(2*lg(n-i)) = O(lg(n))。最壞當(dāng)m==n時(shí),總的運(yùn)行時(shí)間: O(lg(n) + lg(n-1) + lg(n-2) + ... + lg(1)) = O(lg(n*(n-1)*(n-2)*...*1)) = O(lg(n!))
????if?(matrix?==?null?||?matrix.length?<=?0)?{
????????return?false;
????}
????int?count?=?Math.min(matrix.length,?matrix[0].length);
????for?(int?i?=?0;?i?????????boolean?rowSearchRes?=?bSearch(matrix,?target,?i,?true);
????????if?(rowSearchRes)?{
????????????return?true;
????????}
????????boolean?colSearchRes?=?bSearch(matrix,?target,?i,?false);
????????if?(colSearchRes)?{
????????????return?true;
????????}
????}
????return?false;
}
private?boolean?bSearch(int[][]?martix,?int?target,?int?i,?boolean?searchRow)?{
????int?left?=?i,?right?=?searchRow???martix[0].length?-?1?:?martix.length?-?1,?mid?=?0;
????while?(left?<=?right)?{
????????mid?=?(left?+?right)?>>>?1;
????????if?(searchRow)?{
????????????if?(target?==?martix[i][mid])?{
????????????????return?true;
????????????}
????????????if?(target?>?martix[i][mid])?{
????????????????left?=?mid?+?1;
????????????}
????????????if?(target?????????????????right?=?mid?-?1;
????????????}
????????}else?{
????????????if?(target?==?martix[mid][i])?{
????????????????return?true;
????????????}
????????????if?(target?>?martix[mid][i])?{
????????????????left?=?mid?+?1;
????????????}
????????????if?(target?????????????????right?=?mid?-?1;
????????????}
????????}
????}
????return?false;
}
按照本題二維矩陣的特性,如果把左下角的值當(dāng)做根節(jié)點(diǎn),可以將矩陣看做一個(gè)二叉搜索樹,上一個(gè)列值比它小,下一個(gè)行值比它大。
所以,從最下角值開始搜索,如果目標(biāo)值大于當(dāng)前值,后面的所有的行值也都是大于目標(biāo)值的,列往上移動(dòng)。
如果目標(biāo)值小于當(dāng)前值,往上的所有列值都是小于目標(biāo)值的,移動(dòng)下一行。
時(shí)間復(fù)雜度O(m+n),空間復(fù)雜度O(1)。
public?boolean?searchMatrix(int[][]?martrix,?int?target)?{????if?(martrix?==?null?||?martrix.length?<=?0)?{
????????return?false;
????}
????int?row?=?martrix.length?-?1,?col?=?0;
????while?(col?=?0)?{
????????if?(martrix[row][col]?==?target)?{
????????????return?true;
????????}?else?if?(martrix[row][col]?>?target)?{
????????????row?=?row?-?1;
????????}else?{
????????????col?=?col?+?1;
????????}
????}
????return?false;
}
總結(jié)
二分查找題目注意在使用不同while條件時(shí),不同的細(xì)節(jié)處理,包括左右指針的更新,退出循環(huán)后的取值考慮。
記得不要忘記分析只有兩個(gè)元素的case。
旋轉(zhuǎn)數(shù)組類的查找,首先要通過中值與邊值的比較,來確定哪半邊是有序的。升序數(shù)組旋轉(zhuǎn)后右半邊是小區(qū)間,求旋轉(zhuǎn)數(shù)組最小值時(shí),使用左中值和右邊值比較。
查找數(shù)組中,如果有重復(fù)元素,需要滑動(dòng)指針,跳過疑惑值,但是有可能時(shí)間復(fù)雜度退化為O(n)。
總結(jié)
以上是生活随笔為你收集整理的problem k: 查找某一个数_quot;细节魔鬼quot; 二分查找的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python numpy库安装 mac_
- 下一篇: c 数据压缩算法_CCSDS图像压缩算法