目录
题目链接:128. 最长连续序列 - 力扣(LeetCode)
今天是力扣哈希题目的最后一道题啦~~~然后今天更新了一篇利用摄像头就可以作为虚拟键盘的文章,源码已经附上,大家可以看看下面是文章链接感谢支持!!!
上题目
题目链接:128. 最长连续序列 - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例
示例 1:
输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9
提示
0 <= nums.length <= 105
-109 <= nums[i] <= 109
其实我在看见这个题目的时候我是觉得挺简单的,因为我的第一个思路非常的简单,直接进行一个排序即可,然后统计出来最长的序列不就好啦,哈哈,于是我就直接开始写代码了。 但是我又看了一眼题目问我能否使用时间复杂度为O(n)
的算法解决,那么应该还有更快的方法,你能想到吗?
解法一:排序+双指针+剪枝
思路
这段代码的目标是找到一个整数数组中最长的连续子序列的长度。我们一步步来分析代码的实现。
1. 获取数组长度并进行特判
首先,我们获取数组的长度 len
。如果数组的长度小于等于1,那么最长的连续子序列的长度就是数组的长度本身(因为只有一个元素或者没有元素)。
2. 对数组进行排序
为了方便找到连续的子序列,我们先对数组进行排序。排序后,相邻的元素如果差值为1,那么它们就是连续的。
3. 初始化变量
我们定义一个变量 max
,用于存储当前找到的最长连续子序列的长度。初始值为1,因为最短的子序列长度至少为1。
4. 遍历数组并寻找最长连续子序列
- 我们使用一个
for
循环从数组的第一个元素开始遍历,直到倒数第二个元素。 - 在每次循环中,我们首先检查剩余的元素是否足够长。如果剩余的元素长度小于当前找到的最长子序列长度
max
,那么直接返回max
。 - 我们定义两个指针
low
和fast
,分别指向当前子序列的起始位置和下一个位置。 - 使用
while
循环来寻找当前起始位置i
开始的最长连续子序列。- 如果遇到相同的元素,我们跳过(去重)。
- 如果两个元素的差值为1,说明它们是连续的,我们增加当前子序列的长度
curMax
,并移动两个指针。 - 如果两个元素不连续,退出
while
循环。
- 更新
max
,如果当前子序列的长度curMax
大于max
,则更新max
。
5. 返回结果
最后,遍历完数组后,返回找到的最长连续子序列的长度 max
。
总结
这段代码通过排序和双指针法,遍历数组并寻找最长的连续子序列。排序保证了我们只需检查相邻元素是否连续,双指针法则有效地找到每个起始位置的最长连续子序列。通过这种方法,我们可以在 的时间复杂度内解决问题。
整体代码
class Solution {
public int longestConsecutive(int[] nums) {
// 获取数组的长度
int len = nums.length;
// 长度特判
if (len <= 1){
return len;
}
// 直接先进行排序
Arrays.sort(nums);
// 1,2,3,4,100,200
// 定义max用于存储最长的子序列
int max = 1;
for (int i = 0; i < len - 1; i++) {
// 如果剩余的长度已经没有max的长度还长,那么剩下的就不用比较了
if (len - i < max){
return max;
}
// 定义当前序列的最长长度
int curMax = 1;
// 定义慢指针
int low = i;
// 定义快指针
int fast = i + 1;
// 进入循环
while (fast < len){
// 去重,如果遇到相同的元素就直接跳过
if (nums[fast] - nums[low] == 0){
fast++;
low++;
continue;
}
// 如果前后相差1说明是连续子序列
if (nums[fast] - nums[low] == 1){
curMax++;
fast++;
low++;
}else {
// 不是直接结束循环
break;
}
}
// 如果当前的最长子序列大于全部的最长子序列的大小,更新
if (curMax > max){
max = curMax;
}
}
// 全部判断完毕,返回
return max;
}
}
怎么说呢,如果不是有一个剪枝的操作(如果剩余的长度已经没有max的长度还长,那么剩下的就不用比较了),这段代码仍然会超时,所以按理来说还有更好的方式来解决这个问题。
超级优化
基于上一种解法的思路,进行代码大改如下
class Solution {
public int longestConsecutive(int[] nums) {
//0 1 2 3 6 7
//第一种方法直接暴力
if (nums.length == 1){
return 1;
}
if (nums.length == 0){
return 0;
}
//对数组进行排序
Arrays.sort(nums);
notSeem(nums);
int len = 1;
int maxLen = 0;
for (int i = 1; i < nums.length ; i++) {
//0 1 1 2也算是0 1 2 三个而不是两个
if (nums[i] == nums[i - 1] + 1){
//这里证明当前索引位置和前一个位置连续
len++;
}else {
//断了,不连续了
if (len > maxLen){
maxLen = len;
}
//然后清空长度临时存储len
len = 1;
}
}
if (len > maxLen){
maxLen = len;
}
return maxLen;
}
private static int[] notSeem(int[] nums) {
//这里进行去重操作
// 1 2 2 3
// i j
int left = 0;
int right = 1;
while (right < nums.length){
if (nums[left] == nums[right]){
right++;
continue;
}
nums[++left] = nums[right++];
}
int[] ints = new int[left + 1];
for (int i = 0; i <= left; i++) {
ints[i] = nums[i];
}
return ints;
}
}
快不快老铁们 !!!!!!!!!!!!!!!!黑子说话!!!!!!!!!!!!
解法二:哈希表
我们这里其实有很多的寻找的过程,或者说基本上都是一个寻找的过程,那么其实哈希表的用武之地就已经出来了。并且我们这里采用set集合作为哈希表,利用set集合的特性直接实现了去重的效果一举两得。
乐色思路
-
使用哈希集合去重:
- 首先,将数组中的所有元素存入一个哈希集合
set
中,这样可以自动去除重复的元素。哈希集合的特性是每个元素都是唯一的。
- 首先,将数组中的所有元素存入一个哈希集合
-
初始化最大长度:
- 定义一个变量
max
来记录最长连续序列的长度。
- 定义一个变量
-
遍历集合中的每一个元素:
- 对集合中的每一个元素
num
进行处理,尝试找到以这个元素为中心的最长连续序列。
- 对集合中的每一个元素
-
向左右扩展找到连续序列:
- 对于每个
num
,分别向左和向右扩展,寻找连续的数字。 - 定义两个变量
curLMax
和curRMax
来记录当前元素num
左侧和右侧的最长连续长度。 - 使用两个循环:
- 第一个循环向左查找,即
curLeft
从num
开始,逐步减小,直到不再有连续的数字为止。 - 第二个循环向右查找,即
curRight
从num
开始,逐步增加,直到不再有连续的数字为止。
- 第一个循环向左查找,即
- 对于每个
-
更新最大长度:
- 如果当前计算出来的连续序列长度
curLMax + curRMax + 1
比max
大,则更新max
。
- 如果当前计算出来的连续序列长度
-
返回结果:
- 最终返回
max
,即数组中的最长连续序列的长度。
- 最终返回
public class longestConsecutive {
public int longestConsecutive(int[] nums) {
// 这里采用哈希表的方式来解决一下
// 由于需要一个去重的效果,所以这里采用set集合
Set<Integer> set = new HashSet<>();
// 将数组中的内容存入set,且存入的过程中便已经去重(set集合的特性)
for (int num : nums) {
set.add(num);
}
// 定义max
int max = 0;
// 进入判断环节
for (Integer num : set) {
int curLMax = 0;
int curRMax = 0;
int curLeft = num;
int curRight = num;
// 往左找连续的
while (set.contains(curLeft - 1)){
curLMax++;
curLeft--;
}
// 往右找连续的
while (set.contains(curRight + 1)){
curRMax++;
curRight++;
}
// 1,2,3,4,100,200
if (max < curLMax + curRMax + 1){
max = curLMax + curRMax + 1;
}
}
return max;
}
}
给你们看看其中一个用例的夸张程度。经过我的IDEA卡了九九八十一天之后,这个数组的长度高达呃算不出来
代码过长你受得了吗???????
但是难不到我,我直接粘贴到word里,直接搜索逗号有多少个然后最后再+1不就好了,但是我现在的文本框也已经卡了十分钟了。最后的结果是:
这里99999是它根本显示不完结果太多了,好吧,逆天。
那么这段代码在到力扣上运行的时候会被他的第69个测试用例卡主,直接超时,但是我**都用哈希表了还会超时,干集贸啊!!!!!???不理解,然后我看了一下官方的操作,他的思路不是找到一个数就直接往两边数,而是只找一段数列的第一个数(也就是不能有比这个数小1的数)作为开头直接开始找,这样的话就只需要循环这种数的数量的次数即可,而不是循环set集合的长度的次数。
那么下面修改好的思路就来了
高级思路
-
使用哈希集合去重:
- 首先,将数组中的所有元素存入一个哈希集合
set
中,这样可以自动去除重复的元素。
- 首先,将数组中的所有元素存入一个哈希集合
-
初始化最大长度:
- 定义一个变量
max
来记录最长连续序列的长度。
- 定义一个变量
-
遍历集合中的每一个元素:
- 对集合中的每一个元素
num
进行处理,尝试找到以这个元素为起点的最长连续序列。
- 对集合中的每一个元素
-
优化:只从可能的起点开始查找:
- 这里的关键优化点在于,只在当前元素
num
的左边不存在连续元素时,才开始查找。也就是说,只有当num - 1
不存在于集合中时,才从num
开始扩展连续序列。这可以避免在中间状态的元素上重复查找,减少了不必要的计算。
- 这里的关键优化点在于,只在当前元素
-
向右扩展找到连续序列:
- 对于每个可能的起点
num
,从num
开始,向右查找连续的数字。 - 使用一个循环向右查找,直到不再有连续的数字为止。
- 对于每个可能的起点
-
更新最大长度:
- 如果当前计算出来的连续序列长度
curMax
比max
大,则更新max
。
- 如果当前计算出来的连续序列长度
-
返回结果:
- 最终返回
max
,即数组中的最长连续序列的长度。
- 最终返回
class Solution {
public int longestConsecutive(int[] nums) {
// 这里采用哈希表的方式来解决一下
// 由于需要一个去重的效果,所以这里采用set集合
Set<Integer> set = new HashSet<>();
// 将数组中的内容存入set,且存入的过程中便已经去重(set集合的特性)
for (int num : nums) {
set.add(num);
}
// 定义max
int max = 0;
// 进入判断环节
for (Integer num : set) {
// 我们只要右边存在连续的,不要左边存在连续的,这样可以节省很多寻找时间
// 因为左边不存在连续的地方很少,而处于中间状态的很多
if (!set.contains(num - 1)){
// 那么现在的数就是只存在右边是连续的数了
int curMax = 1;
int curNum = num;
while(set.contains(curNum + 1)){
curMax++;
curNum++;
}
if (max < curMax){
max = curMax;
}
}
}
return max;
}
}
这段代码的优化之处在于减少了不必要的计算。具体来说:
- 在原始算法中,对于每一个元素,都会向左右两边扩展查找连续序列,这样会导致大量的重复计算,尤其是在数组中存在很多连续元素时。
- 在优化后的算法中,通过
if (!set.contains(num - 1))
这一判断,仅在当前元素的左边不存在连续元素时才开始查找。这样可以确保每个连续序列只会被计算一次,避免了重复计算,从而提高了效率。
就这个结果而言已经是很不错了,但是还是没有前面方法的
总结
一个算法题很多思路,很多方法,而且我们真的要学会研究自己的好方法,官方的方法即使优化好也才20+ms,但是我们自己的方式可以达到10+ms几乎一倍的性能提升就给硬件带去了非常宽泛的选择空间,这就是软件的魅力。
如果大家喜欢,点个关注点个赞支持一下吧~~~~