「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
记录 1 道算法题
最长连续序列 - 并查集
题目的要求是给定一个乱序的数组,然后返回里面最长的连续的数的个数。
比如 [100,4,200,1,3,2],最长的连续的数是 1,2,3,4。所以返回 4。
一
有可能传入空数组,所以在开头做长度判断。
二
里面会有一些重复的数,所以会简单的用一个对象进行去重。
if(set[item]) continue
set[item] = true
...
三
怎么在只遍历一遍的情况下得出答案呢。
这种集合的问题大多都能用并查集进行解决。主要的思路是收集每一个数字,并为他创建一个集合。当遇到与他相邻的数字就收集到自己的集合里。这时候会分为两种情况,1、与只有一个数字的集合进行合并。2、与拥有多个数字的集合进行合并,比如 [1,2] 和 [3,4] 进行合并。所以我们需要记录每一个集合的等级,由等级高吞并等级低。
为了让数字之间产生交集,以作为每次循环内进行合并的判断点,我们需要让相邻的数字产生联系。做法是每次遍历的时候除了生成当前数字的集合,也要生成当前数字的前一个数字的集合,即 prev。
有了这个 prev 之后,我们遍历的时候就可以判断当前的数字是否存在集合,是就进行继承,不是就创建集合。
然后对于 prev 的策略是,如果 prev 存在说明已经有之前循环的当前数字集合存在,那么就要与这轮循环的当前数字集合进行合并。如果不存在则创建 prev 的集合。
四
由于数组的不连续的,所以我们需要建立起数字和并查集集合列表下标的映射。
并查集集合列表下标与遍历时的 i 是一致的。
实现
function longestConsecutive(nums) {
const len = nums.length
if (len === 0) return 0
// 去重
const set = {}
// 映射
const map = {}
// 并查集集合列表
const parent = Array.from(new Array(len), (_, i) => i)
// 等级数组
const size = new Array(len).fill(1)
for(let i = 0; i < len; i++) {
const item = nums[i]
const prev = item - 1
// 去重
if (set[item]) continue
set[item] = true
if (map[item] !== undefined) {
// 继承集合,通过其他数字留下的 prev 联系到该数字
// 所以 +1
const a = parent[i] = find(parent, map[item])
size[a]++
} else {
// 创建集合,指向自己
map[item] = i
}
if (map[prev] !== undefined) {
// 比较集合等级,大合并小,然后规模扩大
const a = parent[i]
const b = find(parent, map[prev])
if (size[b] > size[a]) {
const t = a
a = b
b = t
}
parent[b] = a
size[a] += size[b]
} else {
// 创建集合,指向自己
map[prev] = i
}
}
return Math.max(...size)
}
function find(parent, i) {
if (parent[i] !== i) {
parent[i] = find(parent, parent[i])
}
return parent[i]
}
哈希表解法
思路是先遍历一遍数组收集起来,顺便去重,然后再遍历一遍,对每个数进行再进行循环 +1,并计数,直到没有连续数字就停止,进行下一个数字。
优化的细节是当我们得到一个数字的时候,如果他有前一个数字,那说明他不是这个连续序列的起点,所以跳过。
function longestConsecutive(nums) {
if (nums.length === 0) return 0
const map = {}
// key value 都一样的,进行去重和收集。用 Set 也可以。
for (let i = 0; i < nums.length; i++) {
const a = nums[i]
map[a] = a
}
let count = 1
// 遍历去重过的对象
const keys = Object.keys(map)
for (let i = 0; i < keys.length; i++) {
let a = map[keys[i]]
// 当没有前一个的时候,
// 说明是连续序列的开头,
// 进行递增,计数
if (map[a - 1] == undefined) {
let _count = 0
while (map[a] !== undefined) {
_count++
a++
}
// 比较出历史最大
count = Math.max(count, _count)
}
}
return count
}
结束