Go 每週一刷1.0

Remember發表於2021-01-09

圖片

圖片拍攝於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 協議》,轉載必須註明作者和本文連結
吳親庫裡