

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:这里给大家带来一道群友面试字节的手撕题,感觉很不错,我一开始也没想到更好的方法(因为博主是个刚开始刷算法的小白),因此拿出来和大家分享一下,一起交流。
摘要:
摘要:本文分享了一道字节跳动的面试题------寻找未排序整数数组中最长连续序列的长度。
题目要求时间复杂度为O(n),传统排序法(O(nlogn))不符合要求。
核心解法是使用HashSet去重并实现O(1)查询,通过判断当前数字是否为序列起点(即num-1不存在),然后向后延伸统计序列长度。相比排序法(时间换空间),哈希表解法以空间换时间,满足O(n)复杂度要求。
文章提供了两种实现代码,并解释了哈希表快速查找的原理。
题目背景:119.最长连续序列
给定一个未排序的整数数组
nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。示例 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 <= 104-109 <= nums[i] <= 109进阶: 可以设计并实现时间复杂度为
O(n)的解决方案吗?
这里面试官要求的就是时间复杂度为O(n)
题目解析:
首先拿到这个题目
我们想到的可能是先给数组排序,Arrays.sort(); 遍历一遍之后,统计当前连续序列的长度,并更新最大值,但是这样并不符合面试官的要求,这种方法,排序的时间复杂度为O(NlogN),遍历是O(N),总复杂度为O(NlogN),所以这个方法并不适用。
那到这里,思路就比较乱了,唯一一个容易想到的解题思路竟然不符合要求,这时友善的面试官提醒了一下,用哈希表。
那我们来分析一下,用哈希表能解决吗,以及怎么使用哈希表。
分析一下题目,找出最长连续的序列,这类问题通常要求时间复杂度为O(N),然后我们用的排序法的时间复杂度是不符合要求的,那就代表此时的数组是无序的状态,此时问题就变成了要在无序数组中,快速判断连续的序列,也就是快速判断某个数字的邻居是否存在。
想要在数组中快速找到某个元素,同时还不能依靠数组的下标 ,因为数组的元素并不是有序的,也就是时间复杂度为O(1),这就已经指向哈希表了。
如果不用哈希表的话,就要对每个数字都遍历一次去寻找邻居,显然时间复杂度为O(N2) ,同时题目还有第二层逻辑,看给定的第二个示例,数组里面有重复元素,看到重复这两个字,我们瞬间就想到了HsahSet的自动去重。
大体的方向我们确定了,但是还有一个问题,我们要找最长的连续序列,那这个起点我们怎么找,或者说怎么确定
我们之间从头开始判断,判断这个元素-1在不在哈希表中,如果在,说明当前元素就不是起始元素,(当前元素-1)才是起始元素,然后接着判断,这样我们就确定了第一个起始序列元素,然后我们找后面的元素,就是**(起始元素+1)因为是连续的,不断地+1,判断哈希表中有没有这个元素,同时每次循环的时候都要记录当前序列的长度,从而最后通过比较找到最长的序列。**
关于如何不依赖数组的下标和数组有序:
核心原理:哈希函数
普通的数组查找,就像是在一排没有编号的格子里找东西,你必须从第一个格子开始一个个看**(遍历),或者利用二分查找(需要排序)**。
而哈希表的做法完全不同。它有一个哈希函数。 当你想要存储或查找一个元素(比如数字 100 )时,哈希表不会去遍历,而是把 100 扔进这个公式里算一下。
它不需要排序,也不需要从头遍历。它通过计算,直接定位到了存储位置。这就是为什么它的时间复杂度是 O(1)(瞬间找到)。
不依赖"物理下标",但它有"逻辑位置"
提到的不依赖下标,其实是指它不像数组那样依赖连续的物理内存下标。
数组:数据必须紧挨着放,下标 0 旁边必须是下标 1 。
哈希表:底层其实还是一个数组,但它允许数据"跳跃"存放。通过哈希函数计算出来的那个位置,就是它在哈希表里的逻辑下标。
不管你的数据原本是多少,经过公式一算,都会被映射到数组的某个具体下标上。
存:把数据扔进哈希函数,算出一个位置,放进去。
找:把数据再扔进哈希函数,算出同一个位置,直接去那里拿。这就是为什么它既不需要数据有序,也不需要遍历下标,就能实现极速查找的原因。
具体的原理在我之前的章节有提过:【LeetCode刷题日记】哈希表:从0基础到实战全解析
感兴趣的朋友可以看看。
题目答案:
java
import java.util.HashSet;
import java.util.Set;
class Solution {
public int longestConsecutive(int[] nums) {
// 边界情况处理
if (nums == null || nums.length == 0) {
return 0;
}
// 1. 将所有数字存入 HashSet,自动去重并提供 O(1) 查找
Set<Integer> numSet = new HashSet<>();
for (int num : nums) {
numSet.add(num);
}
int maxLen = 0;
// 2. 遍历集合中的每个数字
for (int num : numSet) {
// 3. 只有当 num 是序列的起点时(即 num-1 不在集合中),才开始计数
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentLen = 1;
// 4. 从起点开始,不断查找下一个连续数字
while (numSet.contains(currentNum + 1)) {
currentNum += 1;
currentLen += 1;
}
// 5. 更新最大长度
maxLen = Math.max(maxLen, currentLen);
}
}
return maxLen;
}
}
直接排序:
java
import java.util.Arrays;
class Solution {
public int longestConsecutive(int[] nums) {
// 1. 边界情况处理
if (nums == null || nums.length == 0) {
return 0;
}
// 2. 排序:让连续的数字在数组中相邻
Arrays.sort(nums);
int maxLen = 1; // 记录最长序列的长度,至少为1
int currentLen = 1; // 记录当前正在统计的序列长度
// 3. 从第二个元素开始遍历
for (int i = 1; i < nums.length; i++) {
// 情况一:遇到重复数字,直接跳过,不改变当前长度
if (nums[i] == nums[i - 1]) {
continue;
}
// 情况二:当前数字和前一个数字连续
else if (nums[i] == nums[i - 1] + 1) {
currentLen++;
}
// 情况三:序列中断,重置当前长度,并更新最大长度
else {
maxLen = Math.max(maxLen, currentLen);
currentLen = 1; // 从当前数字开始新的序列
}
}
// 4. 最后再比较一次,防止最长的序列在数组末尾
return Math.max(maxLen, currentLen);
}
}
对比一下,排序的方法是时间换空间,哈希表是空间换时间。
结语:如果对你有帮助,请**点赞,关注,收藏,**你的支持就是我最大的鼓励!
