Leetcode 565 & 240 题解

栏目: 编程工具 · 发布时间: 6年前

内容简介:近期刷题有一个新的体会,那就是以leetcode 565 为例,这题大意如下:一个长度为 n 的整形数组 A ,每个数的范围都在 0 到 n-1 以内,假设从数组的某个索引 i 开始,依次执行 A[i] 求值操作,将得到的数加入到集合 S 中,直到集合 S 出现重复元素为止,即中止运算。例如数组 [5,4,0,3,1,6,2] ,我们从 0 开始,依次执行求值操作,有:在上个例子中我们的 S 集合为 {5,6,2,0}。现在给定一个数组,我们要求出这个集合的最长长度。

近期刷题有一个新的体会,那就是 不要想着 pass 就完事了,得想办法将自己的运行时间提速

以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 相同时则终止,最后取最大值即可,相应代码如下

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
}

复制代码

这么写代码的话虽然也通过了,但是看了下耗时还是很不友好,居然是千毫秒级别

Leetcode 565 & 240 题解

后面仔细想想,还是拿最初的数组 [5,4,0,3,1,6,2] 做例子,从索引 0 出发得到的集合为 [5,6,2,0] ,然而本质上如果从索引值为 2 5 或 6 出发的话得到的也是这个集合,因为它们始终凑成一个环。既然如此,那么我们可以**设置一个布尔数组,判断当前值如果之前已经出现在集合 S 内就不需要再重复计算,具体代码如下

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 毫秒

Leetcode 565 & 240 题解

类似的操作还有 leetcode 240: 二维有序数组排序 ,题目大意是给定一个 m*n 的二维数组,从左到右以及从上到下都是有序的,如下面所示:

[
  [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 则为 false

首先最直观的思维就是遍历矩阵的每一行,判断当前要查找的值是否在当前行第 0 个元素和最后一个元素之间,如果是则对当前行的数组做二叉搜索

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 时按列遍历

Leetcode 565 & 240 题解

像我上面最初的想法,本质上只利用了从左到右有序这个性质,并没有充分利用从上到下有序这个性质。 从这个思维点出发,怎么样才能让这两个性质都一起用起来,从而加快速度?

还是拿上面的数组为例,比如我想搜索 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
}
复制代码

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

Leetcode 565 & 240 题解


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法分析

数据结构与算法分析

[美]Mark Allen Weiss / 张怀勇 / 人民邮电出版社 / 2007年 / 49.00元

《数据结构与算法分析:C++描述(第3版)》是数据结构和算法分析的经典教材,书中使用主流的程序设计语言C++作为具体的实现语言。书的内容包括表、栈、队列、树、散列表、优先队列、排序、不相交集算法、图论算法、算法分析、算法设计、摊还分析、查找树算法、k-d树和配对堆等。《数据结构与算法分析:C++描述(第3版)》适合作为计算机相关专业本科生的数据结构课程和研究生算法分析课程的教材。本科生的数据结构课......一起来看看 《数据结构与算法分析》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具