Leetcode 565 & 240 题解

近期刷题有一个新的体会,那就是不要想着 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
}
复制代码

提交上去后,运行时间很美满

相关文章
相关标签/搜索