1477.找两个和为目标值且不重叠的子数组

目标

给你一个整数数组 arr 和一个整数值 target 。

请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target 。可能会有多种方案,请你返回满足要求的两个子数组长度和的 最小值 。

请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1 。

示例 1:

输入:arr = [3,2,2,4,3], target = 3
输出:2
解释:只有两个子数组和为 3 ([3] 和 [3])。它们的长度和为 2 。

示例 2:

输入:arr = [7,3,4,7], target = 7
输出:2
解释:尽管我们有 3 个互不重叠的子数组和为 7 ([7], [3,4] 和 [7]),但我们会选择第一个和第三个子数组,因为它们的长度和 2 是最小值。

示例 3:

输入:arr = [4,3,2,6,2,3,4], target = 6
输出:-1
解释:我们只有一个和为 6 的子数组。

示例 4:

输入:arr = [5,5,4,4,5], target = 3
输出:-1
解释:我们无法找到和为 3 的子数组。

示例 5:

输入:arr = [3,1,1,1,5,1,2,1], target = 3
输出:3
解释:注意子数组 [1,2] 和 [2,1] 不能成为一个方案因为它们重叠了。

说明:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 1000
  • 1 <= target <= 10^8

思路

有一个整数数组 arr 和一个整数值 target,从数组中找到两个不重叠的子数组,使得子数组的和为 target,返回这两个子数组长度之和的最小值。

将重叠问题进行前后缀分解,在各自的部分只需考虑和为 target 的最小长度,最小长度可以使用滑动窗口。

代码


/**
 * @date 2026-09-17 10:06
 */
public class MinSumOfLengths1477 {

    public int minSumOfLengths(int[] arr, int target) {
        int n = arr.length;
        int[] pre = new int[n + 1];
        int[] suf = new int[n + 1];
        Arrays.fill(pre, Integer.MAX_VALUE);
        Arrays.fill(suf, Integer.MAX_VALUE);
        int sum = 0;
        int l = 0;
        for (int i = 0; i < n; i++) {
            sum += arr[i];
            while (sum > target) {
                sum -= arr[l++];
            }
            if (sum == target) {
                pre[i + 1] = Math.min(pre[i], i - l + 1);
            } else {
                pre[i + 1] = pre[i];
            }
        }
        sum = 0;
        int r = n - 1;
        for (int i = n - 1; i >= 0; i--) {
            sum += arr[i];
            while (sum > target) {
                sum -= arr[r--];
            }
            if (sum == target) {
                suf[i] = Math.min(suf[i + 1], r - i + 1);
            } else {
                suf[i] = suf[i + 1];
            }
        }
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            if (pre[i + 1] != Integer.MAX_VALUE && suf[i + 1] != Integer.MAX_VALUE) {
                res = Math.min(res, pre[i + 1] + suf[i + 1]);
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }

}

性能

1621.大小为K的不重叠线段的数目

目标

给你一维空间的 n 个点,其中第 i 个点(编号从 0 到 n-1)位于 x = i 处,请你找到 恰好 k 个不重叠 线段且每个线段至少覆盖两个点的方案数。线段的两个端点必须都是 整数坐标 。这 k 个线段不需要全部覆盖全部 n 个点,且它们的端点 可以 重合。

请你返回 k 个不重叠线段的方案数。由于答案可能很大,请将结果对 10^9 + 7 取余 后返回。

示例 1:

输入:n = 4, k = 2
输出:5
解释:
如图所示,两个线段分别用红色和蓝色标出。
上图展示了 5 种不同的方案 {(0,2),(2,3)},{(0,1),(1,3)},{(0,1),(2,3)},{(1,2),(2,3)},{(0,1),(1,2)} 。

示例 2:

输入:n = 3, k = 1
输出:3
解释:总共有 3 种不同的方案 {(0,1)}, {(0,2)}, {(1,2)} 。

示例 3:

输入:n = 30, k = 7
输出:796297179
解释:画 7 条线段的总方案数为 3796297200 种。将这个数对 10^9 + 7 取余得到 796297179 。

示例 4:

输入:n = 5, k = 3
输出:7

示例 5:

输入:n = 3, k = 2
输出:1

说明:

  • 2 <= n <= 1000
  • 1 <= k <= n-1

思路

数轴上有 n 个点,编号为 0 ~ n - 1,求从中选择 k 个不重叠线段(至少覆盖两个点)的方案数。线段 [a, b] 与 [c, d] 不重叠指 b <=c 或 d <= a。

定义 dp[i][k] 表示 [0, i] 中恰好 k 个不重叠线段的方案数。根据选或者不选 i 考虑状态转移方程,如果不选 i,方案数等于前面的方案数,如果选,需要枚举前面所有的 p ∈ [0, i - 1],累加 dp[p][j - 1]。

  • dp[i][j] = (dp[i - 1][j] + sum) % mod,sum = Σdp[p][j - 1], p ∈ [0, i - 1]

代码


/**
 * @date 2026-09-17 15:31
 */
public class NumberOfSets1621 {

    public int numberOfSets(int n, int k) {
        int mod = 1000000007;
        int[][] dp = new int[n][k + 1];
        dp[0][0] = 1;
        int[] prefix = new int[k + 1];
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= k; j++) {
                prefix[j] = (prefix[j] + dp[i - 1][j - 1]) % mod;
                dp[i][j] = (dp[i - 1][j] + prefix[j]) % mod;
            }
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            res = (res + dp[i][k]) % mod;
        }
        return res;
    }

}

性能

2472.不重叠回文子字符串的最大数目

目标

给你一个字符串 s 和一个 正 整数 k 。

从字符串 s 中选出一组满足下述条件且 不重叠 的子字符串:

  • 每个子字符串的长度 至少 为 k 。
  • 每个子字符串是一个 回文串 。

返回最优方案中能选择的子字符串的 最大 数目。

子字符串 是字符串中一个连续的字符序列。

示例 1 :

输入:s = "abaccdbbd", k = 3
输出:2
解释:可以选择 s = "abaccdbbd" 中斜体加粗的子字符串。"aba" 和 "dbbd" 都是回文,且长度至少为 k = 3 。
可以证明,无法选出两个以上的有效子字符串。

示例 2 :

输入:s = "adbcda", k = 2
输出:0
解释:字符串中不存在长度至少为 2 的回文子字符串。

说明:

  • 1 <= k <= s.length <= 2000
  • s 仅由小写英文字母组成

思路

返回字符串 s 中长度 至少 为 k 的不重叠回文子串的最大个数。

定义 dp[i] 表示 s[i, n - 1] 中长度为 k 的不重叠回文子串的最大数目,

  • 如果不选 s[i],dp[i] = dp[i + 1]
  • 如果选择 s[i],枚举终点 j,使得 j - i + 1 >= k,如果 s[i, j] 是回文,dp[i] = max(dp[i], 1 + dp[j + 1]))

需要快速判断子串是否是回文,可以预处理。

代码


/**
 * @date 2026-09-15 9:24
 */
public class MaxPalindromes2472 {

    public int maxPalindromes(String s, int k) {
        int n = s.length();
        boolean[][] isPalindrome = new boolean[n][n];
        for (int i = 0; i < n; i++) {
            isPalindrome[i][i] = true;
            int l = i - 1, r = i + 1;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)){
                isPalindrome[l--][r++] = true;
            }
            l = i - 1;
            r = i;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)){
                isPalindrome[l--][r++] = true;
            }
        }
        int[] dp = new int[n + 1];
        for (int i = n - k; i >= 0; i--) {
            dp[i] = dp[i + 1];
            for (int j = n - 1; j >= i + k - 1; j--) {
                if (isPalindrome[i][j]) {
                    dp[i] = Math.max(dp[i], 1 + dp[j + 1]);
                }
            }
        }
        return dp[0];
    }

}

性能

836.矩形重叠

目标

矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。矩形的上下边平行于 x 轴,左右边平行于 y 轴。

如果相交的面积为 正 ,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。

给出两个矩形 rec1 和 rec2 。如果它们重叠,返回 true;否则,返回 false 。

示例 1:

输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3]
输出:true

示例 2:

输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1]
输出:false

示例 3:

输入:rec1 = [0,0,1,1], rec2 = [2,2,3,3]
输出:false

说明:

  • rect1.length == 4
  • rect2.length == 4
  • -10^9 <= rec1[i], rec2[i] <= 10^9
  • rec1 和 rec2 表示一个面积不为零的有效矩形

思路

使用数组 [x1, y1, x2, y2] 表示一个矩形的 左下 (x1, y1) 与 右上 (x2, y2) 顶点。给出两个数组 rec1 与 rec2,判断它们是否重叠。

如果两个矩形重叠,那么两个矩形在两个坐标轴方向的投影区间都应该相交。

代码


/**
 * @date 2026-09-14 9:08
 */
public class IsRectangleOverlap836 {

    public boolean isRectangleOverlap(int[] rec1, int[] rec2) {
        int x1 = rec1[0], x3 = rec2[0];
        int y1 = rec1[1], y3 = rec2[1];
        int x2 = rec1[2], x4 = rec2[2];
        int y2 = rec1[3], y4 = rec2[3];
        return !(x2 <= x3 || x4 <= x1) && !(y2 <= y3 || y4 <= y1);
    }

}

性能

3414.不重叠区间的最大得分

目标

给你一个二维整数数组 intervals,其中 intervals[i] = [li, ri, weighti]。区间 i 的起点为 li,终点为 ri,权重为 weighti。你最多可以选择 4 个互不重叠 的区间。所选择区间的 得分 定义为这些区间权重的总和。

返回一个至多包含 4 个下标且 字典序最小 的数组,表示从 intervals 中选中的互不重叠且得分最大的区间。

如果两个区间没有任何重叠点,则称二者 互不重叠 。特别地,如果两个区间共享左边界或右边界,也认为二者重叠。

示例 1:

输入: intervals = [[1,3,2],[4,5,2],[1,5,5],[6,9,3],[6,7,1],[8,9,1]]
输出: [2,3]
解释:
可以选择下标为 2 和 3 的区间,其权重分别为 5 和 3。

示例 2:

输入: intervals = [[5,8,1],[6,7,7],[4,7,3],[9,10,6],[7,8,2],[11,14,3],[3,5,5]]
输出: [1,3,5,6]
解释:
可以选择下标为 1、3、5 和 6 的区间,其权重分别为 7、6、3 和 5。

说明:

  • 1 <= intervals.length <= 5 * 10^4
  • intervals[i].length == 3
  • intervals[i] = [li, ri, weighti]
  • 1 <= li <= ri <= 10^9
  • 1 <= weighti <= 10^9

思路

// todo

代码

性能

3483.不同三位偶数的数目

目标

给你一个数字数组 digits,你需要从中选择三个数字组成一个三位偶数,你的任务是求出 不同 三位偶数的数量。

注意:每个数字在三位偶数中都只能使用 一次 ,并且 不能 有前导零。

示例 1:

输入: digits = [1,2,3,4]
输出: 12
解释: 可以形成的 12 个不同的三位偶数是 124,132,134,142,214,234,312,314,324,342,412 和 432。注意,不能形成 222,因为数字 2 只有一个。

示例 2:

输入: digits = [0,2,2]
输出: 2
解释: 可以形成的三位偶数是 202 和 220。注意,数字 2 可以使用两次,因为数组中有两个 2 。

示例 3:

输入: digits = [6,6,6]
输出: 1
解释: 只能形成 666。

示例 4:

输入: digits = [1,3,5]
输出: 0
解释: 无法形成三位偶数。

说明:

  • 3 <= digits.length <= 10
  • 0 <= digits[i] <= 9

思路

有一个数字数组 digits,数组元素为 0 ~ 9 ,从中选择 3 个组成一个三位偶数,求不同的三位偶数的个数。

从结果来考虑,三位数 100 ~ 999,针对每个数字判断能否由数组中的数字构成即可。

从构造的角度考虑,需要保证个位是偶数,且百位不是 0,要求数字不同,需要维护可用的数字种类,需要知道每个数字的已使用次数,可以使用回溯。

代码


/**
 * @date 2026-09-11 8:56
 */
public class TotalNumbers3483 {

    public int totalNumbers(int[] digits) {
        int[] d = new int[10];
        for (int num : digits) {
            d[num]++;
        }
        int res = 0;
        for (int num = 100; num < 999; num += 2) {
            if (valid(num, d)) {
                res++;
            }
        }
        return res;
    }

    public boolean valid(int num, int[] d) {
        int[] tmp = new int[10];
        while (num > 0) {
            tmp[num % 10]++;
            num /= 10;
        }
        for (int i = 0; i < 10; i++) {
            if (d[i] < tmp[i]) {
                return false;
            }
        }
        return true;
    }

    public int totalNumbers_v0(int[] digits) {
        int[] d = new int[10];
        for (int num : digits) {
            d[num]++;
        }
        int res = 0;
        // 枚举百位
        for (int i = 1; i < d.length; i++) {
            if (d[i] == 0) {
                continue;
            }
            d[i]--;
            res += dfs(0, i, d);
            d[i]++;
        }

        return res;
    }

    int dfs(int index, int num, int[] d) {
        if (index == 2) {
            // 第三个数如果是偶数返回 1
            return num % 2 == 0 ? 1 : 0;
        }
        int res = 0;
        // 枚举十位
        for (int i = 0; i < d.length; i++) {
            if (d[i] == 0) {
                continue;
            }
            d[i]--;
            res += dfs(index + 1, i, d);
            d[i]++;
        }
        return res;
    }

}

性能

2265.统计值等于子树平均值的节点数

目标

给你一棵二叉树的根节点 root ,找出并返回满足要求的节点数,要求节点的值等于其 子树 中值的 平均值 。

注意:

  • n 个元素的平均值可以由 n 个元素 求和 然后再除以 n ,并 向下舍入 到最近的整数。
  • root 的 子树 由 root 和它的所有后代组成。

示例 1:

输入:root = [4,8,5,0,1,null,6]
输出:5
解释:
对值为 4 的节点:子树的平均值 (4 + 8 + 5 + 0 + 1 + 6) / 6 = 24 / 6 = 4 。
对值为 5 的节点:子树的平均值 (5 + 6) / 2 = 11 / 2 = 5 。
对值为 0 的节点:子树的平均值 0 / 1 = 0 。
对值为 1 的节点:子树的平均值 1 / 1 = 1 。
对值为 6 的节点:子树的平均值 6 / 1 = 6 。

示例 2:

输入:root = [1]
输出:1
解释:对值为 1 的节点:子树的平均值 1 / 1 = 1。

说明:

  • 树中节点数目在范围 [1, 1000] 内
  • 0 <= Node.val <= 1000

思路

有一颗二叉树,统计其中节点值等于子树平均值(节点本身及其子树的节点值之和除以节点个数向下取整)的节点个数。

dfs 依题意统计即可。

代码


/**
 * @date 2026-09-10 8:56
 */
public class AverageOfSubtree2265 {

    int res = 0;

    public int averageOfSubtree(TreeNode root) {
        dfs(root);
        return res;
    }

    public class Dto {
        public int sum;
        public int num;

        public Dto() {
        }

        public Dto(int sum, int num) {
            this.sum = sum;
            this.num = num;
        }
    }

    public Dto dfs(TreeNode node) {
        if (node == null) {
            return new Dto();
        }
        Dto cur = new Dto(node.val, 1);
        Dto l = dfs(node.left);
        Dto r = dfs(node.right);
        cur.sum += l.sum + r.sum;
        cur.num += l.num + r.num;
        if (node.val == cur.sum / cur.num) {
            res++;
        }
        return cur;
    }
}
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */

性能

3871.统计范围内的逗号II

目标

给你一个整数 n。

返回将所有从 [1, n](包含两端)范围内的整数以 标准 数字格式书写时所用到的 逗号总数。

在 标准 格式中:

  • 从右边开始,每 三位 数字后插入一个逗号。
  • 位数 少于四位 的数字不包含逗号。

示例 1:

输入: n = 1002
输出: 3
解释:
数字 "1,000"、"1,001" 和 "1,002" 每个都包含一个逗号,总计 3 个逗号。

示例 2:

输入: n = 998
输出: 0
解释:
从 1 到 998 的所有数字位数都少于四位,因此没有使用逗号。

说明:

  • 1 <= n <= 10^15

思路

返回 [1, n] 之间所有整数的标准写法中总共有多少逗号。所谓标准写法指从右开始每 3 个数字插入一个逗号,且逗号不能位于开头。

  • 1,000 ~ 999,999 之间的数字有 1 个逗号
  • 1,000,000 ~ 999,999,999 之间的数字有 2 个逗号
  • 1,000,000,000 ~ 999,999,999,999 之间的数字有 3 个逗号
  • 1,000,000,000,000 ~ 999,999,999,999,999 之间的数字有 4 个逗号
  • 1,000,000,000,000,000 有 5 个逗号

返回 max(0, n - 999) + max(0, n - 999999) + max(0, n - 999999999) + max(0, n - 999999999999) + max(0, n - 999999999999999)。

可以写成循环的形式,for (long i = 1000; i <= n; i *= 1000) { res += n - i + 1; }。

代码


/**
 * @date 2026-09-08 9:15
 */
public class CountCommas3871 {

    public long countCommas(long n) {
        long res = 0;
        for (long i = 1000; i <= n; i *= 1000) {
            res += n - i + 1;
        }
        return res;
    }
}

性能

3870.统计范围内的逗号

目标

给你一个整数 n。

返回将所有从 [1, n](包含两端)范围内的整数以 标准 数字格式书写时所用到的 逗号总数。

在 标准 格式中:

  • 从右边开始,每 三位 数字后插入一个逗号。
  • 位数 少于四位 的数字不包含逗号。

示例 1:

输入: n = 1002
输出: 3
解释:
数字 "1,000"、"1,001" 和 "1,002" 每个都包含一个逗号,总计 3 个逗号。

示例 2:

输入: n = 998
输出: 0
解释:
从 1 到 998 的所有数字位数都少于四位,因此没有使用逗号。

说明:

  • 1 <= n <= 10^5

思路

返回 [1, n] 之间所有整数的标准写法中总共有多少逗号。所谓标准写法指从右开始每 3 个数字插入一个逗号,且逗号不能位于开头。

只需判断 1 ~ n 之间有多少个数字在 1,000 ~ 100,000 之间,返回 max(0, n - 999) 即可。

代码


/**
 * @date 2026-09-08 9:04
 */
public class CountCommas3870 {

    public int countCommas(int n) {
        return Math.max(0, n - 999);
    }
}

性能

940.不同的子序列II

目标

给定一个字符串 s,计算 s 的 不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 。

字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

例如,"ace" 是 "abcde" 的一个子序列,但 "aec" 不是。

示例 1:

输入:s = "abc"
输出:7
解释:7 个不同的子序列分别是 "a", "b", "c", "ab", "ac", "bc", 以及 "abc"。

示例 2:

输入:s = "aba"
输出:6
解释:6 个不同的子序列分别是 "a", "b", "ab", "ba", "aa" 以及 "aba"。

示例 3:

输入:s = "aaa"
输出:3
解释:3 个不同的子序列分别是 "a", "aa" 以及 "aaa"。

说明:

  • 1 <= s.length <= 2000
  • s 仅由小写英文字母组成

思路

返回字符串 s 不重复的非空子序列的个数。

定义 dp[i + 1][j] 表示 [0, i] 中以字母 j 结尾的子序列个数。使用当前字母作为子序列右端点的方案数为 1 + Σdp[i][j],即 当前字母自身 加上 与之前的方案拼接。其它右端点的方案数与之前方案数相同 dp[i + 1][j] = dp[i][j]。

代码


/**
 * @date 2026-09-08 14:54
 */
public class DistinctSubseqII940 {

    public int distinctSubseqII(String s) {
        int n = s.length();
        int MOD = 1000000007;
        int[][] dp = new int[n + 1][26];
        for (int i = 0; i < n; i++) {
            int sum = 0;
            int c = s.charAt(i) - 'a';
            for (int j = 0; j < 26; j++) {
                dp[i + 1][j] = dp[i][j];
                sum = (sum + dp[i][j]) % MOD;
            }
            dp[i + 1][c] = 1 + sum;
        }
        int res = 0;
        for (int num : dp[n]) {
            res = (res + num) % MOD;
        }
        return res;
    }
}

性能