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)

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

相关推荐
如何原谅奋力过但无声2 分钟前
【LeetCode hot100】代码随想录未cover部分
数据结构·算法·leetcode
ljt272496066112 分钟前
Vue笔记(十五)--自定义引用
笔记·1024程序员节
xieyan081121 分钟前
笔记不只是仓库:让 AI 帮旧想法重新参与思考
人工智能·笔记
文丑颜不良啊1 小时前
LeetCode701~725题目解析
java·leetcode
江屿风1 小时前
【Linux-System】【System V共享内存:从原理到实战的完整解析】流食般投喂
linux·运维·服务器·笔记·系统架构·1024程序员节
東隅已逝,桑榆非晚1 小时前
搜索二叉树
c++·笔记·学习·1024程序员节
木井巳2 小时前
【动态规划: 线性dp】解码方法
java·算法·leetcode·动态规划·推荐算法
java_nnnn12 小时前
LeetCode刷题滑动窗口(串联所有单词的子串+最小覆盖的子串)Java
算法·leetcode·职场和发展
Young Doro12 小时前
从 DV 表示到 MI 下界,再到“置换技巧“(含“指数倾斜“完整推导)
笔记
Navigator_Z12 小时前
LeetCode //C - 1301. Number of Paths with Max Score
c语言·算法·leetcode