劍指offer-Go版實現 第二章:面試需要的基礎知識

棋佈發表於2021-12-25

leetcode題目實在太多了,找了半天還是回到這本10年前的書,題目數量不多,但是都比較經典,覆蓋知識點比較廣。開始參加leetcode周賽,做兩題都是很難的,尤其是面對一堆大牛動不動四道題全做出來,很受刺激,真的是自慚形穢。狠下心來,只能自己慢慢研究一波,現在穩定兩道題,偶爾還能突破三道題,這時候再重新回顧一遍劍指offer第二版,發現,以前死記硬背應對面試的東西,現在可以自己實現出來了。很多同學也說自己演算法很差,有的甚至連陣列和連結串列都分不清楚的,所以就試著分享,用GO語言重新實現一遍,也把涉及到的相關知識點一併講解。

第一章主要介紹面試的環節和流程,感覺跟國內的實際情況有點不相符,畢竟很多並沒有這麼專業,所以就不打算深究了,主要以面試題的Go版本實現為主,一起相互刷題吧。途中也有很多自己理解不透徹的地方,畢竟,演算法方面,我也是個半桶水。

面試題1:賦值運算子函式

實在不知道這題的考點是什麼,對於任何一種語言來說,字串的拼接方式很多種,也不會有哪家公司的面試官腦子不開竅問這種題吧。

面試題2:實現單例模式

書上用的是C++,採用的最好方式是靜態內部類實現的。GO語言本身來說並沒有類一說,也沒有內部類一說,init函式實現一個全域性的,就相當於實現一個靜態的例項,更多的是用sync.Do函式來做單例,實際上,init函式更受用一點,簡單方便,只是可能會增加依賴。

面試題3:二維陣列中的查詢

先附上leetcode地址:leetcode-cn.com/problems/er-wei-sh...
書中都是其他語言的,想要自己實現不可能直接抄程式碼改寫吧,先自己實現一遍還是有必要的,起碼自己動了腦,還可以藉助leetcode提供的測試用例來驗證,實現方式也不一定就是書上提供的。程式碼解釋看註釋。

// 在一個 n * m 的二維陣列中,每一行都按照從左到右遞增的順序排序,
//每一列都按照從上到下遞增的順序排序。請完成一個高效的函式,
//輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。
func findNumberIn2DArray(matrix [][]int, target int) bool {
    if len(matrix) == 0 {
        return false
    }
    var col = len(matrix[0])//從每一行的最後一個開始查詢,組成矩陣
    var row = 0
    for ; row < len(matrix) && col > 0; {
        if matrix[row][col-1] == target {//相等直接返回
            return true
        } else if matrix[row][col-1] > target {//大於,向左移動一列,縮小矩陣範圍
            col--
        } else {//小於就向下移動一行,縮小矩陣範圍
            row++
        }
    }
    return false
}

面試題4:替換空格

leetcode-cn.com/problems/ti-huan-k...
這道題考查的是陣列從後往前坐替換,減少移動次數。但是,演算法比賽講究的是時間,如果面試的時候面試官有要求,就按照書上說的來實現,但是直接使用庫函式,leetcode上是接近雙百的,要研究的可以自己去把字串遍歷字元陣列,第一字元進行替換操作。

func replaceSpace(s string) string {
    return strings.Replace(s, " ", "%20", -1)
}

面試題5:從尾到頭列印連結串列

leetcode-cn.com/problems/cong-wei-...
這個實現方式就更多了,書上說的很全面,面試官溝通清楚是不是可以破壞結構,是不是可以額外新增儲存空間。

func reversePrint(head *ListNode) []int {
    res := make([]int, 0)
    if head == nil {
        return res
    }
    for head != nil {
        res = append(res, head.Val)
        head = head.Next
    }
    for i, j := 0, len(res)-1; i < j; {
        res[i], res[j] = res[j], res[i]
        i++
        j--
    }
    return res
}

這裡有更簡潔的程式碼,但是效率很低,就是直接在陣列前面插入數字,直接返回陣列就可以了,由此可見,陣列的插入是多麼的耗時間。原因和前面二維陣列查詢的原因一樣,前面每次插入所有的元素都要排序一次。

func reversePrint(head *ListNode) []int {
    res := make([]int, 0)
    if head == nil {
        return res
    }
    for head.Next != nil {
        res = append([]int{head.Val}, res...)
        head = head.Next
    }
    res = append([]int{head.Val}, res...)
    return res
}

面試題6:重建二叉樹

leetcode-cn.com/problems/zhong-jia...
可以知道,只要和樹關聯的都是中等難度起步,沒有簡單的。前序和中序遍歷,構建一棵樹。面試中不會問到這種問題的,用筆畫出來可能更好理解。不會的不用糾結,過段時間回頭看發現這都是基礎了。另外,遞迴也要好好研究下,我也用的不是很溜,有時候知道原理也不一定能實現的出來,所以藉助leetcode的測試用例,還是很有用的。當然有人取巧,把每個用例都是手動實現一遍,我們是練習,沒必要,大牛才這麼搞。

func buildTree(preorder []int, inorder []int) *TreeNode {
    if len(preorder) == 0 {
        return nil
    }
    var index = 0
    for ;index < len(inorder); index++{// 遍歷中序
        if preorder[0] == inorder[index] {//找到根結點的值位置
            break
        }
    }
    root:=&TreeNode{}
    root.Val = preorder[0]
    root.Left = buildTree(preorder[1:index+1],inorder[0:index])//遞迴左子樹
    root.Right = buildTree(preorder[index+1:],inorder[index+1:])//遞迴右子樹
    return root
}

面試題7:用兩個棧實現佇列

leetcode-cn.com/problems/yong-lian...
這個提,我覺得完全是動嘴的題目,沒有考察程式碼能力,純粹的思維考察。有時候不要糾結,該跳過跳過,看看實現原理,不動手也可以的。畢竟這個實現也是新增和刪除,想不到該分享的技術實現在哪。用了官網的答案:

type CQueue struct {
    stack1, stack2 *list.List
}

func Constructor() CQueue {
    return CQueue{
        stack1: list.New(),
        stack2: list.New(),
    }
}

func (this *CQueue) AppendTail(value int)  {
    this.stack1.PushBack(value)
}

func (this *CQueue) DeleteHead() int {
    // 如果第二個棧為空
    if this.stack2.Len() == 0 {
        for this.stack1.Len() > 0 {
            this.stack2.PushBack(this.stack1.Remove(this.stack1.Back()))
        }
    }
    if this.stack2.Len() != 0 {
        e := this.stack2.Back()
        this.stack2.Remove(e)
        return e.Value.(int)
    }
    return -1
}

面試題8:旋轉陣列最小數字

leetcode-cn.com/problems/xuan-zhua...
暴力搜尋都可以通過


func minArray(numbers []int) int {
    if len(numbers) == 0 {
        return 0
    }
    min := numbers[0]
    for i := 1; i < len(numbers); i++ {
        if numbers[i-1] > numbers[i] {
            if min > numbers[i] {
                min = numbers[i]
            }
            break
        }
    }
    return min
}

當然,人家看差點肯定不是這個,是如果更快的查詢到這個值。因為是組多有兩部分排序組成的,依然可以使用二分查詢。參考官網的,我也是沒想到這麼多特例的,low逼一個。如果有人不會,看題解吧,比我解釋起來好太多太多。leetcode-cn.com/problems/xuan-zhua...

func minArray(numbers []int) int {
    low := 0
    high := len(numbers) - 1
    for low < high {
        pivot := low + (high - low) / 2
        if numbers[pivot] < numbers[high] {
            high = pivot
        } else if numbers[pivot] > numbers[high] {
            low = pivot + 1
        } else {
            high--
        }
    }
    return numbers[low]
}

面試題9:斐波那契數列

leetcode-cn.com/problems/fei-bo-na...
經典的不能再經典的題,遞迴的經典使用題型。

func fib(n int) int {
if n == 0 {
        return 0
    }
    if n == 1 {
        return 1
    }
    return fib(n-1)+fib(n-2)
}

這是經典寫法,完全按照表示式來實現的,結果在leetcode裡面是超過時間限制的提示。體會到網站的強大之處嗎?

func fib(n int) int {
    const mod int = 1e9 + 7
    if n < 2 {
        return n
    }
    p, q, r := 0, 0, 1
    for i := 2; i <= n; i++ {
        p = q
        q = r
        r = (p + q) % mod
    }
    return r
}

昨天有人跟我反饋說可以使用尾遞迴也是一種優化方式,這個確實是,但是不符合leetcode的要求,因為尾遞迴的方式改變了入參個數。如果要使用,只能新增一個新的函式包裹一層。但是,過不了leetcode的測試用例,我也不知道哪裡出問題了,知道的朋友還望告知一下。

func fib(n int64) int64 {
    return getFibNum(0, 1, n)
}
func getFibNum(first, second, n int) int {
    const mod int = 1e9 + 7 // leetcode要求
    if n < 2 {
        return n
    }
    if 2 == n {
        return first + second
    }
    return getFibNum(second, first+second, n-1)%mod
}

面試題10:二進位制中1的個數

leetcode-cn.com/problems/er-jin-zh...
我這個道題也是按照人家的答案來實現的,想想大學時光的那些知識點全部忘的一乾二淨。剛好可以補充一下。

func hammingWeight(num uint32) int {
    var count = 0
    for ; num > 0; {
        count++
        num = (num - 1) & num//與操作,直接得出1的個數
    }
    return count
}

這就是第二章的練習題,也是基礎的,如果有不會的,絕對要花時間去學習,研究,尤其是演算法這個東西,又沒有實際業務支撐的肯定很難學習一下的,全靠自律了,沒時間也要擠時間。我問過類似的問題,大佬的回答是:少玩抖音,少玩遊戲,時間自然就有了。共勉!!
第三章,研究的時間肯定會很長,因為每一道題都是自己敲會,不會就反覆敲,十遍不行就二十遍,知道自己完全會了為止。

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

相關文章