本篇文章僅記錄在平時刷題過程當中,讓人眼前一亮的處理思路,因此本篇文章適合算法愛好者閱讀及參考,沒有算法功底的程序猿們,建議不用花費太多的時間在本篇文章node
1,題目描述:給定一個字符串數組,請根據「相同字符集」進行分組(摘自 LeetCode 49)面試
例 :Input: ["eat", "tea", "tan", "ate", "nat", "bat"],算法
Output:[數組
["ate","eat","tea"],
["nat","tan"],
["bat"]
]app
基礎分析:這類問題的常見處理並不難,只須要將每一個字符記錄對應值,內部循環比較,外部循環子串數組便可,時間複雜度 O(K2[子串平均長度] * N * Log(N)2)工具
晉級分析:我在基礎之上,將字符串的和存了下來,將內部循環比較的次數下降,時間複雜度能夠達到 O(K2 * N * Log(N) * Min(N)[表明字符串和相同的次數])性能
高級分析:首先引進一組概念:正整數惟一分解定理,每一個大於1的天然數,要麼自己爲質數,要麼能夠由2個或以上的質數相乘,且組合惟一;上述定理結合問題來看,咱們僅須要將字符串中的每一個字符與質數一一對應,並將字符串全部字符對應的質數乘積保存下來,便可確保字符串的 hash 惟一,時間複雜度 O(K * N * Log(N))測試
Coding :優化
1 func GroupAnagramsOpt(strs []string) [][]string {
2 var res [][]string
3 strMap := make(map[int][]string) 4 prime := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103} 5 6 for _, str := range strs { 7 sum := 1 8 for _, char := range str { 9 sum = prime[char-'a'] * sum 10 } 11 if _, ok := strMap[sum]; !ok { 12 strMap[sum] = []string{str} 13 } else { 14 strMap[sum] = append(strMap[sum], str) 15 } 16 } 17 18 for _, v := range strMap { 19 res = append(res, v) 20 } 21 22 return res 23 }
2,題目描述:給定一個 n,表明 n x n 的棋盤,擺放 n 個皇后,使其相互之間沒法攻擊(同一橫豎斜僅一個旗子,8個皇后問題),返回全部的擺放狀況(摘自 LeetCode 50)編碼
例 :Input: 4
Output: [
[".Q..","...Q","Q...","..Q."],
["..Q.","Q...","...Q",".Q.."]
]
基礎分析:採用遞歸法與回朔法,每次檢查當前位置能否落子,落子後將當前位置所在的橫豎斜 4 個方向所有置位不可落子,最終得出全部可能
晉級分析:對於 n x n 矩陣,不可落子不須要記錄到具體的點,僅須要記錄(行、列、左斜(i+j)、右斜(i-j+n))便可快速判斷當前行,列、位置能否落子,省去記錄和判斷的時間複雜度
高級分析:我本身作的時候,發現一直在糾結重複性問題,因此初版是在將結果放入隊列時,進行重複排查,發現效率較低;再想經過遞歸的返回值,肯定當前位置是否能夠再次落子,以排除相同可能性,發現每次回溯後都必須處理標記數據;再後來發現每次遞歸的行,無需從 0 開始(同行僅有1個旗子),循環時不須要每次都從(0,0)點開始判斷,節省相同擺法的重複性時間消耗。
Coding :
1 // 51. N-Queens 2 func SolveNQueens(n int) [][]string { 3 stepMap := make([][]bool, 3) 4 qMap := make([][]bool, n) 5 for i, _ := range stepMap { 6 stepMap[i] = make([]bool, 2*n) 7 } 8 for i, _ := range qMap { 9 qMap[i] = make([]bool, n) 10 } 11 12 t := &struct{ a [][]string }{a: make([][]string, 0)} 13 SolveNQueensSub(stepMap, qMap, t, n, 0) 14 return t.a 15 } 16 17 func SolveNQueensSub(stepMap [][]bool, qMap [][]bool, res *struct{ a [][]string }, n, i int) { 18 // trans to res 19 if i == n { 20 var vs []string 21 for _, row := range qMap { 22 v := make([]byte, n) 23 for i, r := range row { 24 if r { 25 v[i] = 'Q' 26 } else { 27 v[i] = '.' 28 } 29 } 30 vs = append(vs, string(v)) 31 } 32 res.a = append(res.a, vs) 33 return 34 } 35 36 for j := 0; j < n; j++ { 37 // col + / + \ 38 if !stepMap[0][j] && !stepMap[1][i+j] && !stepMap[2][j-i+n] { 39 qMap[i][j] = true 40 stepMap[0][j] = true 41 stepMap[1][i+j] = true 42 stepMap[2][j-i+n] = true 43 SolveNQueensSub(stepMap, qMap, res, n, i+1) 44 qMap[i][j] = false 45 stepMap[0][j] = false 46 stepMap[1][i+j] = false 47 stepMap[2][j-i+n] = false 48 } 49 } 50 }
3,題目描述:給定兩個字符串 A,B,咱們能夠對 A 字符串進行 3 種操做。
「插入一個字符「
」替換一個字符「
」刪除一個字符「
如何在最少的操做次數後,可使 A 與 B 相等(摘自 LeetCode 72)
例 : input: 「horse「,」ros「
output: 3(1,h -> r,"rorse";2,del 'r',"rose";3,del 'e',"ros")
基礎分析:暴力法進行遞歸循環,每次成功後,記錄次數返回最小次數,理論上是 n^3(這種方法我沒有嘗試,通常 leetcode 上的題目超過 n^2 的解法,每每都會超時,這裏只是提出一種解法)
晉級分析:通常看到「最少」/"最大"/「最小」... 這類字眼,咱們首先腦子中要冒出 4 個字「動態規劃「,這是一個算法工程師的基本素質。這道題,咱們若是用動態規劃的思路去考慮,就會一目瞭然。"horse" 和 "ros" 的匹配,咱們能夠根據動態規劃的思路去進行降級,分別求 "horse" 和 "ro"、"hors" 和 「ros」、 「hors」 和 「ro」 這三種狀況下最小次數,這裏咱們將這三種狀況分別稱爲 A、B、C 下的最小次數,則最終的最小次數與三種狀況的結果息息相關。具體的關係靜下心來推導:
A 狀況:當前最小次數 + 1
B 狀況:當前最小次數 + 1
C 狀況:若是最後一位 'e' 和 's' 相等(這裏只是提出假設),則返回當前最小次數,若是最後一位不相等,則返回當前最小次數 + 1
而咱們須要的最小次數即是上面 3 種狀況最小值。這裏就能夠給出公式
if str1[n] == str2[m] -> map[n][m] = min(map[n-1][m] + 1,map[n][m-1] + 1, map[n][m])
else -> map[n][m] = min(map[n-1][m] + 1,map[n][m-1] + 1, map[n][m] + 1)
當時作題時,根據這樣的邏輯進行編碼後,發如今跑測試用例時會計算少,爲何呢?其實能看到緣由是第一列、第一行是有些特殊的,首先是沒有 m-一、n-1 去比較計算,是能根據前一位判斷當前的最小次數,這一行的邏輯是比較簡單的,簡單就是 "m" 和 "djfioncvohghmnhs" 的匹配,這裏不詳細給出推導,有些算法功底的同窗應該是能夠很快寫出代碼的,直接上代碼
1 func MinDistanceV2(word1 string, word2 string) int { 2 if len(word1) == 0 { 3 return len(word2) 4 } 5 if len(word2) == 0 { 6 return len(word1) 7 } 8 9 n := len(word1) 10 m := len(word2) 11 disMap := make([][]int, n) 12 for i := 0; i < n; i++ { 13 disMap[i] = make([]int, m) 14 } 15 16 // first column 17 isUse := false 18 for i := 0; i < n; i++ { 19 disMap[i][0] = i + 1 20 if word1[i] == word2[0] { 21 isUse = true 22 } 23 if isUse { 24 disMap[i][0]-- 25 } 26 } 27 28 // first row 29 isUse = false 30 for i := 0; i < m; i++ { 31 disMap[0][i] = i + 1 32 if word1[0] == word2[i] { 33 isUse = true 34 } 35 if isUse { 36 disMap[0][i]-- 37 } 38 } 39 40 for i := 1; i < n; i++ { 41 for j := 1; j < m; j++ { 42 dis := Common.MAXINTNUM 43 if word1[i] == word2[j] { 44 if dis > disMap[i-1][j-1] { 45 dis = disMap[i-1][j-1] 46 } 47 } else { 48 if dis > disMap[i-1][j-1]+1 { 49 dis = disMap[i-1][j-1] + 1 50 } 51 } 52 if dis > disMap[i-1][j]+1 { 53 dis = disMap[i-1][j] + 1 54 } 55 if dis > disMap[i][j-1]+1 { 56 dis = disMap[i][j-1] + 1 57 } 58 disMap[i][j] = dis 59 } 60 } 61 return disMap[n-1][m-1] 62 }
高級分析:有些人給出一些比較有趣的解法,相較於上述的解法並無太多的優化,但多了一份巧妙。能夠看到下圖,既然第一行、第一列須要特殊處理,那能夠在每一個字符串前面加一列不存在的字符,初始化是使用 for 循環對第一行、第一列先進行簡單賦值後再進行公式計算。(這裏就不給出代碼了,我我的使用的是第二種方法,之因此將這種方法列爲高級分析,僅僅是解題思路須要適當的巧妙,可讓代碼邏輯看起來簡單不少)
4,題目描述:給定一個排序數組和一個目標值,找出數組中是否含有當前目標(摘自 LeetCode 81)
例 : input: [1,3,6,7,9];3
output: true
基礎分析:根據原題,當時笨方法 for 循環,另外一種業務中經常使用的方法即是二分查找法
晉級分析:以前參加過一個國內知名公司的面試,該公司比較注重算法,幾乎每場面試都有一個算法題等着你,而我此次碰到的即是這道題的迭代,在原題的基礎上將數組進行一次翻滾,將後面一部分(有多是0-n)按順序挪到數組前。在這種條件下,我也是沒有任何慫,萬物都有解決的辦法嘛,大不了就是笨方法,但面試官確定不會對這種笨方法有任何欣賞的點,確定是須要二分查找法,最終也是利用1-3分鐘將思路和代碼寫了出來。
不少人其實這裏糾結的是每次選前半段仍是後半段,其實咱們進行拆分,就能夠很明確的知道選擇哪邊;
1,判斷當前中心是在翻滾點的左邊仍是右邊,其實就是判斷中間點的指是否大於最後一個值,大於則表明在翻滾點左側,小於則表明在翻滾點右測(這第一步每每很重要,但常常有人考慮不到,包括我本身第一次的思路,由於兩種不一樣的結果決定下面咱們判斷的方式)
2,當中點在翻滾點左側時,咱們只須要比較當前目標是否比首個數字大,若是大,則表明須要查前半段,不然就是後半段;相反,當中點在翻滾點右側時,咱們只須要比較當前目標是否比最後一個數字小,若是小,則表明須要查後半段,不然就是前半段
按上述兩點進行循環,便可以 O(logn) 的時間複雜度獲得結果
高級分析:當我看似艱難的將上面的代碼寫好以後(其實想的腦闊疼,改了好幾版)。還未囂張,面試官忽然問若是數組中有重複的數字時,是否須要作什麼修改。我考慮了幾秒,以爲是沒問題,面試官一笑就過了,我覺得個人聰明徵服了面試官大大,面試結束後興起稍微寫了一下代碼在本地跑完以後,才發現有了重複測試案例後,結果是錯誤的。左思右想以爲異常丟人,而且想了很久的解決方法,其實很簡單,在上面的解法以前作一次判斷
1,當前中點是否與首位數組相等,相等則循環拋棄首位數組,start++;反之則是 end--;咱們直接丟棄掉就能夠啦,只是這種方案最壞時間複雜度就降到了 O(n)。上最終版代碼
1 func Search(nums []int, target int) bool { 2 if len(nums) == 0 { 3 return false 4 } 5 6 start := 0 7 end := len(nums) - 1 8 for start <= end { 9 mid := (end-start)/2 + start 10 if nums[mid] == target { 11 return true 12 } 13 14 for start != mid && nums[mid] == nums[start] { 15 start++ 16 if start == mid { 17 start = mid + 1 18 goto OUT 19 } 20 } 21 for end != mid && nums[mid] == nums[end] { 22 end-- 23 if end == mid { 24 end = mid - 1 25 goto OUT 26 } 27 } 28 29 if nums[mid] < nums[end] { 30 if target > nums[mid] { 31 if target == nums[end] { 32 return true 33 } else if target < nums[end] { 34 start = mid + 1 35 end-- 36 } else { 37 end = mid - 1 38 } 39 } else { 40 end = mid - 1 41 } 42 43 } else { 44 if target < nums[mid] { 45 if target == nums[start] { 46 return true 47 } else if target > nums[start] { 48 start++ 49 end = mid - 1 50 } else { 51 start = mid + 1 52 } 53 } else { 54 start = mid + 1 55 } 56 } 57 OUT: 58 } 59 return false 60 }
5,題目描述:給定一個數組,每一個數字表明當前位置的柱子高度,請返回柱子組成所能組成的最大高度(摘自 LeetCode 84)
例 : input: [3,1,5,4,1]
output: 選擇 5,4 -> 4 * 2 = 8
基礎分析:暴力美學,有用的就是好的方法,對任意兩個位置遍歷並每次計算二者之間的最低柱子,進行面積計算得出最大的面積,時間複雜度O(n3),按 leetcode 的尿性,這種複雜度是別想跑過測試用例的
晉級分析:上述的算法中,其實咱們可使用 n 的空間,來記錄以當前爲起點,後面柱子的最低高度,這樣咱們每次就能夠省去找出兩點之間高度的次數,時間複雜度O(n2),其實這種方法已經達到了通常算法喜愛者的水準,可做爲一名刷題者,這種方法只能讓你經過面試,絕對達不到驚豔的地步。給出代碼:
1 func LargestRectangleArea(heights []int) int { 2 if len(heights) == 0 { 3 return 0 4 } 5 6 highs := make([]int, 0, len(heights)) 7 res := 0 8 for i := 0; i < len(heights); i++ { 9 for j := 0; j < len(highs); j++ { 10 if highs[j] > heights[i] { 11 highs[j] = heights[i] 12 } 13 curArx := highs[j] * (i - j + 1) 14 if curArx > res { 15 res = curArx 16 } 17 } 18 if heights[i] > res { 19 res = heights[i] 20 } 21 highs = append(highs, heights[i]) 22 } 23 return res 24 }
高級分析:其實能夠看到,上述的難點在於咱們沒法動態的滑動先後兩端,保證每次滑動都爲最優解,若是可以解決這個問題,咱們就能夠在 O(n)的時間下完成算法。其實換個角度想,向後滑動時,新的柱子高度若是大於等於上一個柱子,那儘管往上加,面積必定是大的;而若是下一個柱子比當前柱子小,則須要將前面全部的高柱子進行一次計算,獲得這一部分的最大面積後,高的那些柱子已經失去意義了(能夠將這一段比做一個區間,A-B 之間存在一些高柱子,A 比 B 小,那 A 前列的柱子和 B 後續的柱子不管如何都不可能再用到中間的高柱子,前列的直接按 A 的高度算,後續的直接按 B 的高度算)
由前日後,又要由後往前計算並排除,直接使用棧工具,能夠給出步驟:
1,對數組進行循環處理,循環 1,2,3 步,直到全部數組處理完畢
1,當前位置高度大於等於棧頂的數值時,直接 Push 到棧裏面
2,當前位置高度小於棧頂數值時,進行 3 步驟循環,當棧爲空或者棧頂數值小於當前位置高度,跳出循環
3,取出棧頂的數值,進行面積計算,公式:當前高度(h) * 兩點距離
4,棧不爲空時,說明還有須要處理的數據,這時候循環 5 步,直到棧爲空
5,取出棧頂的數組,進行面積計算,公式:棧頂高度(h)* (數組長度 - 棧頂下標)
備註:棧中存儲(下標,高度),防止最後一個數據未處理,能夠提早插入一個 (-1,0) 的數據,固然也能夠利用一些邏輯判斷特殊處理
給出代碼:
1 func LargestRectangleAreaOpt(heights []int) int { 2 if len(heights) == 0 { 3 return 0 4 } 5 stack := &Common.Stack{} 6 res := 0 7 8 type node struct { 9 index int 10 num int 11 } 12 stack.Push(&node{index: -1, num: 0}) 13 for i := 0; i < len(heights); i++ { 14 for stack.Size() > 1 { 15 top := stack.Top().(*node) 16 if heights[i] >= top.num { 17 break 18 } 19 20 stack.Pop() 21 nextTop := stack.Top().(*node) 22 area := top.num * (i - nextTop.index - 1) 23 if area > res { 24 res = area 25 } 26 } 27 stack.Push(&node{index: i, num: heights[i]}) 28 } 29 30 for stack.Size() > 1 { 31 top := stack.Pop().(*node) 32 nextTop := stack.Top().(*node) 33 area := top.num * (len(heights) - 1 - nextTop.index) 34 if area > res { 35 res = area 36 } 37 } 38 return res 39 }
6,題目描述:給定一個二維數組,每一個位置給定 0 或 1,返回所能組成最大面積的矩陣(摘自 LeetCode 85)
例 : input: [1,0,1,1,0],[1,1,1,1,0]
output: 選擇 2 * 2 或 1 * 4 = 4
基礎分析:暴力解法,每一個兩個點之間進行判斷,每次遍歷兩點所組成的矩形是否所有爲 1,時間複雜度 O(n2m2)不用想了,除非不考慮性能纔會這樣作
晉級分析:動態規劃,在對二維數據的循環過程當中,分別記錄其向上,向左的連續數量。判斷公式:
if data[i][j] == 1 {left[i] = left[i-1]+1; right[j] = right[j-1] + 1} else {left[i] = 0; right[i] = 0},
這裏能夠將每一個點的向左向右連續統計出來,而對於當前點的最大面積只須要對一個方向進行遍歷求解便可,簡單給出一張圖
一個點從下往上循環計算,即可以的到當前點的最大面積,最終時間複雜度 O(n2m)
高級分析:動態規劃,很厲害的一種算法,徹底是沒有想到的。這裏給出別人的思路。每一個點的面積(這裏其實並非最大面積),爲當前點的最高高度,按最高高度擴展其寬度,算面積,如圖
能夠看到,黃點的面積能夠如圖所示計算,開始的時候我很糾結,這樣並非黃點的最大面積,但我忽略一個重要的問題
按新的面積計算方案,黃點的左右兩側總能找到最大的面積點,因此根據這個思路走下去,利用動態規劃計算新的面積,每一個點分別計算當前點的最高高度、高度對應的左側範圍,高度對應的右側範圍,公式分別爲
if data[i][j] == 1 {height[i] = height[i]-1} else {height = 0}
if data[i][j] == 1 {left[i] = min(left[i-1], continue(左側連續爲1的數量)} else {left[i] = 0}
if data[i][j] == 1 {right[i] = min(right[i-1], continue(右側連續爲1的數量)} else {right[i] = 0}
給出代碼:
1 func MaximalRectangle(matrix [][]byte) int { 2 if len(matrix) == 0 || len(matrix[0]) == 0 { 3 return 0 4 } 5 6 type node struct { 7 height int 8 left int 9 right int 10 } 11 res := 0 12 n := len(matrix) 13 m := len(matrix[0]) 14 nodeMat := make([][]node, n) 15 for i := 0; i < n; i++ { 16 nodeMat[i] = make([]node, m) 17 } 18 19 continueNum := 0 20 // full height and left 21 for i := 0; i < n; i++ { 22 for j := 0; j < m; j++ { 23 if matrix[i][j] == '1' { 24 continueNum++ 25 if i == 0 { 26 nodeMat[i][j].height = 1 27 } else { 28 nodeMat[i][j].height = 1 + nodeMat[i-1][j].height 29 } 30 31 if j == 0 { 32 nodeMat[i][j].left = 1 33 } else { 34 nodeMat[i][j].left = continueNum 35 if i != 0 && nodeMat[i-1][j].left != 0 && (nodeMat[i-1][j].left < nodeMat[i][j].left) { 36 nodeMat[i][j].left = nodeMat[i-1][j].left 37 } 38 } 39 } else { 40 continueNum = 0 41 } 42 } 43 continueNum = 0 44 } 45 46 // full right 47 for i := 0; i < n; i++ { 48 for j := m - 1; j >= 0; j-- { 49 if matrix[i][j] == '1' { 50 continueNum++ 51 if j == m-1 { 52 nodeMat[i][j].right = 1 53 } else { 54 nodeMat[i][j].right = continueNum 55 if i != 0 && nodeMat[i-1][j].right != 0 && (nodeMat[i-1][j].right < nodeMat[i][j].right) { 56 nodeMat[i][j].right = nodeMat[i-1][j].right 57 } 58 } 59 curArx := nodeMat[i][j].height * (nodeMat[i][j].right + nodeMat[i][j].left - 1) 60 if curArx > res { 61 res = curArx 62 } 63 } else { 64 continueNum = 0 65 } 66 } 67 continueNum = 0 68 } 69 70 return res 71 }
用心寫代碼,Refuse copy on coding,Refuse coding by butt.