【题目】求数组中的最长连续序列的长度,序列中每个值的索引不要求连续,但值时连续的。
【例如】数组data={9, 1, 8, 3, 2, 4},最长连续序列为{1, 2, 3, 4},返回4。
【所属范围】动态规划
【解】本题使用一个HashMap存储中间状态,key为数组元素值,value为该值所在的连续序列的长度。遍历数组时,利用HashMap可以判断当前元素 data[i] 是否已经出现过,如果出现过了,就不作处理,因为它不会影响已有的连续序列的发生扩张。如果没有出现过,就把它当做是长度为1的连续序列加入到map中。
接下来,我们判断 data[i]-1 在不在map中,如果在的话,说明可以合并 data[i]-1 所在的序列和 data[i] 所在的序列。由于 data[i] 是第一次出现,所以data[i]-1所在的连续序列必然不包含 data[i],那么 data[i]-1 所在的序列必然是从 data[i]-1 开始向左延伸的,现在我们可以找到 data[i]-1 所在连续序列的最左位置记为left。同样的道理,data[i] 所在的子序列必然是从 data[i] 开始向右延伸,所以可以找到该序列的最右位置记为right,现在 data[i-1]和 data[i] 将两个子序列连接起来了,合并后的连续序列的最左端为left,最右端为right,合并后的序列长度为 right-left+1,然后更新 left 和 right 在map中的value,也就是它们所在连续序列的长度。由于 left 和 right 之间的值不会影响该连续序列的扩张,因此,不需要更新它们在map中的value,只用作是否存在过的依据。
同理,对于data[i]和data[i+1]也可以按照上述方式进行合并序列。
在计算序列长度的时候,我们可以更新最大长度。最后返回这个最大长度。
【代码】
public int longestConsecutive(int[] data){
if(data == null || data.length == 0){
return 0;
}
//使用hash表,key存储了当前的元素,value存储了当前元素所在的连续序列的长度
Map<Integer,Integer> map = new HashMap<>();
int max = 1;
for(int i = 0; i < data.length; ++i){
//已经出现过的元素不再处理
if(!map.containsKey(data[i])){
//首先将它作为一个长度为1的连续序列,加入到map中
map.put(data[i],1);
//然后看data[i]-1是否在map中
if(map.containsKey(data[i]-1)){
//如果在map中,表示data[i]-1所在的连续序列可以和data[i]合并,并返回合并后的连续序列的长度
//并且,由于data[i]没有出现过,所以data[i]-1所在连续序列中不包含data[i],data[i]-1所在序列是从右向左延伸的
max = Math.max(max,merge(map,data[i]-1,data[i]));
}
//data[i]+1的处理也是类似
if(map.containsKey(data[i]+1)){
max = Math.max(max,merge(map,data[i],data[i]+1));
}
}
}
return max;
}
private int merge(Map<Integer,Integer> map, int less, int more){
//由于less所在的连续序列中肯定不包含more,所有可以计算得到它连续序列的起始位置
int left = less - map.get(less) + 1;
//more所在的连续序列也不会包含less
int right = more + map.get(more) - 1;
int length = right - left + 1;
//将连续序列的首尾各自加入到map中,也就是说map中只会更新一段连续序列的首和尾,
//如果遇到某个数把两个序列连接起来了,那么首和尾的value值都会被更新
map.put(left,length);
map.put(right,length);
return length;
}