文章目录
前言
这篇刷题笔记覆盖数组类四道高频题:LC53 最大子数组、LC56 合并区间、LC189 轮转数组、LC41 缺失的第一个正数。每道题都踩了坑,记录解法和易错点,方便回顾。
一、LC53 最大子数组(前缀和)
子数组和 = 两个前缀和之差。固定右端点,想让差值最大,就减迄今为止最小的前缀和。
java
public int maxSubArray(int[] nums) {
int n = nums.length;
int[] pre = new int[n + 1];
int ans = nums[0], min = 0;
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + nums[i];
ans = Math.max(pre[i + 1] - min, ans);
min = Math.min(pre[i + 1], min);
}
return ans;
}
为什么 min 更新放后面?
如果先更新 min,min 可能等于当前 prei+1,那 pre[i+1] - min = 0,相当于取了空子数组。放在后面保证减的是之前的最小值,子数组至少包含当前元素。
二、LC56 合并区间
先按起点排序,然后遍历合并。用 LinkedList 方便取最后一个区间。
java
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> list = new LinkedList<>();
list.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] last = list.getLast();
if (intervals[i][0] <= last[1]) {
last[1] = Math.max(last[1], intervals[i][1]);
} else {
list.add(intervals[i]);
}
}
return list.toArray(new int[list.size()][]);
}
踩坑记录:
- 排序必须放在 addFirst 之前,否则第一个元素没排序
toArray(new int[][])编译报错,必须传new int[list.size()][]System.out.println(数组)打印的是[I@xxxxx,要用Arrays.toString()
三、LC189 轮转数组(三次反转)
O(n) 时间 + O(1) 空间的正解:
java
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
void reverse(int[] nums, int l, int r) {
while (l < r) {
int t = nums[l];
nums[l] = nums[r];
nums[r] = t;
l++; r--;
}
}
举例 [1,2,3,4,5,6,7],k=3:
原始: [1, 2, 3, 4, 5, 6, 7]
整体反转: [7, 6, 5, 4, 3, 2, 1]
前3反转: [5, 6, 7, 4, 3, 2, 1]
后面反转: [5, 6, 7, 1, 2, 3, 4]
踩坑记录:
nums = Arrays.copyOf()只改了局部变量,原数组不变,要用System.arraycopyk要先取模,否则count - k可能负数i %= nums.length写成i % (nums.length)编译不过,取模结果必须赋值
四、LC41 缺失的第一个正数(原地哈希)
核心思想:值 x 应该放在下标 x-1。做法是不断 swap,把每个值换到它该去的位置。
java
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n
&& nums[nums[i] - 1] != nums[i]) {
int tmp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = tmp;
}
}
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) return i + 1;
}
return n + 1;
}
三个易错点:
4.1 越界:>= 不是 >
java
if (num > nums.length) return; // 错!num == n 时 nums[n] 越界
if (num >= nums.length) return; // 对
数组下标最大是 n-1,num == n 就已经越界了。
4.2 死循环:必须 swap 不能覆盖
两个位置值互换时(比如 nums[2]=3, nums[3]=2),只覆盖不交换会无限递归:
dfs(3) → nums[3]=2 → dfs(2)
dfs(2) → nums[2]=3 → dfs(3) // 死循环
swap 后各自归位,nums[num]==num 就能命中终止条件。
4.3 用 while 不用 if
换回来的新值可能还要继续归位,if 只处理一次,while 持续处理到当前位置合法为止。
总结
| 题目 | 核心技巧 | 复杂度 |
|---|---|---|
| LC53 最大子数组 | 前缀和 + 维护最小前缀 | O(n) |
| LC56 合并区间 | 排序 + 一次遍历 | O(n log n) |
| LC189 轮转数组 | 三次反转 | O(n), O(1) |
| LC41 缺失第一个正数 | 原地哈希 swap | O(n), O(1) |
刷数组题,边界条件和索引映射是最容易翻车的地方,写之前先把下标关系在纸上画清楚。