diff --git a/README.md b/README.md index d20cf72..4cf622d 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ My LeetCode solutions with Chinese explanation. 我的LeetCode中文题解。 | 124 |[Binary Tree Maximum Path Sum](https://leetcode.com/problems/binary-tree-maximum-path-sum/)|[C++](solutions/124.%20Binary%20Tree%20Maximum%20Path%20Sum.md)|Hard| | | 125 |[Valid Palindrome](https://leetcode.com/problems/valid-palindrome)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/125.%20Valid%20Palindrome.md)|Easy| | | 127 |[Word Ladder](https://leetcode.com/problems/word-ladder/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/127.%20Word%20Ladder.md)|Medium| | +| 128 |[Longest Consecutive Sequence](https://leetcode.com/problems/longest-consecutive-sequence/)|[C++](solutions/128.%20Longest%20Consecutive%20Sequence.md)|Hard| | | 129 |[Sum Root to Leaf Numbers](https://leetcode.com/problems/sum-root-to-leaf-numbers/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/129.%20Sum%20Root%20to%20Leaf%20Numbers.md)|Medium| | | 130 |[Surrounded Regions](https://leetcode.com/problems/surrounded-regions/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/130.%20Surrounded%20Regions.md)|Medium| | | 131 |[Palindrome Partitioning](https://leetcode.com/problems/palindrome-partitioning/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/131.%20Palindrome%20Partitioning.md)|Medium| | diff --git a/solutions/128. Longest Consecutive Sequence.md b/solutions/128. Longest Consecutive Sequence.md new file mode 100644 index 0000000..157395e --- /dev/null +++ b/solutions/128. Longest Consecutive Sequence.md @@ -0,0 +1,86 @@ +# [128. Longest Consecutive Sequence](https://leetcode.com/problems/longest-consecutive-sequence/) + +# 思路 + +给定一个无序的数组,找出最长的可组成连续序列的元素的个数,要求时间复杂度为O(n)。 + +## 思路一 + +看看暴力法怎样解此题:遍历一遍数组,对每个数num,我们假设num就是某个连续范围的最小值,所以我们需要判断num+1、num+2...是否在数组中。可见这样的时间复杂度为O(n^2)。 + +我们可以对暴力法优化一下: +* 由于要经常判断某个元素是否在数组中,所以我们可以事先用一个hash存放每个元素,查找复杂度为O(1); +* 对于某个元素num,如果 num-1 也在数组中,直接跳过就可以了,因为我们处理 num-1 时就会处理到num、num+1...。即如果num是某个候选范围的起点时(即num-1不在数组中),我们才不断判断num+1、num+2...是否在数组中。 + +虽然有两层循环,但是每个范围只会被遍历一次,所以总的复杂度为O(n) + +## 思路二 + +这题其实可以看做是区间合并的问题,可以采取类似并查集的思路。我们用一个名为mp的hashmap记录以这个数为端点(可能左也可能右端点)的连续范围的长度,例如果`mp[4] = 2`,说明存在一个以4为左端点的长度为2的连续范围(即4、5),或者存在一个以4为右端点的长度为2的连续范围(即3、4)。但是值得注意的是,如果数num不在某个范围的端点,那么`mp[num]`的值没有意义,我们也不会用它。 + +那么如果我们遍历到了数num,该怎样求得`mp[num]`呢。 +* 首先如果之前已经遇到过num了,那直接跳过即可; +* 否则我们可以查看num-1和num+1是否之前出现过: + * 如果出现过num-1,那么`mp[num-1]`一定表示以num-1为右端点的范围长度(因为num之前没出现过,所以num-1不可能是左端点,下同); + * 如果出现过num+1,那么`mp[num+1]`一定表示以num+1为左端点的范围长度; + + 所以num可把左右两个范围连成一个大小为`mp[num-1] + mp[num+1] + 1`的范围,因此需要更新该大范围的左端点的值`mp[num - mp[num-1]]`和右端点的值`mp[num + mp[num+1]]`。至于`mp[num]`等于什么无关紧要,因为我们不需要用除了端点之外的值,所以我们随便赋一个值给`mp[num]`就行了,仅仅表明已经出现num了。 + +本思路相比于思路一的优势在于无需先将所有元素用一个hash存着,所以适合数组动态增长的情况。 + +只需要遍历一遍数组,所以总的时间复杂度为O(n) + +# C++ +## 思路一 +``` C++ +class Solution { +public: + int longestConsecutive(vector& nums) { + unordered_setnum_set(nums.begin(), nums.end()); + int n = num_set.size(); + if(n <= 1) return n; + + int res = 1; + for(int num: num_set){ + if(!num_set.count(num - 1)){ // 如果num是起点 + int cur_res = 1; + while(num_set.count(num + cur_res)) cur_res++; + res = max(cur_res, res); + } + } + return res; + } +}; +``` + +## 思路二 +``` C++ +class Solution { +public: + int longestConsecutive(vector& nums) { + //mp[num] = i 表示以num为端点(可能左也可能右端点)的连续范围的长度 + unordered_mapmp; + int n = nums.size(); + if(n <= 1) return n; + + int res = 1; + for(int num: nums){ + if(mp.count(num)) continue; // 这表明num已经出现过 + + // 如果 mp.count(num-1) == 1 说明以num-1为右端点的连续范围长度为mp[num-1] + int l = mp.count(num - 1) ? mp[num - 1] : 0; + // 如果 mp.count(num+1) == 1 说明以num+1为左端点的连续范围长度为mp[num-1] + int r = mp.count(num + 1) ? mp[num + 1] : 0; + + int total = r + l + 1; // 左右两个连续范围组成一个大的连续范围 + res = max(res, total); + + mp[num] = -1; // 这里可以赋成任意值, 因为我们不会用除了端点之外的值 + + mp[num - l] = total; // 更新大范围的左端点的值 + mp[num + r] = total; // 更新大范围的右端点的值 + } + return res; + } +}; +``` \ No newline at end of file