1暴力解法
1.1我的代码
题目要求时间复杂度为O(n),因此我已经尽可能向哈希法上靠,还是被ds判断为暴力解法
public static int longestConsecutive(int[] nums) {
int result = 0;
//将nums放入集合
Set<Integer> set = new TreeSet<>();
for (int num : nums) {
set.add(num);
}
//添加到treeset后元素已经变为有序
for (Integer i : set) {
//从最小的元素开始遍历
int temp = 1;
for (int j = 1; j < set.size(); j++) {
if(!set.contains(i+j)) {
break;
}
temp++;
}
if(result<temp)
result = temp;
if(result == set.size())
return result;
}
return result;
}
这段代码如何体现暴力解法特性:
- 每个元素都被视为起点:
- 即使某个元素是连续序列的中间元素(如
[1,2,3]中的2),代码仍会以它为起点重新检测后续连续序列
for (Integer i : set) { // 每个元素都被作为起点
int temp = 1;
for (int j = 1; j < set.size(); j++) {
if(!set.contains(i+j)) break;
temp++;
}
// ...
}
- 重复检测序列:
-
对于连续序列
[1,2,3,4]: -
起点1:检测
2,3,4(3次)- 起点4:检测
5(1次) - 起点3:检测
4(1次) - 起点2:检测
3,4(2次)
- 起点4:检测
-
共计算3+2+1+1=7次,而实际序列长度只有4
- 无谓的计算浪费:
-
- 当
[1,2,3]已被检测后,起点2和3的检测都是多余的 - 最坏情况时间复杂度达到O(n²)
- 当
- 未利用有序性优化:
-
- 虽然使用了TreeSet排序,但未能利用"连续序列起点"的特性(起点前无相邻元素)
1.2ds优化代码,摆脱暴力解法
通过优化,可摆脱暴力解法
for (Integer i : set) {
if (!set.contains(i - 1)) { // 确保 i 是起点
int temp = 1;
while (set.contains(i + temp)) {
temp++;
}
result = Math.max(result, temp);
}
}
优化思路:如果集合中存在i-1元素,说明当前元素不是连续序列的起点,没有处理的必要,优化后时间复杂度为O(n)
1.3总结:
暴力解法的特征:
- 穷举所有可能性:对每个可能起点都尝试计算整个连续序列
- 重复计算:多次处理相同数据段
- 高时间复杂度:通常有较高的多项式时间或指数时间复杂度
- 未利用优化条件:没有跳过不必要的计算
本题的核心在于:当前数字只有是连续片段的起点才需对其进行处理,判断是否为起点只需判断是否存在i-1
2.最优解法
public int longestConsecutive(int[] nums) {
// 特殊情况处理
if (nums == null || nums.length == 0) {
return 0;
}
// 创建哈希集合存储所有数字
Set<Integer> numSet = new HashSet<>();
for (int num : nums) {
numSet.add(num);
}
int longestStreak = 0;
for (int num : numSet) {
// 只考虑连续序列的起点(当前数字-1不存在)
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentStreak = 1;
// 探索当前连续序列的长度
while (numSet.contains(currentNum + 1)) {
currentNum++;
currentStreak++;
}
// 更新最长序列长度
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}
该解法与上文优化思路相同,不再赘述