看題目就應該知道是演算法題的思路,這裡不理解為完全為了面試,這樣會很累的,權當做技術的追求吧,我們除了增刪改查,複製貼上,寫服務,封裝程式碼,總要找點有其他動腦子的事情做吧,雖說全域性觀很重要,架構很重要,但是我們總要給自己找個學習的理由吧?
回到正題,我們寫需求之前都會寫個概要設計文件(當然現在很多人已經沒有這習慣,公司也沒有這要求了,但是不妨礙我們的習慣)。演算法也是一樣,知道遠離和實現方式,剩下寫程式碼似乎也並不是很輕鬆的一件事,因為要涵蓋很多種情況,有時候僅僅是一個條件判斷條件寫錯了,就會讓你找好久。但是一切的前提就是,你得知道怎麼實現,通用情況下怎麼實現的,然後才能完善其他的測試用例。
本章節主要介紹的是兩方面:1、抽象問題形象化 2、複雜問題簡單化。理論永遠是簡單的,實踐才是難題。這一張我看的本身就很累,前面已經說過了,時間會比較久。後面三章時間會更久,因為目前為止還沒有涉及到困難級別的題,默哀吧,騷年。
面試題19:二叉樹的映象
leetcode-cn.com/problems/er-cha-sh...
請完成一個函式,輸入一個二叉樹,該函式輸出它的映象。
顯然,優先使用前序遍歷,首先要學會前序遍歷一棵樹
func mirrorTree(root *TreeNode) *TreeNode {
if root == nil {
return nil
}
root.Right, root.Left = root.Left, root.Right
if root.Left != nil {
mirrorTree(root.Left)
}
if root.Right != nil {
mirrorTree(root.Right)
}
return root
}
這題明顯就是讓我們找快感的題左右互換而已。
面試題20:順時針列印矩陣
leetcode-cn.com/problems/shun-shi-...
func spiralOrder(matrix [][]int) []int {
result := make([]int, 0)
if matrix == nil || len(matrix) == 0 {
return nil
}
var row = len(matrix)
var col = len(matrix[0])
if row <= 0 || col <= 0 { //一行或者一列,直接返回遍歷之後的一維陣列
for i := 0; i < row; i++ {
for j := 0; j < col; j++ {
result = append(result, matrix[i][j])
}
}
return result
}
startIndex := 0 //起始新增的位置
for col > startIndex*2 && row > startIndex*2 {
endX := col - startIndex - 1
endY := row - startIndex - 1
//從左到右新增
for i := startIndex; i <= endX; i++ {
result = append(result, matrix[startIndex][i])
}
//從上到下新增
if startIndex < endY {
for i := startIndex + 1; i <= endY; i++ {
result = append(result, matrix[i][endX])
}
}
//從右到左新增
if startIndex < endX && startIndex < endY {
for i := endX - 1; i >= startIndex; i-- {
result = append(result, matrix[endY][i])
}
}
//從下到上新增
if startIndex < endX && startIndex < endY-1 {
for i := endY - 1; i >= startIndex+1; i-- {
result = append(result, matrix[i][startIndex])
}
}
startIndex++
}
return result
}
不要小瞧這道題,看原理我們都知道怎麼實現,但是寫程式碼的難度比理解遠離更困難,重點是,力扣認為這是一道簡單題。用網友的話說,這怕不是對簡單兩個字有什麼誤解。。。
面試題21:包含min函式的棧
leetcode-cn.com/problems/bao-han-m...
定義棧的資料結構,請在該型別中實現一個能夠得到棧的最小元素的 min 函式在該棧中,呼叫 min、push 及 pop 的時間複雜度都是 O(1)。
這是一道設計題,考察的完全是棧和輔助棧的用法,我覺得沒必要去看,本著面試寫程式碼的原則,寫了本地也沒法測試,只能交給leetcode。
面試題22:棧的壓入、彈出序列
leetcode-cn.com/problems/zhan-de-y...
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。
假設壓入棧的所有數字均不相等。例如,序列 {1,2,3,4,5} 是某棧的壓棧序列,序列 {4,5,3,2,1} 是該壓棧序列對應的一個彈出序列,但 {4,3,5,1,2} 就不可能是該壓棧序列的彈出序列。
func validateStackSequences(pushed []int, popped []int) bool {
//1、陣列模擬棧
//indexPopped := 0
//tempPushed := make([]int, 0)
//for i := 0; i < len(pushed); i++ {
// tempPushed = append(tempPushed, pushed[i])
// for len(tempPushed)>0 && tempPushed[len(tempPushed)-1] == popped[indexPopped] { //比較棧頂值是否等於popped的當前值
// tempPushed = tempPushed[:len(tempPushed)-1]
// indexPopped++
// }
//}
//if len(tempPushed) == 0 {
// return true
//}
//return false
//2、棧實現
indexPopped := 0
stack := NewStack()
for i := 0; i < len(pushed); i++ {
stack.Push(pushed[i])
for !stack.IsEmpty() && stack.Peek() == popped[indexPopped] { //比較棧頂值是否等於popped的當前值
stack.Pop()
indexPopped++
}
}
return stack.IsEmpty()
}
面試題23:從上到下列印二叉樹
leetcode-cn.com/problems/cong-shan...
從上到下列印出二叉樹的每個節點,同一層的節點按照從左到右的順序列印。
func levelOrder(root *TreeNode) []int {
// 陣列模擬佇列
result := make([]int, 0)
if root == nil {
return result
}
//層次遍歷,想明白還是很簡單的
level := make([]*TreeNode, 0) //記錄每一層的元素,方便尋找每一層的子節點
level = append(level, root) //首先新增第一層
for len(level) > 0 { //一層一個資料都沒有跳出迴圈
for i := 0; i < len(level); i++ { //遍歷當前層,新增子節點層
result = append(result, level[i].Val)
}
temp := level
for i := 0; i < len(temp); i++ {//模擬雙端佇列,pushFront資料
if temp[i].Left != nil {
level = append(level, temp[i].Left)
}
if temp[i].Right != nil {
level = append(level, temp[i].Right)
}
}
level = level[len(temp):]//popBack資料
}
return result
}
請實現一個函式按照之字形順序列印二叉樹,即第一行按照從左到右的順序列印,第二層按照從右到左的順序列印,第三行再按照從左到右的順序列印,其他行以此類推。
升級後只需要區分層級的奇偶就行,邏輯類似
func levelOrder(root *TreeNode) [][]int {
// 陣列模擬佇列
result := make([][]int, 0)
if root == nil {
return result
}
//層次遍歷,想明白還是很簡單的
level := make([]*TreeNode, 0) //記錄每一層的元素,方便尋找每一層的子節點
level = append(level, root) //首先新增第一層
high := 0
for len(level) > 0 { //一層一個資料都沒有跳出迴圈
data := make([]int, 0)
for i := 0; i < len(level); i++ { //遍歷第一層,新增子節點層
data = append(data, level[i].Val)
}
temp := level
high++
for i := 0; i < len(temp); i++ {
if temp[i].Left != nil {
level = append(level, temp[i].Left)
}
if temp[i].Right != nil {
level = append(level, temp[i].Right)
}
}
level = level[len(data):]
if high%2 == 0 {
for i := 0; i < len(data)/2; i++ {
data[i], data[len(data)-1-i] = data[len(data)-1-i], data[i]
}
}
result = append(result, data)
}
return result
}
面試題24: 二叉搜尋樹的後序遍歷序列
leetcode-cn.com/problems/er-cha-so...
func verifyPostorder(postorder []int) bool {
return verifyPostorderCheck(postorder, 0, len(postorder)-1)
}
func verifyPostorderCheck(postorder []int, left, right int) bool {
if left >= right {
return true
}
leftIndex := left
for postorder[leftIndex] < postorder[right] {
leftIndex++
}
rightIndex := leftIndex
for i := rightIndex; i < right; i++ {
if postorder[i] < postorder[right] {
return false
}
}
//判斷左子樹是不是二叉搜尋樹
leftResult := verifyPostorderCheck(postorder, left, rightIndex-1)
//判斷右子樹是不是二叉搜尋樹
rightResult := verifyPostorderCheck(postorder, rightIndex, right-1)
return leftResult && rightResult
}
面試題25: 二叉樹中和為某一值的路徑
leetcode-cn.com/problems/er-cha-sh...
典型的回溯 遞迴大法
func pathSum(root *TreeNode, target int) (result [][]int) {
cachePath := make([]int, 0)
var pathSumDfs func(root *TreeNode, sum int)
pathSumDfs = func(root *TreeNode, sum int) {
if root == nil {
return
}
sum -= root.Val
cachePath = append(cachePath, root.Val)
defer func() { cachePath = cachePath[:len(cachePath)-1] }()
//判斷是不是葉子節點,並且路徑值等於目標值
if sum == 0 && root.Left == nil && root.Right == nil {
//result = append(result, cachePath)
//需要複製出一個副本,否則就是指標傳遞了
result = append(result, append([]int(nil), cachePath...))
return
}
//不是就繼續遞迴子節點
pathSumDfs(root.Left, sum)
pathSumDfs(root.Right, sum)
}
pathSumDfs(root, target)
return
}
面試題26:複雜連結串列的複製
leetcode-cn.com/problems/er-cha-sh...
請實現 copyRandomList 函式,複製一個複雜連結串列。在複雜連結串列中,每個節點除了有一個 next 指標指向下一個節點,
還有一個 random 指標指向連結串列中的任意節點或者 null。
random 指標:當前節點的隨機指標指向的節點可能還沒建立
// 直接兩次迴圈遍歷 超出時間限制
func copyRandomList(head *Node) *Node {
dummy := &Node{}
tail := dummy
copyNode := head
nextNode := head
for copyNode != nil {
for nextNode != nil {
tail.Next = copyNode
if copyNode.Random == nextNode {
tail.Random = nextNode
break
}
nextNode = nextNode.Next
}
}
return dummy.Next
}
//回溯+雜湊
func copyRandomList(head *Node) *Node {
var clone func(node *Node) *Node
cacheNodes := make(map[*Node]*Node)
clone = func(node *Node) *Node {
if node == nil {
return nil
}
if n, has := cacheNodes[node]; has {
return n
}
cloneNode := &Node{Val: node.Val}
cacheNodes[node] = cloneNode
cloneNode.Next = clone(node.Next)
cloneNode.Random = clone(node.Random)
return cloneNode
}
return clone(head)
}
分治 迭代 + 節點拆分
func copyRandomList(head *Node) *Node {
//1、複製自身節點
cloneNodes := func(head *Node) {
cloneHead := head
for cloneHead != nil {
newNode := &Node{Val: cloneHead.Val, Next: cloneHead.Next}
cloneHead.Next = newNode
cloneHead = newNode.Next
}
}
//2、複製隨機節點
cloneRandom := func(head *Node) {
cloneRandomHead := head
for cloneRandomHead != nil {
randomNode := cloneRandomHead.Next
if cloneRandomHead.Random != nil {
randomNode.Random = cloneRandomHead.Random.Next
}
cloneRandomHead = randomNode.Next
}
}
//3、奇偶拆分連結串列
splitNode := func(head *Node) *Node {
return nil
}
cloneNodes(head)
cloneRandom(head)
return splitNode(head)
}
面試題27: 二叉搜尋樹與雙向連結串列
leetcode-cn.com/problems/er-cha-so...
leetcode沒有提供Go版本的語言實現這道題,所以只能是仿照編譯,本地執行,其他語言自行實現
本題我也沒搞懂,看課本答案才知道怎麼實現的,慚愧,很多寫法不是人家給出Demo我都不知道還可以這麼寫
type Node struct {
Val int
left *Node
right *Node
}
func treeToDoublyList(root *Node) *Node {
var lastNodeInList *Node //已經轉換好的連結串列的最後一個結點
convertNode(root, &lastNodeInList)
headOfList := lastNodeInList //需要返回的頭節點
for headOfList != nil && headOfList.left != nil {
headOfList = headOfList.left
}
return headOfList
}
func convertNode(root *Node, lastNodeInList **Node) {
if root == nil {
return
}
currentNode := root
if currentNode.left != nil {
convertNode(currentNode.left, lastNodeInList)
}
currentNode.left = *lastNodeInList
if *lastNodeInList != nil {
(*lastNodeInList).right = currentNode
}
*lastNodeInList = currentNode
if currentNode.right != nil {
convertNode(currentNode.right, lastNodeInList)
}
}
面試題28:字串的排列
leetcode-cn.com/problems/zi-fu-chu...
func permutation(s string) []string {
result := make([]string, 0)
byteStr := []byte(s)
//fmt.Println(byteStr)
var getStrDfs func(index int)
getStrDfs = func(index int) {
if index == len(byteStr)-1 { //交換到最後一個字元,退出迴圈
//fmt.Println("the end byte", byteStr)
result = append(result, string(byteStr))
}
mapIsChange := make(map[byte]byte) //利用集合做剪枝,去重處理
for i := index; i < len(byteStr); i++ {
if _, ok := mapIsChange[byteStr[i]]; ok {
continue //已經交換過,不再交換
}
mapIsChange[byteStr[i]] = byteStr[i]
byteStr[i], byteStr[index] = byteStr[index], byteStr[i]//交換
getStrDfs(index + 1)
byteStr[i], byteStr[index] = byteStr[index], byteStr[i]//恢復交換
}
}
getStrDfs(0)
return result
}
總體來說,樹的考察比較多,對於樹本身就不是常用的搬磚農民工來說,就是個劣勢。雪上加霜的是,跟樹相關的,必定是遞迴。我們平時寫程式碼儘量規避遞迴的程式碼規範來說,這又是一個挑戰。總而言之,這一章確實不容易,建議知道原理的情況下寫程式碼,然後能白板寫出來才算真的懂了,否則就是浪費時間,因為不會寫程式碼實現的題,你其實還是不會。
本作品採用《CC 協議》,轉載必須註明作者和本文連結