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 協議》,轉載必須註明作者和本文連結