近期刷題有一個新的體會,那就是不要想着 pass 就完事了,得想辦法將本身的運行時間提速golang
以 leetcode 565 爲例,這題大意以下:一個長度爲 n 的整形數組 A ,每一個數的範圍都在 0 到 n-1 之內,假設從數組的某個索引 i 開始,依次執行 A[i] 求值操做,將獲得的數加入到集合 S 中,直到集合 S 出現重複元素爲止,即停止運算。例如數組 [5,4,0,3,1,6,2] ,咱們從 0 開始,依次執行求值操做,有:算法
A[0]=5 -> A[5]=6 ->
A[6]=2 -> A[2]=0
-x-> A[0]=5
複製代碼
在上個例子中咱們的 S 集合爲 {5,6,2,0}。如今給定一個數組,咱們要求出這個集合的最長長度。數組
剛開始看這題我感受很容易啊,直接模擬不就完事了嗎,遍歷數組的每一個值,將索引 i 做爲起點並依次執行 A[i] 操做(計數當前的操做次數),當某次求值操做與起點 i 相同時則終止,最後取最大值便可,相應代碼以下bash
func arrayNesting(nums []int) int {
n := len(nums)
if n <= 1 {
return 1
}
result := 0
for i := 0; i < n; i++ {
s, current, tmpl := i, nums[i], 1
for current != s {
current = nums[current]
tmpl++
}
result = maxValue(result, tmpl)
}
return result
}
func maxValue(a, b int) int {
if a > b {
return a
}
return b
}
複製代碼
這麼寫代碼的話雖然也經過了,可是看了下耗時仍是很不友好,竟然是千毫秒級別app
後面仔細想一想,仍是拿最初的數組 [5,4,0,3,1,6,2] 作例子,從索引 0 出發獲得的集合爲 [5,6,2,0] ,然而本質上若是從索引值爲 2 5 或 6 出發的話獲得的也是這個集合,由於它們始終湊成一個環。既然如此,那麼咱們能夠**設置一個布爾數組,判斷當前值若是以前已經出如今集合 S 內就不須要再重複計算,具體代碼以下ui
func arrayNesting(nums []int) int {
n := len(nums)
if n <= 1 {
return 1
}
visited := []bool{}
for i := 0; i < n; i++ {
visited = append(visited, false)
}
result := 1
for i := 0; i < n; i++ {
s, current, tmpl := i, nums[i], 1
visited[s] = true
if visited[current] {
continue
}
for current != s {
current = nums[current]
visited[current] = true
tmpl++
}
result = maxValue(result, tmpl)
}
return result
}
func maxValue(a, b int) int {
if a > b {
return a
}
return b
}
複製代碼
結果這個運行時間就比以前好多了,直接 20 毫秒spa
相似的操做還有leetcode 240: 二維有序數組排序,題目大意是給定一個 m*n 的二維數組,從左到右以及從上到下都是有序的,以下面所示:code
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30],
]
複製代碼
如今就讓你在這個矩陣內快速查找某個值,找到則返回 true ,找不到則返回 false,上述例子中搜索 5 則爲 true,搜索 20 則爲 falsecdn
首先最直觀的思惟就是遍歷矩陣的每一行,判斷當前要查找的值是否在當前行第 0 個元素和最後一個元素之間,若是是則對當前行的數組作二叉搜索blog
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
if m == 0 {
return false
}
n := len(matrix[0])
if n == 0 {
return false
}
result := false
for _, row := range matrix {
if target >= row[0] && target <= row[n-1] {
result = binarySearch(row, n, target)
}
if result == true {
return true
}
}
return false
}
func binarySearch(row []int, n, target int) bool {
left, right := 0, n-1
for left <= right {
m := left + (right-left)/2
if row[m] == target {
return true
} else if target < row[m] {
right = m - 1
} else {
left = m + 1
}
}
return false
}
複製代碼
這樣的效果雖然能經過,且時間也不算慢,但在此題運行時間的排名裏只超過了 31.25% 的 golang coder,那麼就說明了 O(m*log2(n)) 並非這個算法的最優時間複雜度。另外理論上 m 要小於 n 才能算是比較好的算法,因此實際上我剛纔的解法也忽視了分類討論的狀況:當 m < n 時按行遍歷,當 m > n 時按列遍歷
像我上面最初的想法,本質上只利用了從左到右有序這個性質,並無充分利用從上到下有序這個性質。從這個思惟點出發,怎麼樣才能讓這兩個性質都一塊兒用起來,從而加快速度?
仍是拿上面的數組爲例,好比我想搜索 6 ,我能夠從最右上角的數字 15 出發,由於 15 在最右上角,結合矩陣的性質, 15 左邊的元素都比 15 小(對應行), 15 下邊的元素都比 15 大(對應列)
[
[1, 4, 7, 11, 15]<-
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30],
]
複製代碼
那麼 6 比 15 小,說明 6 不可能與 15 同列,因而咱們數組的範圍變爲:
[
[1, 4, 7, 11]<-
[2, 5, 8, 12]
[3, 6, 9, 16]
[10, 13, 14, 17]
[18, 21, 23, 26]
]
複製代碼
同理 6 比 11 和 7 小,因此數組的搜索範圍爲:
[
[1, 4, 7]<-
[2, 5, 8]
[3, 6, 9]
[10, 13, 14]
[18, 21, 23]
]
[
[1, 4]<-
[2, 5]
[3, 6]
[10, 13]
[18, 21]
]
複製代碼
繼續看最右上角的數,此次 6 比 4 和 5 都大,說明 6 不可能跟 4 或 5 同行,因此搜索範圍又縮小爲:
[
[2, 5]<-
[3, 6]
[10, 13]
[18, 21]
]
[
[3, 6]<-
[10, 13]
[18, 21]
]
複製代碼
如今咱們找到 6 了,能夠看到這個算法的時間複雜度最壞狀況也是 O(m+n) ,比剛纔有了必定的提高
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
if m == 0 {
return false
}
n := len(matrix[0])
if n == 0 {
return false
}
rightUp, currentRow, currentColumn := -1, 0, n-1
for currentRow >= 0 && currentRow < m
&& currentColumn >= 0 && currentColumn < n {
rightUp = matrix[currentRow][currentColumn]
if rightUp == target {
return true
} else if rightUp > target {
currentColumn--
} else {
currentRow++
}
}
return false
}
複製代碼
提交上去後,運行時間很美滿