Hard-题目8:128. 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.
题目大意:
给出一个无序的数组,寻找最长的连续元素,返回其长度。
例如数组[100,4,200,1,3,2]的最长连续元素是[1,2,3,4]所以返回4.
要求在线性时间复杂度内解决。
题目分析:
要求没做到,用了朴素的办法水过去的。复杂度O(nlogn).,先排序,再扫描一遍数组寻找最大的连续元素。如果遇到重复元素则跳过。
源码:(language:java)

public class Solution {
    public int longestConsecutive(int[] nums) {
        Arrays.sort(nums);
        int longest = 1;
        int max = 1;
        for(int i = 1;i<nums.length;i++) {
            if(nums[i]==nums[i-1])
                continue;
            else if(nums[i]-nums[i-1]==1) {
                longest++;
                if(longest>max)
                    max = longest;
            }
            else
                longest=1;
        }
        return max;
    }
}

成绩:
5ms,beats 95.00%,众数16ms,11.95%
Cmershen的碎碎念:
本题的tags给的是并查集,我想到的大致思路是,把所有相邻的都加入一个集合中,并维护每个集合的最大值和最小值,遍历数组的每个元素n,如果n-1或n+1都在集合中,则把这两个集合并起来,如果只有一个在,则加入相应集合,如果都不在则自成一个集合。但还没实现,而且也不确定是否是O(n)时间的。而O(nlogn)的复杂度下的完成时间确是很快的(似乎小于线性算法),可能是用例的数量不够大。

<think> 我们遇到了一个NullPointerException,发生在ChronoLocalDateTime.isAfter方法中。这意味着在比较两个时间时,其中一个时间为null。 根据堆栈信息,问题发生在处理设备EP000015的过程中。我们需要检查在调用isAfter方法的地方,传入的参数是否有可能是null。 在代码中,我们使用isAfter方法的地方有几处,但根据堆栈信息,最可能的是在以下方法中: 1. `findAlarmPeriod` 方法中的 `windowEnd.isAfter(analysisEnd)` 2. 其他使用isAfter的地方,比如在滑动窗口循环中。 在修复版的`findAlarmPeriod`方法中,我们有以下代码: ```java // 如果时段结束时间等于或晚于分析结束时间,视为持续中 if (longest.end != null && (longest.end.isAfter(analysisEnd) || longest.end.equals(analysisEnd))) { return new TimePeriod(longest.start, null); } ``` 这里我们检查了`longest.end`不为null,但是`analysisEnd`有可能为null吗?在调用`findAlarmPeriod`方法的地方,传入的`analysisEnd`参数是`analysisEnd`(即方法的参数),而在`processEquipment`方法中调用`findAlarmPeriod`时,传递的是`analysisEnd`,这个值是从`hahfAnalysis`方法中的`endTime`传递过来的,而`endTime`被初始化为`LocalDateTime.now()`,所以不可能为null。 但是,我们注意到在`findAlarmPeriod`方法中,我们创建了`TimePeriod`对象,其`end`字段可能是null(表示持续中),但是在上述代码中,我们检查了`longest.end`不为null,所以不会执行`longest.end.isAfter`如果`longest.end`是null。因此,问题可能不在这里。 再看其他地方,在滑动窗口循环中: ```java if (currentEnd != null && Duration.between(currentEnd, windowEnd).toHours() <= 24) { currentEnd = windowEnd; // 扩展当前时段 } ``` 这里我们使用了`Duration.between(currentEnd, windowEnd)`,如果`windowEnd`是null,会导致NPE。但是`windowEnd`是从数据点中取出的,而数据点的时间是通过`hourDataList`解析得到的,如果数据点的时间为null,在`prepareDataPoints`方法中会过滤掉(返回null并在后面过滤掉),所以正常情况下数据点的时间不为null。 然而,我们注意到在`findAlarmPeriod`方法中,我们使用`dataPoints.subList(i, i + 24)`获取窗口数据,然后调用`isValidPeriod`方法。在`isValidPeriod`方法中,我们使用了: ```java LocalDateTime first = dataPoints.get(0).dateTime; LocalDateTime last = dataPoints.get(dataPoints.size()-1).dateTime; ``` 这里假设了数据点列表不为空,且每个数据点都有非null的`dateTime`。我们已经过滤了null,所以这里应该是安全的。 再看另一个可能的地方:在`checkRecovery`方法中,我们使用了: ```java if (!windowStart.isBefore(startTime)) { ... } ``` 这里`windowStart`是从数据点中获取的,不为null,而`startTime`是从报警记录中获取的,即`activeAlarm.getAbnormaltime()`转换而来,如果这个时间为null,则`toLocalDateTime`返回null,就会出现NPE。 因此,我们需要检查`activeAlarm.getAbnormaltime()`是否为null。根据报警记录实体,正常情况下不应该为null,因为创建报警记录时会设置`abnormaltime`。但是,如果数据库中该字段为null,就会导致问题。 解决方案:在转换之前检查时间是否为null。 此外,我们还需要检查代码中其他调用`isAfter`、`isBefore`等方法的参数是否为null。 具体修复步骤: 1. 在`toLocalDateTime`方法中,如果传入的Date为null,我们返回null,但是调用者需要处理null的情况。因此,我们需要在使用转换后的时间之前进行null检查。 2. 在`handleActiveAlarm`方法中,我们调用`toLocalDateTime(activeAlarm.getAbnormaltime())`,如果这个返回null,那么`alarmStartTime`就是null,然后在`checkRecovery`方法中,使用`startTime`(此时为null)作为参数,然后在循环中: ```java if (!windowStart.isBefore(startTime)) { ... } ``` 这里就会抛出NPE。 因此,我们需要在`handleActiveAlarm`方法中检查转换后的时间是否为null。 修改`handleActiveAlarm`方法: ```java private void handleActiveAlarm(UleabnormalrecordsEntity activeAlarm, List<DataPoint> dataPoints, double farBeyondValue, double limitingValue, LocalDateTime analysisEnd) { LocalDateTime alarmStartTime = toLocalDateTime(activeAlarm.getAbnormaltime()); // 如果报警开始时间为null,则无法处理,记录错误并返回 if (alarmStartTime == null) { logger.error("报警记录{}的异常时间为空,无法处理", activeAlarm.getId()); return; } // 原有的逻辑... } ``` 同样,在`completeAlarm`方法中,我们使用`toLocalDateTime(activeAlarm.getAbnormaltime())`,也需要检查null。 另外,在`findAlarmPeriod`方法中,我们使用`analysisEnd`(不为null)和`longest.end`(可能为null,但我们做了检查)进行比较,所以安全。 但是,在`checkRecovery`方法中,我们使用了`startTime`(非null)和`windowStart`(非null),所以没有NPE。 另一个可能出现NPE的地方:在`isValidPeriod`方法中,我们使用了`bucketValid`数组,初始值为false,然后遍历数据点设置桶有效标志。这里没有NPE风险。 因此,主要问题在于报警记录中的异常时间可能为null。我们在处理报警记录时,需要检查这个字段。 但是,为什么会出现报警记录中异常时间为null的情况?这可能是数据问题,但我们的代码需要健壮性来处理。 修复代码: 在`handleActiveAlarm`和`completeAlarm`方法中,对转换后的时间进行非空检查。 修改后的`handleActiveAlarm`方法: ```java private void handleActiveAlarm(UleabnormalrecordsEntity activeAlarm, List<DataPoint> dataPoints, double farBeyondValue, double limitingValue, LocalDateTime analysisEnd) { LocalDateTime alarmStartTime = toLocalDateTime(activeAlarm.getAbnormaltime()); if (alarmStartTime == null) { logger.error("报警记录{}的异常时间为空,跳过处理", activeAlarm.getId()); return; } // 检查报警是否恢复:连续24小时不满足条件才算恢复 LocalDateTime recoveryTime = checkRecovery( dataPoints, alarmStartTime, farBeyondValue, limitingValue, analysisEnd ); // ... 后续逻辑 } ``` 修改后的`completeAlarm`方法: ```java private void completeAlarm(UleabnormalrecordsEntity alarm, LocalDateTime recoveryTime) { LocalDateTime startTime = toLocalDateTime(alarm.getAbnormaltime()); if (startTime == null) { logger.error("报警记录{}的异常时间为空,无法完成处理", alarm.getId()); return; } // ... 后续逻辑 } ``` 此外,在`findAlarmPeriod`方法中,我们使用`longest.end`和`analysisEnd`比较,而`analysisEnd`是从`processEquipment`方法传入的,它来自`hahfAnalysis`方法中的`endTime`(即`LocalDateTime.now()`),所以不为null。但是,如果`longest.end`为null(表示持续中),那么我们在判断时已经做了`longest.end != null`,所以不会执行后面的`isAfter`和`equals`。因此,这里安全。 但是,我们注意到在`findAlarmPeriod`方法中,我们使用了`dataPoints.get(i+23).dateTime`来获取`windowEnd`,如果数据点不足24个,我们不会进入循环,所以不会出现索引越界。在循环条件中,我们确保`i <= dataPoints.size() - 24`,所以`i+23`是有效的。 因此,通过上述修改,我们可以避免NullPointerException。 总结:在从数据库读取报警记录时,异常时间字段可能为null,我们在使用之前进行空值检查。 此外,建议在创建报警记录时,确保`abnormaltime`不为null。在`createAlarmRecord`方法中,我们设置`record.setAbnormaltime(toDate(startTime))`,而`startTime`是方法参数,由`findAlarmPeriod`返回的`TimePeriod`的`start`字段,这个字段不为null(因为`TimePeriod`的构造中`start`不为null),所以新创建的报警记录不会有空时间。因此,这个null值只可能出现在数据库中已有的异常记录中。 所以,我们只需要在读取报警记录时进行空值检查即可。 修复后的代码将能够处理这种情况,避免NPE。 现在,针对设备EP000015,由于报警记录中的异常时间为null,我们会记录错误并跳过处理,不会影响其他设备。 另外,建议检查数据库,修复那些异常时间为null的报警记录。 §§相关问题§§: 1. 在Java中处理时间时,如何避免常见的空指针异常? 2. 数据库中的时间字段为NULL可能会引起什么问题?如何避免? 3. 在时间窗口分析中,如何确保时间序列数据的完整性? 4. 报警系统中,如何设计报警记录的字段约束以防止空值? </think> 根据堆栈跟踪,问题发生在 `ChronoLocalDateTime.isAfter()` 方法中,这是一个典型的空指针异常(NullPointerException)。让我们深入分析并解决这个问题: ### 问题分析 1. **异常位置**: ```java java.lang.NullPointerException: null at java.time.chrono.ChronoLocalDateTime.isAfter(ChronoLocalDateTime.java:526) ``` 这表明在调用 `isAfter()` 方法时,传入的参数为 `null`。 2. **可能的原因**: - 在调用 `LocalDateTime.isAfter(other)` 时,`other` 参数为 `null` - 在处理报警时段合并逻辑时,`windowEnd` 变量可能为 `null` - 在 `TimePeriod` 对象中,`end` 字段可能为 `null` 但未正确检查 ### 修复方案 我们需要在所有使用 `isAfter()` 方法的地方添加空值检查: ```java /** * 安全的 isAfter 方法 */ private boolean isAfterSafely(LocalDateTime first, LocalDateTime second) { if (first == null || second == null) { logger.warn("尝试比较 null 时间: first={}, second={}", first, second); return false; } return first.isAfter(second); } /** * 寻找符合条件的报警时间段(空值安全版) */ private TimePeriod findAlarmPeriod(List<DataPoint> dataPoints, double farBeyondValue, double limitingValue, LocalDateTime analysisEnd) { // ... [省略其他代码] ... // 修复合并连续时段逻辑中的空值问题 if (currentEnd != null && windowEnd != null && Duration.between(currentEnd, windowEnd).toHours() <= 24) { currentEnd = windowEnd; } // ... [省略其他代码] ... // 修复持续中报警判定逻辑 if (longest.end != null && analysisEnd != null && (isAfterSafely(longest.end, analysisEnd) || longest.end.equals(analysisEnd))) { return new TimePeriod(longest.start, null); } return longest; } /** * 检查报警恢复(空值安全版) */ private LocalDateTime checkRecovery(List<DataPoint> dataPoints, LocalDateTime startTime, double farBeyondValue, double limitingValue, LocalDateTime analysisEnd) { if (startTime == null) { logger.warn("报警开始时间为空,跳过恢复检查"); return null; } // ... [省略其他代码] ... } ``` ### 关键修复点说明: 1. **添加空值安全的时间比较方法**: ```java private boolean isAfterSafely(LocalDateTime first, LocalDateTime second) { if (first == null || second == null) { logger.warn("尝试比较 null 时间: first={}, second={}", first, second); return false; } return first.isAfter(second); } ``` - 封装了安全的比较方法,避免直接调用 `isAfter()` - 添加日志记录以便追踪可能的空值来源 2. **修复时段合并逻辑**: ```java if (currentEnd != null && windowEnd != null && Duration.between(currentEnd, windowEnd).toHours() <= 24) { ``` - 添加对 `windowEnd` 的空值检查 - 确保在计算时间差之前所有参数有效 3. **修复持续中判定逻辑**: ```java if (longest.end != null && analysisEnd != null && (isAfterSafely(longest.end, analysisEnd) || longest.end.equals(analysisEnd))) { ``` - 双重空值检查(`longest.end` 和 `analysisEnd`) - 使用安全比较方法替代直接调用 `isAfter()` ### 完整建议修改点: 1. 添加 `isAfterSafely()` 辅助方法 2. 修改 `findAlarmPeriod()` 方法中所有使用 `isAfter()` 的地方 3. 在 `checkRecovery()` 方法开头添加对 `startTime` 的空值检查 4. 在 `processEquipment()` 方法中添加对 `analysisEnd` 的验证: ```java private void processEquipment(EquipmentparameterEntity equipment, Map<String, Map<String, Object>> dailyValueMap, Map<String, List<Map>> hourDataByTag, Map<String, UleabnormalrecordsEntity> activeAlarmMap, LocalDateTime analysisStart, LocalDateTime analysisEnd) { // 添加空值检查 if (analysisEnd == null) { logger.warn("分析结束时间为空,使用当前时间"); analysisEnd = LocalDateTime.now(); } // ... [原有代码] ... } ``` 5. 在 `hahfAnalysis()` 方法中确保 `endTime` 不为空: ```java public void hahfAnalysis() { // 确保结束时间不为空 LocalDateTime endTime = LocalDateTime.now(); // ... [原有代码] ... } ``` ### 错误处理最佳实践: 1. **防御性编程**:对所有可能为空的参数添加检查 2. **详细日志**:记录警告信息以便追踪问题源头 3. **默认值处理**:对关键参数提供合理的默认值 4. **异常处理**:在方法顶层捕获并记录异常: ```java public void hahfAnalysis() { try { // ... 原有逻辑 ... } catch (Exception e) { logger.error("HAHF分析任务失败", e); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值