近期刷题有一个新的体会,那就是不要想着 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
}
复制代码
提交上去后,运行时间很美满