LeetCode刷题笔记:数组专题四连击(LC53/56/189/41)

文章目录

    • 前言
    • [一、LC53 最大子数组(前缀和)](#一、LC53 最大子数组(前缀和))
    • [二、LC56 合并区间](#二、LC56 合并区间)
    • [三、LC189 轮转数组(三次反转)](#三、LC189 轮转数组(三次反转))
    • [四、LC41 缺失的第一个正数(原地哈希)](#四、LC41 缺失的第一个正数(原地哈希))
      • [4.1 越界:`>=` 不是 `>`](#4.1 越界:>= 不是 >)
      • [4.2 死循环:必须 swap 不能覆盖](#4.2 死循环:必须 swap 不能覆盖)
      • [4.3 用 while 不用 if](#4.3 用 while 不用 if)
    • 总结

前言

这篇刷题笔记覆盖数组类四道高频题: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.arraycopy
  • k 要先取模,否则 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)

刷数组题,边界条件和索引映射是最容易翻车的地方,写之前先把下标关系在纸上画清楚。

相关推荐
闪闪发亮的小星星3 天前
高斯光以及高斯光公式解释
笔记
cqbzcsq3 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
阿米亚波3 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.3 天前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
想吃火锅10053 天前
【leetcode】121.买卖股票的最佳时机js/c++
算法·leetcode·职场和发展
.千余3 天前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他
自传.3 天前
尚硅谷 Vibe Coding|第二章 AI编程工具生态 学习笔记
笔记·学习·ai编程·尚硅谷·vibe coding
秋波。未央3 天前
Java Agent 开发 · Day 1 学习笔记(含作业完整标准答案)
java·笔记·学习
凌波粒3 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
啵啵啵鱼3 天前
数组---完
算法·排序算法