力扣top100(day01-03)

发布于:2025-08-14 ⋅ 阅读:(22) ⋅ 点赞:(0)

560. 和为 K 的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum == k) {
                    count++;
                }
                // else if(sum>k){
                //     continue;
                // }
            }
        }
        return count;
    }
}
public class Solution {
    public int subarraySum(int[] nums, int k) {
        // count:记录满足条件的子数组数量
        // pre:当前的前缀和(nums[0] + nums[1] + ... + nums[i])
        int count = 0, pre = 0;

        // mp:哈希表,key 是前缀和,value 是该前缀和出现的次数
        HashMap<Integer, Integer> mp = new HashMap<>();

        // 初始化:前缀和为 0 的情况出现 1 次(空子数组的和为 0)
        mp.put(0, 1);

        // 遍历数组,计算前缀和
        for (int i = 0; i < nums.length; i++) {
            // 更新当前的前缀和
            pre += nums[i];

            // 检查是否存在 pre - k 的前缀和
            // 如果存在,说明从某个位置到当前位置的子数组和为 k
            if (mp.containsKey(pre - k)) {
                // 累加 pre - k 的出现次数到 count
                count += mp.get(pre - k);
            }

            // 将当前前缀和存入哈希表,并更新出现次数
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);
        }

        // 返回满足条件的子数组数量
        return count;
    }
}

关键逻辑注释

1. mp.put(0, 1) 的作用
  • 为什么需要初始化 mp.put(0, 1)
    因为空子数组(不选任何元素)的和为 0。如果某个前缀和直接等于 k(比如 nums[0..i] == k),我们需要 pre - k = 0 的情况能被统计到。

2. if (mp.containsKey(pre - k)) 的逻辑
  • 为什么 pre - k 能表示子数组和为 k
    假设 pre[j] 是当前前缀和,pre[i] 是之前某个前缀和,如果 pre[j] - pre[i] = k,那么子数组 nums[i+1..j] 的和就是 k

    • 举例
      nums = [1, 2, 3]k = 3

      • i = 1 时,pre = 1 + 2 = 3pre - k = 0mp 中有 0),说明子数组 [1, 2] 的和为 3

      • i = 2 时,pre = 1 + 2 + 3 = 6pre - k = 3mp 中有 3),说明子数组 [3] 的和为 3

3. mp.put(pre, ...) 的作用
  • 为什么需要更新哈希表?
    记录每个前缀和的出现次数,后续可以通过 pre - k 快速判断是否存在满足条件的子数组。


示例运行(nums = [1, 1, 1]k = 2

步骤 i nums[i] pre pre - k mp 更新前 count 变化 mp 更新后
初始化 - - 0 - {0: 1} - -
1 0 1 1 -1 {0: 1} {0: 1, 1: 1}
2 1 1 2 0 {0: 1, 1: 1} count += 1 {0: 1, 1: 1, 2: 1}
3 2 1 3 1 {0: 1, 1: 1, 2: 1} count += 1 {0: 1, 1: 1, 2: 1, 3: 1}
  • 结果count = 2(子数组 [1, 1] 和 [1, 1])。


总结

  • 核心思想:用前缀和和哈希表将问题转化为两数之差问题。

  • 时间复杂度:O(n),只需遍历一次数组。

  • 空间复杂度:O(n),哈希表存储前缀和。

53. 最大子数组和

class Solution {
    public int maxSubArray(int[] nums) {
        // pre:以当前元素结尾的子数组的最大和
        // maxAns:全局最大子数组和
        int pre = 0, maxAns = nums[0];

        for (int x : nums) {
            // 决定是否抛弃之前的子数组,或加入当前元素
            pre = Math.max(pre + x, x);
            // 更新全局最大值
            maxAns = Math.max(maxAns, pre);
        }

        return maxAns;
    }
}

这段代码使用 动态规划(Kadane算法) 高效求解 最大子数组和 问题。它的核心思想是:

  1. 维护两个变量

    • pre:表示以当前元素结尾的子数组的最大和。

    • maxAns:记录全局最大子数组和。

  2. 递推关系

    • 对于每个元素 x,决定是将其加入前面的子数组(pre + x),还是以它作为新子数组的起点(x)。

    • 更新 maxAns 为当前最大值。


逐行解析

1. 初始化

java

int pre = 0, maxAns = nums[0];
  • pre:初始化为 0,表示尚未开始累加。

  • maxAns:初始化为 nums[0],因为至少包含一个元素的子数组的最大和可能是第一个元素本身。


2. 遍历数组

java

for (int x : nums) {
    pre = Math.max(pre + x, x);
    maxAns = Math.max(maxAns, pre);
}
  • pre = Math.max(pre + x, x)

    • pre + x:将当前元素 x 加入之前的子数组。

    • x:以当前元素 x 作为新子数组的起点。

    • 选择较大值,确保 pre 始终是以 x 结尾的子数组的最大和。

  • maxAns = Math.max(maxAns, pre)

    • 更新全局最大值 maxAns


示例演示

假设 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

元素 (x) pre (更新前) pre + x x 新 pre maxAns (更新前) 新 maxAns
-2 0 -2 -2 -2 -2 -2
1 -2 -1 1 1 -2 1
-3 1 -2 -3 -2 1 1
4 -2 2 4 4 1 4
-1 4 3 -1 3 4 4
2 3 5 2 5 4 5
1 5 6 1 6 5 6
-5 6 1 -5 1 6 6
4 1 5 4 5 6 6
  • 最终结果maxAns = 6(对应子数组 [4, -1, 2, 1])。


复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组。

  • 空间复杂度:O(1),仅使用常数空间。


关键点总结

  1. 动态规划状态转移

    • pre 表示以当前元素结尾的子数组的最大和。

    • 通过比较 pre + x 和 x 决定是否抛弃之前的子数组。

  2. 全局最大值更新

    • 每次迭代后更新 maxAns,确保不遗漏任何可能的子数组。

  3. 初始化技巧

    • maxAns 初始化为 nums[0],处理数组全为负数的情况

56. 合并区间

class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals.length <= 1) {
            return intervals;
        }

        // 按区间起始点排序
        Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
        
        List<int[]> merged = new ArrayList<>();
        int[] current = intervals[0];
        merged.add(current);
        
        for (int[] interval : intervals) {
            if (interval[0] <= current[1]) {
                // 合并区间
                current[1] = Math.max(current[1], interval[1]);
            } else {
                // 无法合并,添加到结果
                current = interval;
                merged.add(current);
            }
        }
        
        return merged.toArray(new int[merged.size()][]);
    }
}
1. 输入处理与特殊情况

java

if (intervals.length <= 1) {
    return intervals;
}
  • 作用:如果输入的区间数量 ≤ 1(即空数组或单个区间),直接返回原数组。

  • 原因:无需合并,直接返回即可。


2. 区间排序

java

Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
  • 作用:按区间的起始点(intervals[i][0])升序排序。

  • 原因:排序后,重叠的区间会相邻排列,便于后续合并。

  • 示例

    • 输入:[[2,6],[1,3],[8,10],[15,18]]

    • 排序后:[[1,3],[2,6],[8,10],[15,18]]


3. 初始化合并列表

java

List<int[]> merged = new ArrayList<>();
int[] current = intervals[0];
merged.add(current);
  • merged:用于存储合并后的结果区间。

  • current:当前正在处理的区间,初始化为第一个区间。

  • 原因:从第一个区间开始逐步合并。


4. 遍历与合并区间

java

for (int[] interval : intervals) {
    if (interval[0] <= current[1]) {
        // 合并区间
        current[1] = Math.max(current[1], interval[1]);
    } else {
        // 无法合并,添加到结果
        current = interval;
        merged.add(current);
    }
}
  • 逻辑

    1. 可合并:如果当前区间的起始点 ≤ current 的结束点,说明两区间重叠。

      • 更新 current 的结束点为两者的最大值(扩展区间)。

      • 示例

        • current = [1,3]interval = [2,6] → 合并为 [1,6]

    2. 不可合并:如果区间不重叠,将 current 加入结果,并更新 current 为新区间。

      • 示例

        • current = [1,6]interval = [8,10] → 无法合并,将 [1,6] 加入结果,current 更新为 [8,10]


5. 返回结果

java

return merged.toArray(new int[merged.size()][]);
  • 作用:将 List<int[]> 转换为 int[][]

  • 原因:题目要求返回二维数组。

深入解析 Comparator.comparingInt(a -> a[0])

这是Java中用于排序的一个关键表达式,属于函数式编程的用法。我们来彻底理解它的含义和工作原理。


1. 基本作用

java

Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
  • 功能:对二维数组 intervals 按照每个子数组的第一个元素(即区间起点)进行升序排序。

  • 等价于

    java

    Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

2. 分解解析
(1) Comparator 接口
  • 是Java中用于定义比较规则的接口。

  • 核心方法:int compare(T o1, T o2)

(2) comparingInt() 方法
  • 是 Comparator 的静态工厂方法。

  • 签名:

    java

    static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor)
  • 参数ToIntFunction,即从对象中提取一个 int 类型排序键的函数。

  • 作用:根据提取的 int 键生成一个 Comparator

(3) a -> a[0]
  • 是一个Lambda表达式,等价于:

    java

    new ToIntFunction<int[]>() {
        @Override
        public int applyAsInt(int[] a) {
            return a[0];
        }
    }
  • 功能:输入一个区间 a,返回它的起始点 a[0]


3. 完整执行流程
  1. 提取排序键:对每个区间 a,提取 a[0](起始点)。

  2. 比较规则:根据提取的 a[0] 值生成默认的升序比较器。

    • 相当于隐式实现了:

      java

      (a, b) -> Integer.compare(a[0], b[0])

4. 对比其他写法
写法 说明
Comparator.comparingInt(a -> a[0]) 函数式风格,简洁
(a, b) -> a[0] - b[0] 传统Lambda,可能溢出
new Comparator<int[]>() { ... } 匿名类,冗长

5. 为什么用 comparingInt
  1. 避免整数溢出

    • a[0] - b[0] 在极大值时可能溢出。

    • Integer.compare(a[0], b[0]) 会正确处理边界。

  2. 代码可读性

    • 明确表达"按某字段排序"的意图。

  3. 类型安全

    • 编译期检查提取的键是否为 int


6. 扩展用法
(1) 降序排序

java

Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]).reversed());
(2) 多级排序

java

// 先按起点升序,再按终点降序
Arrays.sort(intervals,
    Comparator.comparingInt((int[] a) -> a[0])
             .thenComparing(Comparator.comparingInt(a -> a[1]).reversed())
);

7. 实际应用示例

输入[[3,4],[1,2],[2,3]]
排序后[[1,2],[2,3],[3,4]]
代码执行

  1. 提取每个元素的 a[0]3, 1, 2

  2. 按提取值排序:1, 2, 3

  3. 最终顺序:[1,2][2,3][3,4]


网站公告

今日签到

点亮在社区的每一天
去签到