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)

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

相关推荐
Upsy-Daisy1 小时前
IOTA 学习笔记(一):IOTA 是什么?从区块链到 Tangle
笔记·学习·区块链
小碗羊肉1 小时前
【Agent笔记 | 第五篇】LangChain&LangGraph
笔记·langchain
.千余1 小时前
【Linux】 TCP进阶详解:字节流、粘包问题、异常情况与UDP完整对比2
linux·运维·c语言·开发语言·经验分享·笔记·php
Upsy-Daisy1 小时前
IOTA 学习笔记(二):DAG 与 Tangle 到底是什么?
笔记·学习
不羁的木木1 小时前
Form Kit(卡片开发服务)学习笔记05-进阶实战与性能优化
笔记·学习·harmonyos
x_xbx1 小时前
LeetCode:543. 二叉树的直径
算法·leetcode·职场和发展
土狗TuGou1 小时前
SQL内功笔记 · 第7篇:CTE&临时表&递归
数据库·笔记·后端·sql·mysql
罗超驿2 小时前
11.LeetCode 1004. 最大连续1的个数 III | 滑动窗口解法详解(Java)
java·算法·leetcode
05候补工程师2 小时前
【英语学习笔记】基于“底层逻辑转换”与“去动词化”的英汉互译核心方法论及写作高分公式
经验分享·笔记·学习·考研