本部分主要是 CavsZhouyou 在練習《劍指 Offer》時所做的筆記,主要涉及算法相關知識和一些相關面試題時所做的筆記,分享這份總結給大家,幫助大家對算法的可以來一次全方位的檢漏和排查。
附筆記鏈接,如果對你有幫助請給我點贊鼓勵哦:https://github.com/Wscats/articles
1. 二維數組中的查找
題目:
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的
一個二維數組和一個整數,判斷數組中是否含有該整數。
思路:
(1)第一種方式是使用兩層循環依次遍歷,判斷是否含有該整數。這一種方式最壞情況下的時間復雜度為 O(n^2)。
(2)第二種方式是利用遞增序列的特點,我們可以從二維數組的右上角開始遍歷。如果當前數值比所求的數要小,則將位置向下移動
,再進行判斷。如果當前數值比所求的數要大,則將位置向左移動,再進行判斷。這一種方式最壞情況下的時間復雜度為 O(n)。
2. 替換空格
題目:
請實現一個函數,將一個字符串中的空格替換成“%20”。例如,當字符串為 We Are Happy.則經過替換之后的字符串為 We%20
Are%20Happy
思路:
使用正則表達式,結合字符串的 replace 方法將空格替換為 “%20”
str.replace(/\s/g,"%20")
3. 從尾到頭打印鏈表
題目:
輸入一個鏈表,從尾到頭打印鏈表每個節點的值。
思路:
利用棧來實現,首先根據頭結點以此遍歷鏈表節點,將節點加入到棧中。當遍歷完成后,再將棧中元素彈出並打印,以此來實現。棧的
實現可以利用 Array 的 push 和 pop 方法來模擬。
4. 重建二叉樹
題目:
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸
入前序遍歷序列 {1,2,4,7,3,5,6,8} 和中序遍歷序列 {4,7,2,1,5,3,8,6},則重建二叉樹並返回。
思路:
利用遞歸的思想來求解,首先先序序列中的第一個元素一定是根元素。然后我們去中序遍歷中尋找到該元素的位置,找到后該元素的左
邊部分就是根節點的左子樹,右邊部分就是根節點的右子樹。因此我們可以分別截取對應的部分進行子樹的遞歸構建。使用這種方式的
時間復雜度為 O(n),空間復雜度為 O(logn)。
5. 用兩個棧實現隊列
題目:
用兩個棧來實現一個隊列,完成隊列的 Push 和 Pop 操作。
思路:
隊列的一個基本特點是,元素先進先出。通過兩個棧來模擬時,首先我們將兩個棧分為棧 1 和棧 2。當執行隊列的 push 操作時,直接
將元素 push 進棧 1 中。當隊列執行 pop 操作時,首先判斷棧 2 是否為空,如果不為空則直接 pop 元素。如果棧 2 為空,則將棧 1 中
的所有元素 pop 然后 push 到棧 2 中,然后再執行棧 2 的 pop 操作。
擴展:
當使用兩個長度不同的棧來模擬隊列時,隊列的最大長度為較短棧的長度的兩倍。
6. 旋轉數組的最小數字
題目:
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的
最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為 1。 NOTE:給出的所有元素都大於 0,若數組大
小為 0,請返回 0。
思路:
(1)我們輸入的是一個非遞減排序的數組的一個旋轉,因此原始數組的值遞增或者有重復。旋轉之后原始數組的值一定和一個值相
鄰,並且不滿足遞增關系。因此我們就可以進行遍歷,找到不滿足遞增關系的一對值,后一個值就是旋轉數組的最小數字。
(2)二分法
相關資料可以參考:
《旋轉數組的最小數字》
7. 斐波那契數列
題目:
大家都知道斐波那契數列,現在要求輸入一個整數 n,請你輸出斐波那契數列的第 n 項。 n<=39
思路:
斐波那契數列的規律是,第一項為 0,第二項為 1,第三項以后的值都等於前面兩項的和,因此我們可以通過循環的方式,不斷通過疊
加來實現第 n 項值的構建。通過循環而不是遞歸的方式來實現,時間復雜度降為了 O(n),空間復雜度為 O(1)。
8. 跳台階
題目:
一只青蛙一次可以跳上 1 級台階,也可以跳上 2 級。求該青蛙跳上一個 n 級的台階總共有多少種跳法。
思路:
跳台階的問題是一個動態規划的問題,由於一次只能夠跳 1 級或者 2 級,因此跳上 n 級台階一共有兩種方案,一種是從 n-1 跳上,一
種是從 n-2 級跳上,因此 f(n) = f(n-1) + f(n-2)。
和斐波那契數列類似,不過初始兩項的值變為了 1 和 2,后面每項的值等於前面兩項的和。
9. 變態跳台階
題目:
一只青蛙一次可以跳上 1 級台階,也可以跳上 2 級……它也可以跳上 n 級。求該青蛙跳上一個 n 級的台階總共有多少種跳法。
思路:
變態跳台階的問題同上一個問題的思考方案是一樣的,我們可以得到一個結論是,每一項的值都等於前面所有項的值的和。
f(1) = 1
f(2) = f(2-1) + f(2-2) //f(2-2) 表示 2 階一次跳 2 階的次數。
f(3) = f(3-1) + f(3-2) + f(3-3)
...
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
再次總結可得
| 1 ,(n=0 )
f(n) = | 1 ,(n=1 )
| 2\*f(n-1),(n>=2)
10. 矩形覆蓋
題目:
我們可以用 2*1 的小矩形橫着或者豎着去覆蓋更大的矩形。請問用 n 個 2*1 的小矩形無重疊地覆蓋一個 2\*n 的大矩形,總共
有多少種方法?
思路:
依舊是斐波那契數列的應用
11. 二進制中 1 的個數
題目:
輸入一個整數,輸出該數二進制表示中 1 的個數。其中負數用補碼表示。
思路:
一個不為 0 的整數的二進制表示,一定會有一位為 1。我們找到最右邊的一位 1,當我們將整數減去 1 時,最右邊的一位 1 變為 0,它后
面的所有位都取反,因此將減一后的值與原值相與,我們就會能夠消除最右邊的一位 1。因此判斷一個二進制中 1 的個數,我們可以判
斷這個數可以經歷多少次這樣的過程。
如:1100&1011=1000
12. 數值的整數次方
題目:
給定一個 double 類型的浮點數 base 和 int 類型的整數 exponent。求 base 的 exponent 次方。
思路:
首先我們需要判斷 exponent 正負和零取值三種情況,根據不同的情況通過遞歸來實現。
13. 調整數組順序使奇數位於偶數前面
題目:
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於位於數組的后半
部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
思路:
由於需要考慮到調整之后的穩定性,因此我們可以使用輔助數組的方式。首先對數組中的元素進行遍歷,每遇到一個奇數就將它加入到
奇數輔助數組中,每遇到一個偶數,就將它將入到偶數輔助數組中。最后再將兩個數組合並。這一種方法的時間復雜度為 O(n),空間
復雜度為 O(n)。
14. 鏈表中倒數第 k 個節點
題目:
輸入一個鏈表,輸出該鏈表中倒數第 k 個結點。
思路:
使用兩個指針,先讓第一個和第二個指針都指向頭結點,然后再讓第二個指針走 k-1 步,到達第 k 個節點。然后兩個指針同時向后
移動,當第二個指針到達末尾時,第一個指針指向的就是倒數第 k 個節點了。
15. 反轉鏈表
題目:
輸入一個鏈表,反轉鏈表后,輸出鏈表的所有元素。
思路:
通過設置三個變量 pre、current 和 next,分別用來保存前繼節點、當前節點和后繼結點。從第一個節點開始向后遍歷,首先將當
前節點的后繼節點保存到 next 中,然后將當前節點的后繼節點設置為 pre,然后再將 pre 設置為當前節點,current 設置為 ne
xt 節點,實現下一次循環。
16. 合並兩個排序的鏈表
題目:
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成后的鏈表,當然我們需要合成后的鏈表滿足單調不減規則。
思路:
通過遞歸的方式,依次將兩個鏈表的元素遞歸進行對比。
17. 樹的子結構
題目:
輸入兩棵二叉樹 A、B,判斷 B 是不是 A 的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
思路:
通過遞歸的思想來解決
第一步首先從樹 A 的根節點開始遍歷,在左右子樹中找到和樹 B 根結點的值一樣的結點 R 。
第二步兩棵樹同時從 R 節點和根節點以相同的遍歷方式進行遍歷,依次比較對應的值是否相同,當樹 B 遍歷結束時,結束比較。
18. 二叉樹的鏡像
題目:
操作給定的二叉樹,將其變換為源二叉樹的鏡像。
思路:
從根節點開始遍歷,首先通過臨時變量保存左子樹的引用,然后將根節點的左右子樹的引用交換。然后再遞歸左右節點的子樹交換。
19. 順時針打印矩陣
題目:
輸入一個矩陣,按照從外向里以順時針的順序依次打印出每一個數字,
例如,如果輸入如下矩陣: 1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
則依次打印出數字 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10
思路:
(1)根據左上角和右下角可以定位出一次要旋轉打印的數據。一次旋轉打印結束后,往對角分別前進和后退一個單位,可以確定下一
次需要打印的數據范圍。
(2)使用模擬魔方逆時針解法,每打印一行,則將矩陣逆時針旋轉 90 度,打印下一行,依次重復。
20. 定義一個棧,實現 min 函數
題目:
定義棧的數據結構,請在該類型中實現一個能夠得到棧最小元素的 min 函數。
思路:
使用一個輔助棧,每次將數據壓入數據棧時,就把當前棧里面最小的值壓入輔助棧當中。這樣輔助棧的棧頂數據一直是數據棧中最小
的值。
21. 棧的壓入彈出
題目:
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如
序列 1,2,3,4,5 是某棧的壓入順序,序列 4,5,3,2,1 是該壓棧序列對應的一個彈出序列,但 4,3,5,1,2 就不可能是該壓棧序
列的彈出序列。(注意:這兩個序列的長度是相等的)
思路:
我們可以使用一個輔助棧的方式來實現,首先遍歷壓棧順序,依次將元素壓入輔助棧中,每次壓入元素后我們首先判斷該元素是否與出
棧順序中的此刻位置的元素相等,如果不相等,則將元素繼續壓棧,如果相等,則將輔助棧中的棧頂元素出棧,出棧后,將出棧順序中
的位置后移一位繼續比較。當壓棧順序遍歷完成后,如果輔助棧不為空,則說明該出棧順序不正確。
22. 從上往下打印二叉樹
題目:
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。
思路:
本質上是二叉樹的層序遍歷,可以通過隊列來實現。首先將根節點入隊。然后對隊列進行出隊操作,每次出隊時,將出隊元素的左右子
節點依次加入到隊列中,直到隊列長度變為 0 時,結束遍歷。
23. 二叉搜索樹的后序遍歷
題目:
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。如果是則輸出 Yes,否則輸出 No。假設輸入的數組的任意兩
個數字都互不相同。
思路:
對於一個合法而二叉樹的后序遍歷來說,最末尾的元素為根元素。該元素前面的元素可以划分為兩個部分,一部分為該元素的左子樹,
所有元素的值比根元素小,一部分為該元素的右子樹,所有的元素的值比該根元素大。並且每一部分都是一個合法的后序序列,因此我
們可以利用這些特點來遞歸判斷。
24. 二叉樹中和為某一值路徑
題目:
輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經
過的結點形成一條路徑。
思路:
通過對樹進行深度優先遍歷,遍歷時保存當前節點的值並判斷是否和期望值相等,如果遍歷到葉節點不符合要求則回退處理。
25. 復雜鏈表的復制
題目:
輸入一個復雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果為
復制后復雜鏈表的 head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)
思路:
(1)第一種方式,首先對原有鏈表每個節點進行復制,通過 next 連接起來。然后當鏈表復制完成之后,再來設置每個節點的 ra
ndom 指針,這個時候每個節點的 random 的設置都需要從頭結點開始遍歷,因此時間的復雜度為 O(n^2)。
(2)第二種方式,首先對原有鏈表每個節點進行復制,並且使用 Map 以鍵值對的方式將原有節點和復制節點保存下來。當鏈表復
制完成之后,再來設置每個節點的 random 指針,這個時候我們通過 Map 中的鍵值關系就可以獲取到對應的復制節點,因此
不必再從頭結點遍歷,將時間的復雜度降低為了 O(n),但是空間復雜度變為了 O(n)。這是一種以空間換時間的做法。
(3)第三種方式,首先對原有鏈表的每個節點進行復制,並將復制后的節點加入到原有節點的后面。當鏈表復制完成之后,再進行
random 指針的設置,由於每個節點后面都跟着自己的復制節點,因此我們可以很容易的獲取到 random 指向對應的復制節點
。最后再將鏈表分離,通過這種方法我們也能夠將時間復雜度降低為 O(n)。
26. 二叉搜索樹與雙向鏈表
題目:
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。
思路:
需要生成一個排序的雙向列表,那么我們應該通過中序遍歷的方式來調整樹結構,因為只有中序遍歷,返回才是一個從小到大的排序
序列。
基本的思路是我們首先從根節點開始遍歷,先將左子樹調整為一個雙向鏈表,並將左子樹雙向鏈表的末尾元素的指針指向根節點,並
將根節點的左節點指向末尾節點。再將右子樹調整為一個雙向鏈表,並將右子樹雙向鏈表的首部元素的指針指向根元素,再將根節點
的右節點指向首部節點。通過對左右子樹遞歸調整,因此來實現排序的雙向鏈表的構建。
27. 字符串的排列
題目:
輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串 abc,則打印出由字符 a,b,c 所能排列出來的所有
字符串 abc,acb,bac,bca,cab 和 cba。輸入描述:輸入一個字符串,長度不超過 9(可能有字符重復),字符只包括大小寫字母。
思路:
我們可以把一個字符串看做是兩個部分,第一部分為它的第一個字符,第二部分是它后面的所有字符。求整個字符串的一個全排列,可
以看做兩步,第一步是求所有可能出現在第一個位置的字符,即把第一個字符和后面的所有字符交換。第二步就是求后面所有字符的一
個全排列。因此通過這種方式,我們可以以遞歸的思路來求出當前字符串的全排列。
詳細資料可以參考:
《字符串的排列》
28. 數組中出現次數超過一半的數字
題目:
數組中有一個數字出現的次數超過數組長度的一半。請找出這個數字。例如輸入一個長度為 9 的數組{1,2,3,2,2,2,5,4,2}。由於數
字 2 在數組中出現了 5 次,超過數組長度的一半,因此輸出 2。如果不存在則輸出 0。
思路:
(1)對數組進行排序,排序后的中位數就是所求數字。這種方法的時間復雜度取決於我們采用的排序方法的時間復雜度,因此最快為
O(nlogn)。
(2)由於所求數字的數量超過了數組長度的一半,因此排序后的中位數就是所求數字。因此我們可以將問題簡化為求一個數組的中
位數問題。其實數組並不需要全排序,只需要部分排序。我們通過利用快排中的 partition 函數來實現,我們現在數組中隨
機選取一個數字,而后通過 partition 函數返回該數字在數組中的索引 index,如果 index 剛好等於 n/2,則這個數字
便是數組的中位數,也即是要求的數,如果 index 大於 n/2,則中位數肯定在 index 的左邊,在左邊繼續尋找即可,反之
在右邊尋找。這樣可以只在 index 的一邊尋找,而不用兩邊都排序,減少了一半排序時間,這種方法的時間復雜度為 O(n)。
(3)由於該數字的出現次數比所有其他數字出現次數的和還要多,因此可以考慮在遍歷數組時保存兩個值:一個是數組中的一個數
字,一個是次數。當遍歷到下一個數字時,如果下一個數字與之前保存的數字相同,則次數加 1,如果不同,則次數減 1,如果
次數為 0,則需要保存下一個數字,並把次數設定為 1。由於我們要找的數字出現的次數比其他所有數字的出現次數之和還要大,
則要找的數字肯定是最后一次把次數設為 1 時對應的數字。該方法的時間復雜度為 O(n),空間復雜度為 O(1)。
詳細資料可以參考:
《出現次數超過一半的數字》
29. 最小的 K 個數
題目:
輸入 n 個整數,找出其中最小的 K 個數。例如輸入 4,5,1,6,2,7,3,8 這 8 個數字,則最小的 4 個數字是 1,2,3,4 。
思路:
(1)第一種思路是首先將數組排序,排序后再取最小的 k 個數。這一種方法的時間復雜度取決於我們選擇的排序算法的時間復雜
度,最好的情況下為 O(nlogn)。
(2)第二種思路是由於我們只需要獲得最小的 k 個數,這 k 個數不一定是按序排序的。因此我們可以使用快速排序中的 part
ition 函數來實現。每一次選擇一個樞紐值,將數組分為比樞紐值大和比樞紐值小的兩個部分,判斷樞紐值的位置,如果該樞
紐值的位置為 k-1 的話,那么樞紐值和它前面的所有數字就是最小的 k 個數。如果樞紐值的位置小於 k-1 的話,假設樞
紐值的位置為 n-1,那么我們已經找到了前 n 小的數字了,我們就還需要到后半部分去尋找后半部分 k-n 小的值,進行划
分。當該樞紐值的位置比 k-1 大時,說明最小的 k 個值還在左半部分,我們需要繼續對左半部分進行划分。這一種方法的平
均時間復雜度為 O(n)。
(3)第三種方法是維護一個容量為 k 的最大堆。對數組進行遍歷時,如果堆的容量還沒有達到 k ,則直接將元素加入到堆中,這
就相當於我們假設前 k 個數就是最小的 k 個數。對 k 以后的元素遍歷時,我們將該元素與堆的最大值進行比較,如果比最
大值小,那么我們則將最大值與其交換,然后調整堆。如果大於等於堆的最大值,則繼續向后遍歷,直到數組遍歷完成。這一
種方法的平均時間復雜度為 O(nlogk)。
詳細資料可以參考:
《尋找最小的 k 個數》
30. 連續子數組的最大和
題目:
HZ 偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會后,他又發話了:在古老的一維模式識別中,常常需要計
算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的
正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和為 8(從第 0 個開始,到第 3 個為止)。你會不會被他忽悠
住?(子向量的長度至少是 1)
思路:
(1)第一種思路是直接暴力求解的方式,先以第一個數字為首往后開始疊加,疊加的過程中保存最大的值。然后再以第二個數字為首
往后開始疊加,並與先前保存的最大的值進行比較。這一種方法的時間復雜度為 O(n^2)。
(2)第二種思路是,首先我們觀察一個最大和的連續數組的規律,我們可以發現,子數組一定是以正數開頭的,中間包含了正負數。
因此我們可以從第一個數開始向后疊加,每次保存最大的值。疊加的值如果為負數,則將疊加值初始化為 0,因為后面的數加上負
數只會更小,因此需要尋找下一個正數開始下一個子數組的判斷。一直往后判斷,直到這個數組遍歷完成為止,得到最大的值。
使用這一種方法的時間復雜度為 O(n)。
詳細資料可以參考:
《連續子數組的最大和》
31. 整數中 1 出現的次數(待深入理解)
題目:
求出 1~13 的整數中 1 出現的次數,並算出 100~1# 300 的整數中 1 出現的次數?為此他特別數了一下 1~13 中包含 1 的數字有 1、10、11、
12、13 因此共出現 6 次,但是對於后面問題他就沒轍了。ACMer 希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整
數區間中 1 出現的次數。
思路:
(1)第一種思路是直接遍歷每個數,然后將判斷每個數中 1 的個數,一直疊加。
(2)第二種思路是求出 1 出現在每位上的次數,然后進行疊加。
詳細資料可以參考:
《從 1 到 n 整數中 1 出現的次數:O(logn)算法》
32. 把數組排成最小的數
題目:
輸入一個正整數數組,把數組里所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321
},則打印出這三個數字能排成的最小數字為 321323。
思路:
(1)求出數組的全排列,然后對每個排列結果進行比較。
(2)利用排序算法實現,但是比較時,比較的並不是兩個元素的大小,而是兩個元素正序拼接和逆序拼接的大小,如果逆序拼接的
結果更小,則交換兩個元素的位置。排序結束后,數組的順序則為最小數的排列組合順序。
詳細資料可以參考:
《把數組排成最小的數》
33. 丑數(待深入理解)
題目:
把只包含質因子 2、3 和 5 的數稱作丑數。例如 6、8 都是丑數,但 14 不是,因為它包含因子 7。 習慣上我們把 1 當做是第一個丑數。求
按從小到大的順序的第 N 個丑數。
思路:
(1)判斷一個數是否為丑數,可以判斷該數不斷除以 2,最后余數是否為 1。判斷該數不斷除以 3,最后余數是否為 1。判斷不斷除以
5,最后余數是否為 1。在不考慮時間復雜度的情況下,可以依次遍歷找到第 N 個丑數。
(2)使用一個數組來保存已排序好的丑數,后面的丑數由前面生成。
34. 第一個只出現一次的字符
題目:
在一個字符串(1<=字符串長度<=10000,全部由大寫字母組成)中找到第一個只出現一次的字符,並返回它的位置。
思路:
(1)第一種思路是,從前往后遍歷每一個字符。每遍歷一個字符,則將字符與后邊的所有字符依次比較,判斷是否含有相同字符。這
一種方法的時間復雜度為 O(n^2)。
(2)第二種思路是,首先對字符串進行一次遍歷,將字符和字符出現的次數以鍵值對的形式存儲在 Map 結構中。然后第二次遍歷時
,去 Map 中獲取對應字符出現的次數,找到第一個只出現一次的字符。這一種方法的時間復雜度為 O(n)。
35. 數組中的逆序對
題目:
在數組中的兩個數字,如果前面一個數字大於后面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對
的總數 P。
思路:
(1)第一種思路是直接求解的方式,順序掃描整個數組。每掃描到一個數字的時候,逐個比較該數字和它后面的數字的大小。如果
后面的數字比它小,則這兩個數字就組成了一個逆序對。假設數組中含有 n 個數字。由於每個數字都要和 O(n)個數字作比
較,因此這個算法的時間復雜度是 O(n^2)。
(2)第二種方式是使用歸並排序的方式,通過利用歸並排序分解后進行合並排序時,來進行逆序對的統計,這一種方法的時間復雜
度為 O(nlogn)。
詳細資料可以參考:
《數組中的逆序對》
36. 兩個鏈表的第一個公共結點
題目:
輸入兩個鏈表,找出它們的第一個公共結點。
思路:
(1)第一種方法是在第一個鏈表上順序遍歷每個結點,每遍歷到一個結點的時候,在第二個鏈表上順序遍歷每個結點。如果在第二
個鏈表上有一個結點和第一個鏈表上的結點一樣,說明兩個鏈表在這個結點上重合,於是就找到了它們的公共結點。如果第一
個鏈表的長度為 m,第二個鏈表的長度為 n。這一種方法的時間復雜度是 O(mn)。
(2)第二種方式是利用棧的方式,通過觀察我們可以發現兩個鏈表的公共節點,都位於鏈表的尾部,以此我們可以分別使用兩個棧
,依次將鏈表元素入棧。然后在兩個棧同時將元素出棧,比較出棧的節點,最后一個相同的節點就是我們要找的公共節點。這
一種方法的時間復雜度為 O(m+n),空間復雜度為 O(m+n)。
(3)第三種方式是,首先分別遍歷兩個鏈表,得到兩個鏈表的長度。然后得到較長的鏈表與較短的鏈表長度的差值。我們使用兩個
指針來分別對兩個鏈表進行遍歷,首先將較長鏈表的指針移動 n 步,n 為兩個鏈表長度的差值,然后兩個指針再同時移動,
判斷所指向節點是否為同一節點。這一種方法的時間復雜度為 O(m+n),相同對於上一種方法不需要額外的空間。
詳細資料可以參考:
《兩個鏈表的第一個公共結點》
37. 數字在排序數組中出現的次數
題目:
統計一個數字:在排序數組中出現的次數。例如輸入排序數組{ 1, 2, 3, 3, 3, 3, 4, 5}和數字 3 ,由於 3 在這個數組中出
現了 4 次,因此輸出 4 。
思路:
(1)第一種方法是直接對數組順序遍歷的方式,通過這種方法來統計數字的出現次數。這種方法的時間復雜度為 O(n)。
(2)第二種方法是使用二分查找的方法,由於數組是排序好的數組,因此相同數字是排列在一起的。統計數字出現的次數,我們需要
去找到該段數字開始和結束的位置,以此來確定數字出現的次數。因此我們可以使用二分查找的方式來確定該數字的開始和結束
位置。如果我們第一次我們數組的中間值為 k ,如果 k 值比所求值大的話,那么我們下一次只需要判斷前面一部分就行了,如
果 k 值比所求值小的話,那么我們下一次就只需要判斷后面一部分就行了。如果 k 值等於所求值的時候,我們則需要判斷該值
是否為開始位置或者結束位置。如果是開始位置,那么我們下一次需要到后半部分去尋找結束位置。如果是結束位置,那么我們
下一次需要到前半部分去尋找開始位置。如果既不是開始位置也不是結束位置,那么我們就分別到前后兩個部分去尋找開始和結
束位置。這一種方法的平均時間復雜度為 O(logn)。
38. 二叉樹的深度
題目:
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度為樹的深
度。
思路:
根節點的深度等於左右深度較大值加一,因此可以通過遞歸遍歷來實現。
39. 平衡二叉樹
題目:
輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。
思路:
(1)在遍歷樹的每個結點的時候,調用函數得到它的左右子樹的深度。如果每個結點的左右子樹的深度相差都不超過 1 ,那么它
就是一棵平衡的二叉樹。使用這種方法時,節點會被多次遍歷,因此會造成效率不高的問題。
(2)在求一個節點的深度時,同時判斷它是否平衡。如果不平衡則直接返回 -1,否則返回樹高度。如果一個節點的一個子樹的深
度為-1,那么就直接向上返回 -1 ,該樹已經是不平衡的了。通過這種方式確保了節點只能夠被訪問一遍。
40. 數組中只出現一次的數字
題目:
一個整型數組里除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。
思路:
(1)第一種方式是依次遍歷數組,記錄下數字出現的次數,從而找出兩個只出現一次的數字。
(2)第二種方式,根據位運算的異或的性質,我們可以知道兩個相同的數字異或等於 0,一個數和 0 異或還是它本身。由於數組中
的其他數字都是成對出現的,因此我們可以將數組中的所有數依次進行異或運算。如果只有一個數出現一次的話,那么最后剩下
的就是落單的數字。如果是兩個數只出現了一次的話,那么最后剩下的就是這兩個數異或的結果。這個結果中的 1 表示的是 A 和
B 不同的位。我們取異或結果的第一個 1 所在的位數,假如是第 3 位,接着通過比較第三位來將數組分為兩組,相同數字一定會
被分到同一組。分組完成后再按照依次異或的思路,求得剩余數字即為兩個只出現一次的數字。
41. 和為 S 的連續正數序列
題目:
小明很喜歡數學,有一天他在做數學作業時,要求計算出 9~16 的和,他馬上就寫出了正確答案是 100。但是他並不滿足於此,他在想究
竟有多少種連續的正數序列的和為 100(至少包括兩個數)。沒多久,他就得到另一組連續正數和為 100 的序列:18,19,20,21,22。
現在把問題交給你,你能不能也很快的找出所有和為 S 的連續正數序列?Good Luck!輸出描述:輸出所有和為 S 的連續正數序列。序
列內按照從小至大的順序,序列間按照開始數字從小到大的順序。
思路:
維護一個正數序列數組,數組中初始只含有值 1 和 2,然后從 3 依次往后遍歷,每遍歷到一個元素則將這個元素加入到序列數組中,然后
判斷此時序列數組的和。如果序列數組的和大於所求值,則將第一個元素(最小的元素彈出)。如果序列數組的和小於所求值,則繼續
往后遍歷,將元素加入到序列中繼續判斷。當序列數組的和等於所求值時,打印出此時的正數序列,然后繼續往后遍歷,尋找下一個連
續序列,直到數組遍歷完成終止。
詳細資料可以參考:
《和為 s 的連續正數序列》
42. 和為 S 的兩個數字
題目:
輸入一個遞增排序的數組和一個數字 S,在數組中查找兩個數,是的他們的和正好是 S,如果有多對數字的和等於 S,輸出兩個數
的乘積最小的。輸出描述:對應每個測試案例,輸出兩個數,小的先輸出。
思路:
首先我們通過規律可以發現,和相同的兩個數字,兩個數字的差值越大,乘積越小。因此我們只需要從數組的首尾開始找到第一對和
為 s 的數字對進行了。因此我們可以使用雙指針的方式,左指針初始指向數組的第一個元素,右指針初始指向數組的最后一個元素
。然后首先判斷兩個指針指向的數字的和是否為 s ,如果為 s ,兩個指針指向的數字就是我們需要尋找的數字對。如果兩數的和
比 s 小,則將左指針向左移動一位后繼續判斷。如果兩數的和比 s 大,則將右指針向右移動一位后繼續判斷。
詳細資料可以參考:
《和為 S 的字符串》
43. 左旋轉字符串
題目:
匯編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的
字符序列 S,請你把其循環左移 K 位后的序列輸出。例如,字符序列 S=”abcXYZdef”,要求輸出循環左移 3 位后的結果,即 “X
YZdefabc”。是不是很簡單?OK,搞定它!
思路:
字符串裁剪后拼接
44. 翻轉單詞順序列
題目:
牛客最近來了一個新員工 Fish,每天早晨總是會拿着一本英文雜志,寫些句子在本子上。同事 Cat 對 Fish 寫的內容頗感興趣,有
一天他向 Fish 借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。后來才意識到,這家伙原來把句子單詞的順序翻轉了
,正確的句子應該是“I am a student.”。Cat 對一一的翻轉這些單詞順序可不在行,你能幫助他么?
思路:
通過空格將單詞分隔,然后將數組反序后,重新拼接為字符串。
45. 撲克牌的順子
題目:
LL 今天心情特別好,因為他去買了一副撲克牌,發現里面居然有 2 個大王,2 個小王(一副牌原本是 54 張^\_^)...他隨機從中抽出
了 5 張牌,想測測自己的手氣,看看能不能抽到順子,如果抽到的話,他決定去買體育彩票,嘿嘿!!“紅心 A,黑桃 3,小王,大王
,方片 5”,“Oh My God!”不是順子..... LL 不高興了,他想了想,決定大\小王可以看成任何數字,並且 A 看作 1,J 為 11,
Q 為 12,K 為 13。上面的 5 張牌就可以變成“1,2,3,4,5”(大小王分別看作 2 和 4),“So Lucky!”。LL 決定去買體育彩票啦。
現在,要求你使用這幅牌模擬上面的過程,然后告訴我們 LL 的運氣如何。為了方便起見,你可以認為大小王是 0。
思路:
首先判斷 5 個數字是不是連續的,最直觀的方法是把數組排序。值得注意的是,由於 0 可以當成任意數字,我們可以用 0 去補滿數
組中的空缺。如果排序之后的數組不是連續的,即相鄰的兩個數字相隔若干個數字,但只要我們有足夠的。可以補滿這兩個數字的空
缺,這個數組實際上還是連續的。
於是我們需要做 3 件事情:首先把數組排序,再統計數組中 0 的個數,最后統計排序之后的數組中相鄰數字之間的空缺總數。如
果空缺的總數小於或者等於 0 的個數,那么這個數組就是連續的:反之則不連續。最后,我們還需要注意一點:如果數組中的非 0
數字重復出現,則該數組不是連續的。換成撲克牌的描述方式就是如果一副牌里含有對子,則不可能是順子。
詳細資料可以參考:
《撲克牌的順子》
46. 圓圈中最后剩下的數字(約瑟夫環問題)
題目:
0, 1, … , n-1 這 n 個數字排成一個圈圈,從數字 0 開始每次從圓圏里刪除第 m 個數字。求出這個圈圈里剩下的最后一個數
字。
思路:
(1)使用環形鏈表進行模擬。
(2)根據規律得出(待深入理解)
詳細資料可以參考:
《圓圈中最后剩下的數字》
47. 1+2+3+...+n
題目:
求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等關鍵字及條件判斷語句(A?B:C)。
思路:
由於不能使用循環語句,因此我們可以通過遞歸來實現。並且由於不能夠使用條件判斷運算符,我們可以利用 && 操作符的短路特
性來實現。
48. 不用加減乘除做加法
題目:
寫一個函數,求兩個整數之和,要求在函數體內不得使用 +、-、×、÷ 四則運算符號。
思路:
通過位運算,遞歸來實現。
49. 把字符串轉換成整數。
題目:
將一個字符串轉換成一個整數,要求不能使用字符串轉換整數的庫函數。數值為 0 或者字符串不是一個合法的數值則返回 0。輸入描
述:輸入一個字符串,包括數字字母符號,可以為空。輸出描述:如果是合法的數值表達則返回該數字,否則返回 0。
思路:
首先需要進行符號判斷,其次我們根據字符串的每位通過減 0 運算轉換為整數和,依次根據位數疊加。
50. 數組中重復的數字
題目:
在一個長度為 n 的數組里的所有數字都在 0 到 n-1 的范圍內。數組中某些數字是重復的,但不知道有幾個數字重復了,也不知
道每個數字重復了幾次。請找出數組中任意一個重復的數字。
思路:
(1)首先將數組排序,排序后再進行判斷。這一種方法的時間復雜度為 O(nlogn)。
(2)使用 Map 結構的方式,依次記錄下每一個數字出現的次數,從而可以判斷是否出現重復數字。這一種方法的時間復雜度為 O
(n),空間復雜度為 O(n)。
(3)從數組首部開始遍歷,每遍歷一個數字,則將該數字和它的下標相比較,如果數字和下標不等,則將該數字和它對應下標的值
交換。如果對應的下標值上已經是正確的值了,那么說明當前元素是一個重復數字。這一種方法相對於上一種方法來說不需要
額外的內存空間。
51. 構建乘積數組
題目:
給定一個數組 A[0,1,...,n-1],請構建一個數組 B[0,1,...,n-1],其中 B 中的元素 B[i]=A[0]_A[1]_...*A[i-1]*A
[i+1]*...*A[n-1]。不能使用除法。
思路:
(1) C[i]=A[0]×A[1]×...×A[i-1]=C[i-1]×A[i-1]
D[i]=A[i+1]×...×A[n-1]=D[i+1]×A[i+1]
B[i]=C[i]×D[i]
將乘積分為前后兩個部分,分別循環求出后,再進行相乘。
(2)上面的方法需要額外的內存空間,我們可以引入中間變量的方式,來降低空間復雜度。(待深入理解)
詳細資料可以參考:
《構建乘積數組》
52. 正則表達式的匹配
題目:
請實現一個函數用來匹配包括'.'和'_'的正則表達式。模式中的字符'.'表示任意一個字符,而'_'表示它前面的字符可以出現任
意次(包含 0 次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,
但是與"aa.a"和"ab\*a"均不匹配。
思路:
(1)狀態機思路(待深入理解)
詳細資料可以參考:
《正則表達式匹配》
53. 表示數值的字符串
題目:
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-
16"都表示數值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。、
思路:
利用正則表達式實現
54. 字符流中第一個不重復的字符
題目:
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符 "go" 時,第一個只出現一次
的字符是 "g" 。當從該字符流中讀出前六個字符 "google" 時,第一個只出現一次的字符是 "l"。 輸出描述:如果當前字符流
沒有存在出現一次的字符,返回#字符。
思路:
同第 34 題
55. 鏈表中環的入口結點
題目:
一個鏈表中包含環,如何找出環的入口結點?
思路:
首先使用快慢指針的方式我們可以判斷鏈表中是否存在環,當快慢指針相遇時,說明鏈表中存在環。相遇點一定存在於環中,因此我
們可以從使用一個指針從這個點開始向前移動,每移動一個點,環的長度加一,當指針再次回到這個點的時候,指針走了一圈,因此
通過這個方法我們可以得到鏈表中的環的長度,我們將它記為 n 。
然后我們設置兩個指針,首先分別指向頭結點,然后將一個指針先移動 n 步,然后兩個指針再同時移動,當兩個指針相遇時,相遇
點就是環的入口節點。
詳細資料可以參考:
《鏈表中環的入口結點》
《《劍指 offer》——鏈表中環的入口結點》
56. 刪除鏈表中重復的結點
題目:
在一個排序的鏈表中,存在重復的結點,請刪除該鏈表中重復的結點,重復的結點不保留,返回鏈表頭指針。例如,鏈表 1->2->3-
> 3->4->4->5 處理后為 1->2->5
思路:
解決這個問題的第一步是確定刪除的參數。當然這個函數需要輸入待刪除鏈表的頭結點。頭結點可能與后面的結點重復,也就是說頭
結點也可能被刪除,所以在鏈表頭額外添加一個結點。
接下來我們從頭遍歷整個鏈表。如果當前結點的值與下一個結點的值相同,那么它們就是重復的結點,都可以被刪除。為了保證刪除
之后的鏈表仍然是相連的而沒有中間斷開,我們要把當前的前一個結點和后面值比當前結點的值要大的結點相連。我們要確保 prev
要始終與下一個沒有重復的結點連接在一起。
57. 二叉樹的下一個結點
題目:
給定一棵二叉樹和其中的一個結點,如何找出中序遍歷順序的下一個結點?樹中的結點除了有兩個分別指向左右子結點的指針以外,
還有一個指向父節點的指針。
思路:
這個問題我們可以分為三種情況來討論。
第一種情況,當前節點含有右子樹,這種情況下,中序遍歷的下一個節點為該節點右子樹的最左子節點。因此我們只要從右子節點
出發,一直沿着左子節點的指針,就能找到下一個節點。
第二種情況是,當前節點不含有右子樹,並且當前節點為父節點的左子節點,這種情況下中序遍歷的下一個節點為當前節點的父節
點。
第三種情況是,當前節點不含有右子樹,並且當前節點為父節點的右子節點,這種情況下我們沿着父節點一直向上查找,直到找到
一個節點,該節點為父節點的左子節點。這個左子節點的父節點就是中序遍歷的下一個節點。
58. 對稱二叉樹
題目:
請實現一個函數來判斷一棵二叉樹是不是對稱的。如果一棵二叉樹和它的鏡像一樣,那么它是對稱的。
思路:
我們對一顆二叉樹進行前序遍歷的時候,是先訪問左子節點,然后再訪問右子節點。因此我們可以定義一種對稱的前序遍歷的方式
,就是先訪問右子節點,然后再訪問左子節點。通過比較兩種遍歷方式最后的結果是否相同,以此來判斷該二叉樹是否為對稱二叉
樹。
59. 按之字形順序打印二叉樹(待深入理解)
題目:
請實現一個函數按照之字形順序打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右到左的順序打印,即第一行按照
從左到右的順序打印,第二層按照從右到左順序打印,第三行再按照從左到右的順序打印,其他以此類推。
思路:
按之字形順序打印二叉樹需要兩個棧。我們在打印某一行結點時,把下一層的子結點保存到相應的棧里。如果當前打印的是奇數層
,則先保存左子結點再保存右子結點到一個棧里;如果當前打印的是偶數層,則先保存右子結點再保存左子結點到第二個棧里。每
一個棧遍歷完成后進入下一層循環。
詳細資料可以參考:
《按之字形順序打印二叉樹》
60. 從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。
題目:
從上到下按層打印二叉樹,同一層的結點按從左到右的順序打印,每一層打印一行。
思路:
用一個隊列來保存將要打印的結點。為了把二叉樹的每一行單獨打印到一行里,我們需要兩個變量:一個變量表示在當前的層中還
沒有打印的結點數,另一個變量表示下一次結點的數目。
61. 序列化二叉樹(帶深入理解)
題目:
請實現兩個函數,分別用來序列化和反序列化二叉樹。
思路:
數組模擬
62. 二叉搜索樹的第 K 個節點
題目:
給定一顆二叉搜索樹,請找出其中的第 k 小的結點。
思路:
對一顆樹首先進行中序遍歷,在遍歷的同時記錄已經遍歷的節點數,當遍歷到第 k 個節點時,這個節點即為第 k 大的節點。
63. 數據流中的中位數(待深入理解)
題目:
如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那么中位數就是所有值排序之后位於中間的數值。如果數據
流中讀出偶數個數值,那么中位數就是所有數值排序之后中間兩個數的平均值。
64. 滑動窗口中的最大值(待深入理解)
題目:
給定一個數組和滑動窗口的大小,找出所有滑動窗口里數值的最大值。例如,如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的
大小 3,那么一共存在 6 個滑動窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有以下
6 個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2
,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
思路:
使用隊列的方式模擬
65. 矩陣中的路徑(待深入理解)
題目:
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每
一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子
。例如 a b c e s f c s a d e e 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的
第一個字符 b 占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。
66. 機器人的運動范圍(待深入理解)
題目:
地上有一個 m 行和 n 列的方格。一個機器人從坐標 0,0 的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能
進入行坐標和列坐標的數位之和大於 k 的格子。 例如,當 k 為 18 時,機器人能夠進入方格(35,37),因為 3+5+3+7 = 18。但是
,它不能進入方格(35,38),因為 3+5+3+8 = 19。請問該機器人能夠達到多少個格子?
劍指 offer 相關資料可以參考:
推薦
最后如果文章和筆記能帶您一絲幫助或者啟發,請不要吝嗇你的贊和收藏,你的肯定是我前進的最大動力 😁
附筆記鏈接,閱讀往期更多優質文章可移步查看,喜歡的可以給我點贊鼓勵哦:https://github.com/Wscats/articles