这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
最长连续序列(题号128)
题目
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
提示:
0 <= nums.length <= 105-109 <= nums[i] <= 109
链接
解释
这题啊,这题可以说是根本想不到。
上来来个O(n)这谁受得了,想半天也没有思路,首先得去掉排序的操作了,因为没有任何排序算法的时间复杂度是O(n),最强也就是O(nlogn),所以肯定是不能排序的。
那剩下来的就只有while了,利用while一个个去找,但怎么找呢?
indexOf肯定是不行了,这玩意的时间复杂度不是常数型的,具体原理找了半天也找不到,估计找到了时间复杂度也不是O(1)的,果断放弃。
那剩下的就只有Map结构了,先扫一遍数组,之后将每个值都set到Map上,这样以后取的时候就是O(1)的时间复杂度了,到这一步其实就比较简单了,利用while去不断的查找后的数字,累计递增即可。
不过还有一点需要注意,测试用例有可能出现巨大的情况,所以如果每个数字都使用while来查找的话,必然是会超时的,此时就应该剪枝了。
剪枝该怎么剪呢?让我们回到题目本身,它求的是一个连续的最长序列,那么在循环的时候,如果当前值并不是自己所在序列的第一个元素,那就没有必要进行后续的操作了,因为后面会有头节点的while循环,也还是会找到当前元素的。
这样基本上就完事了,在每次while后累计最大值即可,完成for循环即可拿到最终的结果。
自己的答案
无
更好的方法(Map+剪枝)
思路👆说了,👇看看代码:
var longestConsecutive = function(nums) {
const map = new Map()
for (const num of nums) {
map.set(num, true)
}
let maxLength = 0
for (const num of map.keys()) {
if (!map.has(num - 1)) {
let currentLength = 1
let currentValue = num
while (map.has(currentValue + 1)) {
currentValue++
currentLength++
}
maxLength = Math.max(maxLength, currentLength)
}
}
return maxLength
};
代码整体和上面说的一样,利用currentLength来记录当前序列的长度;currentValue来记录当前序列最后一个值的大小,然后持续使用while迭代,直到找不到最后一个值。
这里的时间复杂度计算是这样的:
- 第一个
for循环,O(n) - 第二个
for循环,O(n) while循环,非指数型增长,不计
有多种时间复杂度的情况下,取最大的即可,这里最大的就是O(n)了,所以最后的时间复杂度是O(n)。
更好的方法(Set+剪枝)
用了Map自然也能用Set了,因为Set自己也有has方法,代码几乎不需要进行更改👇:
var longestConsecutive = function(nums) {
const set = new Set(nums)
let maxLength = 0
for (const num of set) {
if (!set.has(num - 1)) {
let currentLength = 1
let currentValue = num
while (set.has(currentValue + 1)) {
currentValue++
currentLength++
}
maxLength = Math.max(maxLength, currentLength)
}
}
return maxLength
};
相比Map来说少了一次for循环,但其实Set内部也是需要处理的,这里忽略不计,重点是这二者内存占用不同,因为Map多存了true这个值,所以内存占用会比Set大很多,二者的差距也就在这个地方。
这两个方法的思路是没有区别的,但就是因为数据结构的选择,导致了内存占用上超过50%的差距。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇