圖片拍攝於2021年1月2日,舟山東極島。
我說過了,會按照標籤來刷題。每一個類別刷四到五題,分兩篇文章,第一篇基礎題,第二篇稍微變型的題。開篇就從二分開始吧。
Leetcode 704
這是一道最典型的二分基礎題,從一個有序集合中查詢目標值,還不需要考慮元素重複問題,那麼就簡單了。
func search(nums []int, target int) int {
left, right := 0, len(nums)-1
var mid int
for left <= right {
// mid = (left + right) / 2
// 獲取中位數
mid = left + ((right - left) >> 1)
// 如果中位數的值等於目標值,找到了,直接返回
if nums[mid] == target {
return mid
// 如果當前中位數的值 > 目標值,說明值只可能存在中位數的左區間,
// 並且不包括中位數
} else if nums[mid] > target {
right = mid - 1
// 否則當前中位數的值 < 目標值,說明值只可能存在中位數的右區間,
// 並且不包括中位數
} else {
left = mid + 1
}
}
return -1
}
值得注意的是求中位數的時候如果只是單純的 mid = (left + right) / 2 ,那麼當數字過大時相加會造成溢位,因此這裡直接使用位運算。
上面的程式碼還不是很嚴謹,比如說,開頭過個濾。
if len(nums) == 0 {
return -1
}
另外值得討論的一點是 left <= right ,不少人會在這上面糾結到底是 < 還是 <=。這是因為理解的角度不同,就會有大同小異的解法,就會產生差異化的程式碼。只要能理解邊界的問題,那麼咋麼寫都不重要。但是我依然覺得,好的程式碼不單單是多麼精簡和高階的技巧,而是簡單易懂。
left 和 right 都是陣列的下標,他們代表的是當前查詢的範圍區間。在程式中通過判斷的結果,來更新接下來要查詢的區間是往左區間縮小還是右區間縮小。
那什麼時候結束呢?
第一,程式已經找到結果了,那當然直接執行結束。
第二,沒找到結果,不斷的縮小區間,最後這個區間已經縮小成 0,沒區間值了,也結束了。
現在你明白上面為什麼要 <= 了吧。首先 left 肯定是不能大於 right 的,比如 [5,4] 這能是一個正常區間嘛。至於 = ,道理也很簡單,[5,5] 這個區間還有一個公共的 5,如果漏掉,會導致程式出錯。
Leetcode 34
這道題比剛才難度提高了一點,同樣是查詢目標值位置,我們需要返回兩個值,一個值是目標值出現的第一個位置以及目標值出現的最後一個位置。
那我們上面還能用嘛?
改改就能用!
本質上對左右區間縮小的判斷還是一樣的道理。唯一不同的是,之前我們在查詢到目標值之後就返回結果,現在不行了。我們得進一步確認目標值是不是對應第一個和最後一個的位置。
func searchRange(nums []int, target int) (res []int) {
return append([]int{}, findFirstIndex(nums, target), findLastIndex(nums, target))
}
// 查詢元素第一個
func findFirstIndex(nums []int, target int) int {
var mid int
left, right := 0, len(nums)-1
for left <= right {
//如果數字大會造成溢位
// mid = (left + right)/2
//使用位運算
mid = left + ((right - left) >> 1)
// 如果當前中位數的值比目標值大,說明目標值只可能存在中位數的左區間(不包括中位數)
if nums[mid] > target {
right = mid - 1
// 如果當前中位數的值比目標值小,說明目標值可能只可能存在中位數的右區間
} else if nums[mid] < target {
left = mid + 1
} else { //說明 此時中位數的值等於目標值,但是不能確定它就是相同目標值的第一個
//在相等的情況下,如果當前中位數索引處是0,或者當前中位數上一個索引位置的值不等於目標值,那肯定就它了,程式結束
if mid == 0 || (nums[mid-1] != target) {
return mid
} else { //否則的話 肯定在左邊,就往左區間再擠擠
right = mid - 1
}
}
}
// 如果都沒找到,那就返回-1
return -1
}
// 查詢元素最後一個
func findLastIndex(nums []int, target int) int {
var mid int
left, right := 0, len(nums)-1
for left <= right {
mid = left + ((right - left) >> 1)
if nums[mid] > target {
right = mid - 1
} else if nums[mid] < target {
left = mid + 1
} else { //說明 此時中位數的值等於目標值,但是不能確定它就是相同目標值的最後一個
//在相等的情況下,如果當前中位數索引處於最後一個位置,或者當前中位數下一個索引位置的值不等於目標值,那肯定就它了,程式結束
if mid == len(nums)-1 || nums[mid+1] != target {
return mid
} else { //否則的話,肯定再右邊 就往右區間再擠擠
left = mid + 1
}
}
}
// 如果都沒找到,那就返回-1
return -1
}
這期的題目就到這了,你有不一樣的解法嘛?那麼歡迎留言告訴我。程式碼放在:https://github.com/wuqinqiang/LeetCode-Go-Week。
本作品採用《CC 協議》,轉載必須註明作者和本文連結