Skip to content

Latest commit

 

History

History

127. Longest Consecutive Sequence

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

这道题定为 hard 级别的原因并非是解决思路有多难想, 而是难在了效率的瓶颈.

要求在 O(n) 的复杂度情况下解决. 要知道, 完整的迭代一次就需要 O(n), 这相当于要求在一次迭代过程中, 计算出最大连续序列的长度来.

比较直接的思路是, 先排个序, 然后完整迭代, 中途记录 max 连击. 这个思路非常简单, 但且不论排序, 那一次完整的迭代就几乎要超标.

这种情况下, 以空间换时间就是最佳的选择. 如果我们在迭代过程中, 尝试记录当前最大的连续序列长度呢? 这几乎是矛盾的, 因为原数组并非有序. 如何可以知道连续序列的存在?

这让我想到了生活中一个真实的用例: 整理书籍. 学生时代, 每当大考之后, 都会清理书籍, 入库或者变卖. 我们是如何分类清理的呢? 遇到一本书, 看其属于哪一类, 然后放在一个固定的位置. 当全部书籍翻过一遍之后, 就会很自然的出现一堆堆的书堆. 每一堆都是同一类书籍. 我们很清楚的看到哪一类的书籍最多. 最高的那个就是啦.

这里也是一样, 如果我们弄一个 hash 表(key 为数组值, value 为连续序列 size), 将数组里每一个元素都放入表中, 然后发现连续, 则进行 +1 操作. 具体来说, 就是:

  • 若 map[i+1] 和 map[i-1] 存在, 那么 i-1, i, i+1 构成一个三元序列, size == 3;
  • 若只有 map[i+1] 或者只有 map[i-1], 那么 size = 2;
  • 若 map[i+1] 和 map[i-1] 都不存在, 那么 size = 1; (自己)

上面三种情况可以写成一个式子:

map[i] = map[i-1] + map[i+1] + 1; // 左边记录的序列长度 + 右边记录的序列长度 + 自己

但当 map[i] 的值更新后, 我们还应该将其最新值(长度)扩展至该连续序列的边界上去. 可是如何扩展呢?

如 map[i] 的左边最远的连续节点是谁? 右边的最远连续节点是谁? 显然左边可以递推至 map[i-1] 那去, 右边同理. map[i-1] 如果等于1, 表示到它这就断了, 如果等于2, 那么表示它的左边还有一个连续节点.

那么这个节点的位置也可以用一个式子表示:

map[i - map[i-1]] // 左边最远节点, 用当前位置 - 左边的长度, 正好获得最左连续节点的坐标.
map[i + map[i+1]] // 右边同理.

所以当 map[i] 的值更新后, 应推广至最左与最右. 即:

map[i - map[i-1]] = map[i + map[i+1]] = map[i] = map[i-1] + map[i+1] + 1;

最后, 由于我们只关心最长连续序列的长度, 所以我们设置一个变量来记录它, 如 max.

max = std::max(max, map[i]);

综合上述几个式子, 可以归并为一句话:

max = std::max(max, map[i] = map[i - map[i-1]] = map[i + map[i+1]] = map[i-1] + map[i+1] + 1);

这句话即为核心了.

由于哈希表 unordered_map 默认值为 0. 所以如果遇到一个新值, 则记 map[i] == 1. 如果 map[i] != 0, 显然这个值我们已经存在于 hash 表中, 可以舍弃, 进行下一次迭代.