https://leetcode.com/problems/longest-consecutive-sequence/
Given an unsorted array of integers, find the length of the longest consecutive elements sequence.
For example,
Given [100, 4, 200, 1, 3, 2]
,
The longest consecutive elements sequence is [1, 2, 3, 4]
. Return its length: 4
.
Your algorithm should run in O(n) complexity.
解题思路:
这道题的难度在于用O(n)的时间。原来的数组没有排序,如果先排序,必然要花费O(nlogn)的时间,不符合要求。根据经验和直觉,可能是用O(n)的时间去遍历数组,然后借助HashMap,花费O(1)的时间去找某些元素在不在数组中。
按照这样的思路往下想,对于num[i]去寻找它的左侧和右侧邻接的元素,直到在num中不存在,这时更新长度。因为每个数字只能存在于唯一的一个consecutive sequence里,否则这两个sequence就是相连的。
所以,这里拓展过的元素就完全可以从set中删掉了。后面再遍历到的num[j]就可以直接跳过,因为它属于的最长consecutive sequence前面已经找出了。
这样,整体的时间复杂度一定是O(n)。
下面的代码使用了HashMap,同时对num中出现的数字计数。
public class Solution { public int longestConsecutive(int[] num) { Map<Integer, Integer> numMap = new HashMap<Integer, Integer>(); for(int i = 0; i < num.length; i++){ if(numMap.containsKey(num[i])){ numMap.put(num[i], numMap.get(num[i]) + 1); }else{ numMap.put(num[i], 1); } } int longestLength = 0; for(int i = 0; i < num.length; i++){ int currentLength = 0; int next = num[i]; while(numMap.containsKey(next) && numMap.get(next) > 0){ numMap.put(next, numMap.get(next) - 1); currentLength++; next++; } int previous = num[i] - 1; while(numMap.containsKey(previous) && numMap.get(previous) > 0){ numMap.put(previous, numMap.get(previous) - 1); currentLength++; previous--; } longestLength = Math.max(longestLength, currentLength); } return longestLength; } }
后来发现根本没必要,因为一个数字即使出现多次,还是只能出现在一个consecutive sequence里,换成set。
public class Solution { public int longestConsecutive(int[] num) { Set<Integer> numSet = new HashSet<Integer>(); for(int i = 0; i < num.length; i++){ if(!numSet.contains(num[i])){ numSet.add(num[i]); } } int longestLength = 0; for(int i = 0; i < num.length; i++){ int currentLength = 0; int next = num[i]; while(numSet.contains(next)){ numSet.remove(next); currentLength++; next++; } int previous = num[i] - 1; while(numSet.contains(previous)){ numSet.remove(previous); currentLength++; previous--; } longestLength = Math.max(longestLength, currentLength); } return longestLength; } }
再优化,进入循环前就直接判断num[i]在不在set里,不在直接跳过继续!
public class Solution { public int longestConsecutive(int[] num) { Set<Integer> numSet = new HashSet<Integer>(); for(int i = 0; i < num.length; i++){ numSet.add(num[i]); } int longestLength = 0; for(int i = 0; i < num.length; i++){ if(!numSet.contains(num[i])){ continue; } int currentLength = 0; int next = num[i]; while(numSet.contains(next)){ numSet.remove(next); currentLength++; next++; } int previous = num[i] - 1; while(numSet.contains(previous)){ numSet.remove(previous); currentLength++; previous--; } longestLength = Math.max(longestLength, currentLength); } return longestLength; } }
特别警醒的是,开始我的for改成这样,这样如果num[i]不在set里循环就直接退出了,而不是continue。犯了如此基本的错误!
for(int i = 0; i < num.length && numSet.contains(num[i]); i++){ int currentLength = 0; int next = num[i]; while(numSet.contains(next)){ numSet.remove(next); currentLength++; next++; } int previous = num[i] - 1; while(numSet.contains(previous)){ numSet.remove(previous); currentLength++; previous--; } longestLength = Math.max(longestLength, currentLength); }
这就是我的思路。从O(nlogn)到O(n)一般都是这么几个方法。这里使用了空间换时间的思路。