劍指offer-Go版實現 第六、七章:面試中的各項能力和兩個面試案例

棋佈發表於2022-01-16

此章主要練習的是溝通能力、學習能力、知識遷移能力、抽象建模能力等。這需要有紮實的數學基礎,如果沒有,相信,很多人會像我一樣,一道題看很久才能看懂,要理解那就要藉助視訊,還要動手畫圖才可以理解的了。這是先天不足後天畸形的我們面向業務和搜尋程式設計帶來的硬傷。這種題目不會太難,但是很具有動腦的需要,如有必要,建議看原文。

面試題38:數字在排序陣列中出現的次數

leetcode-cn.com/problems/zai-pai-x...
統計一個數字在排序陣列中出現的次數

func search(nums []int, target int) int {
    //start := 0
    //end := len(nums) - 1
    //for i := 0; i < len(nums); i++ {
    //    if nums[i] == target {
    //        start = i
    //        break
    //    }
    //}
    //if start == -1 || start == len(nums) || nums[start] != target {
    //    return 0
    //}
    //for i := 0; i < len(nums); i++ {
    //    if nums[i] > target {
    //        end = i - 1
    //    }
    //}
    //fmt.Println(start,end)
    //return end - start + 1

    //count := 0
    //for i := 0; i < len(nums); i++ {
    //    if nums[i] == target {
    //        count++
    //    }
    //}
    //return count

    //效率最高
    leftmost := sort.SearchInts(nums, target)
    if leftmost == len(nums) || nums[leftmost] != target {
        return 0
    }
    rightmost := sort.SearchInts(nums, target+1) - 1
    return rightmost - leftmost + 1
}

這是課本上原文給的參考程式碼,C++的改寫

func search(nums []int, target int) int {
    count := 0
    if len(nums) == 0 {
        return count
    }
    first := getFirstTargetIndex(nums, target, 0, len(nums)-1)
    last := getLastTargetIndex(nums, target, 0, len(nums)-1)
    if first > -1 && last > -1 {
        count = last - first + 1
    }
    return count
}

func getFirstTargetIndex(nums []int, target, start, end int) int {
    if start > end {
        return -1
    }
    mid := (start + end) / 2
    fmt.Println(mid, start, end)
    midData := nums[mid]
    if midData > target {
        end = mid - 1
    } else if nums[mid] < target {
        start = mid + 1
    } else {
        if (mid > 0 && nums[mid-1] != target) || mid == 0 {
            return mid
        } else {
            end = mid - 1
        }
    }
    return getFirstTargetIndex(nums, target, start, end)
}
func getLastTargetIndex(nums []int, target, start, end int) int {
    if start > end {
        return -1
    }
    mid := (start + end) / 2
    midData := nums[mid]
    if midData > target {
        end = mid - 1
    } else if nums[mid] < target {
        start = mid + 1
    } else {
        if (mid < len(nums)-1 && nums[mid+1] != target) || mid == len(nums)-1 {
            return mid
        } else {
            start = mid + 1
        }
    }
    return getLastTargetIndex(nums, target, start, end)
}

都是精華吧,值得都看。

面試題39:二叉樹的深度

leetcode-cn.com/problems/er-cha-sh...

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    fmt.Println(root.Val)
    left := maxDepth(root.Left)
    right := maxDepth(root.Right)
    if left > right {
        return left + 1
    } else {
        return right + 1
    }
}
//判斷是不是一棵平衡二叉樹
func isBalanced(root *TreeNode) bool {
   if root == nil {
      return true
  }
   fmt.Println(root.Val)
   left := maxDepth(root.Left)
   right := maxDepth(root.Right)
   diff := left - right
   if diff > 1 || diff < -1 {
      return false
  }
   return isBalanced(root.Left) && isBalanced(root.Right)
}

平衡二叉樹是樹上給的發散,也可以根據平衡二叉樹來進行深度計算,並且會避免重複遍歷結點。

面試題40:陣列中出現一次的數字

leetcode-cn.com/problems/shu-zu-zh...
一個整型陣列 nums 裡除兩個數字之外,其他數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。要求時間複雜度是O(n),空間複雜度是O(1)。

// 低效的實現方式
func singleNumbers(nums []int) []int {
    if len(nums) == 0 {
        return nil
    }
    sort.Ints(nums)
    fmt.Println(nums)
    result := make([]int, 0)
    if len(nums) == 1 {
        result = append(result, nums[0])
    }
    if nums[0] != nums[1] {
        result = append(result, nums[0])
    }
    if len(nums) > 1 && nums[len(nums)-2] != nums[len(nums)-1] {
        result = append(result, nums[len(nums)-1])
    }
    for i := 1; i < len(nums)-1; i++ {
        if nums[i] != nums[i-1] && nums[i] != nums[i+1] {
            result = append(result, nums[i])
        }
    }
    return result
}

// 題目限定了必須有兩個一次的數字,其他的兩次
// 分組異或
func singleNumbers(nums []int) []int {
   if len(nums) == 0 {
      return nil
  }
   result := make([]int, 2)
   ret := 0
  for _, v := range nums {
      ret ^= v
   }
   div := 1
  for (div & ret) == 0 {
      div <<= 1
  }
   for _, v := range nums {
      if (div & v) != 0 {
         result[0] ^= v
      } else {
         result[1] ^= v
      }
   }

   return result
}

面試題41-1:和為S的兩個數字

leetcode-cn.com/problems/he-wei-sd...
輸入一個遞增排序的陣列和一個數字s,在陣列中查詢兩個數,使得它們的和正好是s。如果有多對數字的和等於s,則輸出任意一對即可。

func twoSum(nums []int, target int) []int {
    if len(nums) == 0 {
        return nil
    }
    result := make([]int, 0)
    left := 0
    right := len(nums) - 1
    for left <= right {
        if nums[left]+nums[right] == target {
            result = append(result, []int{nums[left], nums[right]}...)
            break
        } else if nums[left]+nums[right] > target {
            right--
        } else {
            left++
        }
    }
    return result
}

面試題41-2:和為s的連續正數序列

leetcode-cn.com/problems/he-wei-sd...
輸入一個正整數 target ,輸出所有和為 target 的連續正整數序列(至少含有兩個數)。
序列內的數字由小到大排列,不同序列按照首個數字從小到大排列.

//雙指標
func findContinuousSequence(target int) [][]int {
    result := make([][]int, 0)
    if target < 3 {
        return nil
    }
    small, big, middle := 1, 2, (1+target)/2
    curSum := small + big
    for small < middle {
        if curSum == target {
            result = append(result, addSeq(small, big))
        }
        for curSum > target && small < middle {
            curSum -= small
            small++
            if curSum == target {
                result = append(result, addSeq(small, big))
            }
        }
        big++
        curSum += big
    }

    return result
}

func addSeq(small, big int) (result []int) {
    for i := small; i <= big; i++ {
        result = append(result, i)
    }
    return
}

面試題42-1:翻轉單詞順序

leetcode-cn.com/problems/fan-zhuan...
輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字串”I am a student. “,則輸出”student. a am I”。

func reverseWords(s string) string {
    split := strings.Split(s, " ")
    var res []string
    for i := len(split) - 1; i >= 0; i-- {
        if split[i] != ""{
            res = append(res, split[i])
        }
    }
    fmt.Println(res)
    return strings.Join(res, " ")
}

面試題42-2:左旋轉字串

leetcode-cn.com/problems/zuo-xuan-...
字串的左旋轉操作是把字串前面的若干個字元轉移到字串的尾部。
請定義一個函式實現字串左旋轉操作的功能。比如,輸入字串”abcdefg”和數字2,該函式將返回左旋轉兩位得到的結果”cdefgab”。

func reverseLeftWords(s string, n int) string {
    //邊界條件可以不用判斷
    //res := make([]uint8, len(s), len(s))
    //for i := 0; i < len(s); i++ {
    //    res[i] = s[i]
    //}
    s1 := s[:n]
    s2 := s[n:]
    return s2 + s1
}

面試題43:n個骰子的點數

leetcode-cn.com/problems/nge-tou-z...
把n個骰子扔在地上,所有骰子朝上一面的點數之和為s。輸入n,列印出s的所有可能的值出現的概率。你需要用一個浮點數陣列返回答案,其中第 i 個元素代表這 n 個骰子所能擲出的點數集合中第 i 小的那個的概率。

//參考的Java程式碼實現的方式寫的,這筆課本上的要簡便一點,實際上原理是一樣的
func dicesProbability(n int) []float64 {
    if n < 1 {
        return nil
    }
    dp := make([][]float64, n)
    for i := range dp {
        dp[i] = make([]float64, (i+1)*6-i)
    }
    for i := range dp[0] {
        dp[0][i] = float64(1) / float64(6)
    }
    for i := 1; i < len(dp); i++ {
        for j := range dp[i-1] {
            for k := range dp[0] {
                dp[i][j+k] += dp[i-1][j] * dp[0][k]
            }
        }
    }
    return dp[n-1]
}

面試題44:撲克牌中的順子

leetcode-cn.com/problems/bu-ke-pai...
從若干副撲克牌中隨機抽 5 張牌,判斷是不是一個順子,即這5張牌是不是連續的。2~10為數字本身,A為1,J為11,Q為12,K為13,而大、小王為 0 ,可以看成任意數字。A 不能視為 14。

func isStraight(nums []int) bool {
    if len(nums) != 5 {
        return false
    }
    //1、先排個序
    sort.Ints(nums)
    //2、記錄0的個數
    zeroNum := 0
    for i := 0; i < len(nums); i++ {
        if nums[i] == 0 {
            zeroNum++
        }
    }
    //fmt.Println(zeroNum)
    //3、判斷是否可以組成順子
    for i := zeroNum; i < len(nums)-1; i++ {
        //fmt.Println(nums[i], nums[i+1])
        if nums[i+1] == nums[i] { //有相等的,直接返回false
            return false
        } else if nums[i+1]-nums[i] > 1 {
            //fmt.Println(nums[i], nums[i+1])
            zeroNum -= nums[i+1] - nums[i] - 1
            if zeroNum < 0 {
                return false
            }
        }
    }
    return true
}

面試題45:圓圈中最後剩下的數字

leetcode-cn.com/problems/yuan-quan...
0,1,···,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裡刪除第m個數字(刪除後從下一個數字開始計數)。求出這個圓圈裡剩下的最後一個數字。例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次是2、0、4、1,因此最後剩下的數字是3。
規律,看書本的,我沒這個水平推出來

func lastRemaining(n int, m int) int {
    if n < 1 || m < 1 {
        return -1
    }
    last := 0
    for i := 2; i <= n; i++ {
        last = (last + m) % i
    }

    return last
}

面試題46:求1+2+…+n

leetcode-cn.com/problems/qiu-12n-l...
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

//似乎只能想到遞迴了
func sumNums(n int) int {
   if n == 0 {
      return 0
  }
   return n + sumNums(n-1)
}

面試題47:不用加減乘除做加法

leetcode-cn.com/problems/bu-yong-j...
寫一個函式,求兩個整數之和,要求在函式體內不得使用 “+”、“-”、“*”、“/” 四則運算子號。

//除了位運算,別無他法了吧,但是用了其他符號,leetcode也檢測不出來
func add(a int, b int) int {
    sum, carry := 0, 0
    for b != 0 {
        sum = a ^ b
        carry = (a & b) << 1
        a = sum
        b = carry
    }
    return a
}

面試題48:不能被繼承的類

不適合Go語言,沒有這一說法吧,難道還能實現不能被繼承的結構體不成?

小結

從這個時間就可以看出來,最難的就是抽象建模。至於發散思維,很好理解,起碼會一種了,看其他的就很好遞推了。這一張題目並不是十分難,就幾道題,可能沒什麼思路,需要看答案解析才能寫出程式碼,更多的是靠自覺來按照要求來編寫,leetcode的工具檢測並沒有這麼完善。建議,看原文,這裡貼出來的知識為了記錄一下我是這麼分階段來練習的。分解任務嘛,光是堅持到這裡的,我相信已經是很少部分人了。第七章,我建議,強烈建議看原文,因為重點並不在於題目而是在於原文的描述和出題的意圖。

看題目就知道用意是什麼,強烈建議看原文,題目並不是重點,題目相信並不難,都是簡單的題目,但是,溝通能力和題目的邊界情況的考慮以及和麵試官的溝通反而顯得更加重要,我們要做的就是超過面試官原先對我們的期待那就一定能拿到offer。再次強烈建議看原文,內容並不多。

面試題49:把字串轉換成整數

leetcode-cn.com/problems/ba-zi-fu-...
寫一個函式 StrToInt,實現把字串轉換成整數這個功能。不能使用 atoi 或者其他類似的庫函式。首先,該函式會根據需要丟棄無用的開頭空格字元,直到尋找到第一個非空格的字元為止。當我們尋找到的第一個非空字元為正或者負號時,則將該符號與之後面儘可能多的連續數字組合起來,作為該整數的正負號;假如第一個非空字元是數字,則直接將其與之後連續的數字字元組合起來,形成整數。該字串除了有效的整數部分之後也可能會存在多餘的字元,這些字元可以被忽略,它們對於函式不應該造成影響。注意:假如該字串中的第一個非空格字元不是一個有效整數字符、字串為空或字串僅包含空白字元時,則你的函式不需要進行轉換。在任何情況下,若函式不能進行有效的轉換時,請返回 0。

func strToInt(str string) int {
    index := 0
    for i := 0; i < len(str) && str[i] == ' '; i++ {
        index = i
    }
    str = str[index:] //第一個非空格的字元開始
    ans := 0
    flag := false

    for i, v := range str {
        if v >= '0' && v <= '9' {
            ans = ans*10 + int(v-'0')
        } else if v == '-' && i == 0 {
            flag = true
        } else if v == '+' && i == 0 {
        } else {
            break
        }

        if ans > math.MaxInt32 {
            if flag {
                return math.MinInt32
            }
            return math.MaxInt32
        }
    }
    if flag {
        return -1 * ans
    }
    return ans
}

面試題50:二叉搜尋樹的最近公共祖先
leetcode-cn.com/problems/er-cha-so...
中最近公共祖先的定義為:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
非常遺憾,不支援GO的語言編寫,但是官方提供了Go的例子、

func lowestCommonAncestor(root, p, q *TreeNode) (ancestor *TreeNode) {
    pathP := getPath(root, p)
    pathQ := getPath(root, q)
    for i := 0; i < len(pathP) && i < len(pathQ) && pathP[i] == pathQ[i]; i++ {
        ancestor = pathP[i]
    }
    return
}
func getPath(root, target *TreeNode) (path []*TreeNode) {
    node := root
    for node != target {
        path = append(path, node)
        if target.Val < node.Val {
            node = node.Left
        } else {
            node = node.Right
        }
    }
    path = append(path, node)
    return path
}

我這50道題給出的都是題目和一個參考答案以及多個參考答案,但都不是重點,重點是通過這些題去思考自己的實現方式。聯絡的重要性不言而喻,有的可能不用敲出來,但是起碼要會說出來。作為追求,萬年不會過時的演算法,我們都應該長久的去做,別讓腦袋秀逗了。
好多小夥伴私聊我說怎麼這麼厲害的,我其實就是個菜雞,看起來很厲害,我演算法真的是很low,不然不會從這本書開始,因為演算法是在太難了,找個有針對性的去練習才能找到成就感和方向感,不然太盲目了。最後貼上高頻考點,這是我花了一塊錢報的培訓課程裡面給出來的,感覺有點幫助。

劍指offer-Go版實現 第七章:兩個面試案例
有人好奇我在塗鴉做什麼崗位,順便回答一下。我在塗鴉不是做演算法的,做的是裝置生態,邊緣閘道器相關的。這些詞對於沒接觸過iot的人來說很陌生,沒關係,我也很陌生。主要的用到和CURD不同的技術是CGO和mqtt,算是和業務脫鉤了,不過技術本身都是一樣的,沒什麼差別。技術棧都是一樣的,作為雲端後臺開發的崗位,基本和你們差不多的。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
歡迎轉載分享提意見,一起code