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;
    }
}

性能

115.不同的子序列

目标

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。

测试用例保证结果在 32 位有符号整数范围内。

示例 1:

输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit
rabbbit
rabbbit

示例 2:

输入:s = "babgbag", t = "bag"
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
babgbag
babgbag
babgbag
babgbag
babgbag

说明:

  • 1 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

思路

有两个字符串 s 和 t,返回 s 的子序列中 t 的出现个数。

定义 dp[i][j] 表示 s[i, s.length - 1] 中 t[j, t.length - 1] 的子序列个数。dp[i][j] = (s[i] == t[j] ? dp[i + 1][j + 1] : 0) + dp[i + 1][j]。t 为空串时,初始化 dp[i][tl] = 1。

代码


/**
 * @date 2026-09-07 10:32
 */
public class NumDistinct115 {

    public int numDistinct(String s, String t) {
        int sl = s.length();
        int tl = t.length();
        char[] scs = s.toCharArray();
        char[] tcs = t.toCharArray();
        int[][] dp = new int[sl + 1][tl + 1];
        for (int i = 0; i <= sl; i++) {
            dp[i][tl] = 1;
        }
        for (int i = sl - 1; i >= 0; i--) {
            for (int j = tl - 1; j >= 0; j--) {
                dp[i][j] = (scs[i] == tcs[j] ? dp[i + 1][j + 1] : 0) + dp[i + 1][j];
            }
        }
        return dp[0][0];
    }

}

性能

3904.最小稳定下标II

目标

给你一个长度为 n 的整数数组 nums 和一个整数 k。

对于每个下标 i,定义它的 不稳定值 为 max(nums[0..i]) - min(nums[i..n - 1])。

换句话说:

  • max(nums[0..i]) 表示从下标 0 到下标 i 的元素中的 最大值 。
  • min(nums[i..n - 1]) 表示从下标 i 到下标 n - 1 的元素中的 最小值 。

如果某个下标 i 的不稳定值 小于等于 k,则称该下标为 稳定下标 。

返回 最小 的稳定下标。如果不存在这样的下标,则返回 -1。

示例 1:

输入: nums = [5,0,1,4], k = 3
输出: 3
解释:
在下标 0 处:[5] 中的最大值是 5,[5, 0, 1, 4] 中的最小值是 0,因此不稳定值为 5 - 0 = 5。
在下标 1 处:[5, 0] 中的最大值是 5,[0, 1, 4] 中的最小值是 0,因此不稳定值为 5 - 0 = 5。
在下标 2 处:[5, 0, 1] 中的最大值是 5,[1, 4] 中的最小值是 1,因此不稳定值为 5 - 1 = 4。
在下标 3 处:[5, 0, 1, 4] 中的最大值是 5,[4] 中的最小值是 4,因此不稳定值为 5 - 4 = 1。
这是第一个不稳定值小于等于 k = 3 的下标,因此答案是 3。

示例 2:

输入: nums = [3,2,1], k = 1
输出: -1
解释:
在下标 0 处,不稳定值为 3 - 1 = 2。
在下标 1 处,不稳定值为 3 - 1 = 2。
在下标 2 处,不稳定值为 3 - 1 = 2。
这些值都不小于等于 k = 1,因此答案是 -1。

示例 3:

输入: nums = [0], k = 0
输出: 0
解释:
在下标 0 处,不稳定值为 0 - 0 = 0,它小于等于 k = 0。因此答案是 0。

说明:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^9
  • 0 <= k <= 10^9

思路

与 3903.最小稳定下标I 相比数据范围扩大了。

代码


/**
 * @date 2026-09-07 9:01
 */
public class FirstStableIndex3904 {

    public int firstStableIndex(int[] nums, int k) {
        int n = nums.length;
        int[] suffix = new int[n + 1];
        Arrays.fill(suffix, Integer.MAX_VALUE);
        for (int i = n - 1; i >= 0; i--) {
            suffix[i] = Math.min(suffix[i + 1], nums[i]);
        }
        int max = 0;
        for (int i = 0; i < n; i++) {
            max = Math.max(max, nums[i]);
            if (max - suffix[i] <= k) {
                return i;
            }
        }
        return -1;
    }
}

性能