3875.构造奇偶一致的数组I

目标

给你一个长度为 n 的数组 nums1,其中包含 互不相同 的整数。

你需要构造另一个长度为 n 的数组 nums2,使得 nums2 中的元素要么全部为 奇数,要么全部为 偶数。

对于每个下标 i,你必须从以下两种选择中 任选其一(顺序不限):

  • nums2[i] = nums1[i]
  • nums2[i] = nums1[i] - nums1[j],其中 j != i

如果能够构造出满足条件的数组,则返回 true;否则,返回 false。

示例 1:

输入: nums1 = [2,3]
输出: true
解释:
    选择 nums2[0] = nums1[0] - nums1[1] = 2 - 3 = -1。
    选择 nums2[1] = nums1[1] = 3。
    nums2 = [-1, 3],两个元素均为奇数。因此答案为 true。

示例 2:

输入: nums1 = [4,6]
输出: true
解释:​​​​​​​
    选择 nums2[0] = nums1[0] = 4。
    选择 nums2[1] = nums1[1] = 6。
    nums2 = [4, 6],两个元素均为偶数。因此答案为 true。

说明:

  • 1 <= n == nums1.length <= 100
  • 1 <= nums1[i] <= 100
  • nums1 中的所有整数互不相同。

思路

有一个元素互不相同的整数数组 nums1,问能否构造另一个相同长度的数组 nums2nums2[i] = nums1[i] 或者 nums2[i] = nums1[i] - nums1[j],其中 j != i。使得 nums2 中的元素全为 奇数偶数

如果数组元素均为奇数/偶数直接满足条件,否则,可以将偶数减去任意奇数,使得整个数组变为奇数。直接返回 true 即可。

代码


/**
 * @date 2026-09-02 9:17
 */
public class UniformArray3875 {

    public boolean uniformArray(int[] nums1) {
        return true;
    }
}

性能

3568.清理教室的最少移动

目标

给你一个 m x n 的网格图 classroom,其中一个学生志愿者负责清理散布在教室里的垃圾。网格图中的每个单元格是以下字符之一:

  • 'S' :学生的起始位置
  • 'L' :必须收集的垃圾(收集后,该单元格变为空白)
  • 'R' :重置区域,可以将学生的能量恢复到最大值,无论学生当前的能量是多少(可以多次使用)
  • 'X' :学生无法通过的障碍物
  • '.' :空白空间

同时给你一个整数 energy,表示学生的最大能量容量。学生从起始位置 'S' 开始,带着 energy 的能量出发。

每次移动到相邻的单元格(上、下、左或右)会消耗 1 单位能量。如果能量为 0,学生此时只有处在 'R' 格子时可以继续移动,此区域会将能量恢复到 最大 能量值 energy。

返回收集所有垃圾所需的 最少 移动次数,如果无法完成,返回 -1。

示例 1:

输入: classroom = ["S.", "XL"], energy = 2
输出: 2
解释:
学生从单元格 (0, 0) 开始,带着 2 单位的能量。
由于单元格 (1, 0) 有一个障碍物 'X',学生无法直接向下移动。
收集所有垃圾的有效移动序列如下:
移动 1:从 (0, 0) → (0, 1),消耗 1 单位能量,剩余 1 单位。
移动 2:从 (0, 1) → (1, 1),收集垃圾 'L'。
学生通过 2 次移动收集了所有垃圾。因此,输出为 2。

示例 2:

输入: classroom = ["LS", "RL"], energy = 4
输出: 3
解释:
学生从单元格 (0, 1) 开始,带着 4 单位的能量。
收集所有垃圾的有效移动序列如下:
移动 1:从 (0, 1) → (0, 0),收集第一个垃圾 'L',消耗 1 单位能量,剩余 3 单位。
移动 2:从 (0, 0) → (1, 0),到达 'R' 重置区域,恢复能量为 4。
移动 3:从 (1, 0) → (1, 1),收集第二个垃圾 'L'。
学生通过 3 次移动收集了所有垃圾。因此,输出是 3。

示例 3:

输入: classroom = ["L.S", "RXL"], energy = 3
输出: -1
解释:
没有有效路径可以收集所有 'L'。

说明:

  • 1 <= m == classroom.length <= 20
  • 1 <= n == classroom[i].length <= 20
  • classroom[i][j] 是 'S'、'L'、'R'、'X' 或 '.' 之一
  • 1 <= energy <= 50
  • 网格图中恰好有 一个 'S'。
  • 网格图中 最多 有 10 个 'L' 单元格。

思路

学生从起点出发清理教室的垃圾,初始能量为 energy,向上下左右四个方向移动需要消耗 1 能量。教室里有障碍物,学生不能移动到障碍物的格子。能量重置点可以恢复能量到初始值。返回从起点出发清理所有垃圾所需的最少移动。

考虑使用 BFS,维护状态 (x, y, e, mask),使用四维数组维护是否处理过。

代码


/**
 * @date 2026-09-01 9:38
 */
public class MinMoves3568 {

    public int minMoves(String[] classroom, int energy) {
        int sx = -1, sy = -1;
        int m = classroom.length;
        int n = classroom[0].length();
        int[][] directions = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        int[][] grid = new int[m][n];
        int no = 1;
        int all = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                char c = classroom[i].charAt(j);
                if (c == 'S') {
                    sy = j;
                    sx = i;
                } else if (c == 'L') {
                    all |= 1 << no;
                    grid[i][j] = no++;
                } else if (c == 'X') {
                    grid[i][j] = -1;
                } else if (c == 'R') {
                    grid[i][j] = -2;
                }
            }
        }
        boolean[][][][] visited = new boolean[m][n][energy + 1][all + 1];
        Deque<int[]> q = new ArrayDeque<>();
        q.offer(new int[]{sx, sy, energy, 0});
        int res = 0;
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int[] p = q.poll();
                if (visited[p[0]][p[1]][p[2]][p[3]]) {
                    continue;
                }
                visited[p[0]][p[1]][p[2]][p[3]] = true;
                if (p[3] == all) {
                    return res;
                }
                if (p[2] == 0) {
                    continue;
                }
                for (int[] d : directions) {
                    int nx = p[0] + d[0];
                    int ny = p[1] + d[1];
                    int e = p[2];
                    int mask = p[3];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] != -1) {
                        if (grid[nx][ny] > 0) {
                            e--;
                            mask |= 1 << grid[nx][ny];
                        } else if (grid[nx][ny] == -2) {
                            e = energy;
                        } else {
                            e--;
                        }
                        if (!visited[nx][ny][e][mask]) {
                            q.offer(new int[]{nx, ny, e, mask});
                        }
                    }
                }
            }
            res++;
        }
        return -1;
    }

}

性能

3090.每个字符最多出现两次的最长子字符串

目标

给你一个字符串 s ,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的 最大 长度。

示例 1:

输入: s = "bcbbbcba"
输出: 4
解释:
以下子字符串长度为 4,并且每个字符最多出现两次:"bcbbbcba"。

示例 2:

输入: s = "aaaa"
输出: 2
解释:
以下子字符串长度为 2,并且每个字符最多出现两次:"aaaa"。

说明:

  • 2 <= s.length <= 100
  • s 仅由小写英文字母组成。

思路

返回字符串 s 所有子串中每个字符出现次数不超过两次的最长子串长度。

滑动窗口。

代码


/**
 * @date 2026-08-14 8:44
 */
public class MaximumLengthSubstring3090 {

    public int maximumLengthSubstring(String s) {
        int n = s.length();
        int[] cnt = new int[26];
        int l = 0;
        int res = 0;
        for (int r = 0; r < n; r++) {
            int i = s.charAt(r) - 'a';
            cnt[i]++;
            while (cnt[i] > 2){
                cnt[s.charAt(l++) - 'a']--;
            }
            res = Math.max(res, r - l + 1);
        }
        return res;
    }
}

性能

2958.最多K个重复元素的最长子数组

目标

给你一个整数数组 nums 和一个整数 k 。

一个元素 x 在数组中的 频率 指的是它在数组中的出现次数。

如果一个数组中所有元素的频率都 小于等于 k ,那么我们称这个数组是 好 数组。

请你返回 nums 中 最长好 子数组的长度。

子数组 指的是一个数组中一段连续非空的元素序列。

示例 1:

输入:nums = [1,2,3,1,2,3,1,2], k = 2
输出:6
解释:最长好子数组是 [1,2,3,1,2,3] ,值 1 ,2 和 3 在子数组中的频率都没有超过 k = 2 。[2,3,1,2,3,1] 和 [3,1,2,3,1,2] 也是好子数组。
最长好子数组的长度为 6 。

示例 2:

输入:nums = [1,2,1,2,1,2,1,2], k = 1
输出:2
解释:最长好子数组是 [1,2] ,值 1 和 2 在子数组中的频率都没有超过 k = 1 。[2,1] 也是好子数组。
最长好子数组的长度为 2 。

示例 3:

输入:nums = [5,5,5,5,5,5,5], k = 4
输出:4
解释:最长好子数组是 [5,5,5,5] ,值 5 在子数组中的频率没有超过 k = 4 。
最长好子数组的长度为 4 。

说明:

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

思路

定义好子数组是元素频次不超过 k 的子数组,返回整数数组的最长好子数组长度。

滑动窗口。

代码


/**
 * @date 2026-08-12 9:19
 */
public class MaxSubarrayLength2958 {

    public int maxSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        Map<Integer, Integer> cnt = new HashMap<>();
        int l = 0;
        int res = 0;
        for (int r = 0; r < n; r++) {
            cnt.merge(nums[r], 1, Integer::sum);
            while (cnt.get(nums[r]) > k) {
                cnt.merge(nums[l++], -1, Integer::sum);
            }
            res = Math.max(res, r - l + 1);
        }
        return res;
    }

}

性能

2996.大于等于顺序前缀和的最小缺失整数

目标

给你一个下标从 0 开始的整数数组 nums 。

如果一个前缀 nums[0..i] 满足对于 1 <= j <= i 的所有元素都有 nums[j] = nums[j - 1] + 1 ,那么我们称这个前缀是一个 顺序前缀 。特殊情况是,只包含 nums[0] 的前缀也是一个 顺序前缀 。

请你返回 nums 中没有出现过的 最小 整数 x ,满足 x 大于等于 最长 顺序前缀的和。

示例 1:

输入:nums = [1,2,3,2,5]
输出:6
解释:nums 的最长顺序前缀是 [1,2,3] ,和为 6 ,6 不在数组中,所以 6 是大于等于最长顺序前缀和的最小整数。

示例 2:

输入:nums = [3,4,5,1,12,14,13]
输出:15
解释:nums 的最长顺序前缀是 [3,4,5] ,和为 12 ,12、13 和 14 都在数组中,但 15 不在,所以 15 是大于等于最长顺序前缀和的最小整数。

说明:

  • 1 <= nums.length <= 50
  • 1 <= nums[i] <= 50

思路

定义数组 nums 的顺序前缀是满足 1 <= j <= i, nums[j] = nums[j - 1] + 1 的前缀,返回大于等于数组顺序前缀和且没有出现在数组的最小整数。

使用哈希表保存数组所有元素,计算顺序前缀和 sum,从 sum 返回第一个不在哈希表中的整数即可。

代码


/**
 * @date 2026-08-11 8:58
 */
public class MissingInteger2996 {

    public int missingInteger(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return nums[0] + 1;
        }
        Set<Integer> set = Arrays.stream(nums).boxed().collect(Collectors.toSet());
        int sum = nums[0];
        for (int i = 1; i < n; i++) {
            if (nums[i] != nums[i - 1] + 1) {
                break;
            }
            sum += nums[i];
        }
        while (set.contains(sum)) {
            sum++;
        }
        return sum;
    }
}

性能

3310.移除可疑的方法

目标

你正在维护一个项目,该项目有 n 个方法,编号从 0 到 n - 1。

给你两个整数 n 和 k,以及一个二维整数数组 invocations,其中 invocations[i] = [ai, bi] 表示方法 ai 调用了方法 bi。

已知如果方法 k 存在一个已知的 bug。那么方法 k 以及它直接或间接调用的任何方法都被视为 可疑方法 ,我们需要从项目中移除这些方法。

只有当一组方法没有被这组之外的任何方法调用时,这组方法才能被移除。

返回一个数组,包含移除所有 可疑方法 后剩下的所有方法。你可以以任意顺序返回答案。如果无法移除 所有 可疑方法,则 不 移除任何方法。

示例 1:

输入: n = 4, k = 1, invocations = [[1,2],[0,1],[3,2]]
输出: [0,1,2,3]
解释:
方法 2 和方法 1 是可疑方法,但它们分别直接被方法 3 和方法 0 调用。由于方法 3 和方法 0 不是可疑方法,我们无法移除任何方法,故返回所有方法。

示例 2:

输入: n = 5, k = 0, invocations = [[1,2],[0,2],[0,1],[3,4]]
输出: [3,4]
解释:
方法 0、方法 1 和方法 2 是可疑方法,且没有被任何其他方法直接调用。我们可以移除它们。

示例 3:

输入: n = 3, k = 2, invocations = [[1,2],[0,1],[2,0]]
输出: []
解释:
所有方法都是可疑方法。我们可以移除它们。

说明:

  • 1 <= n <= 10^5
  • 0 <= k <= n - 1
  • 0 <= invocations.length <= 2 * 10^5
  • invocations[i] == [ai, bi]
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • invocations[i] != invocations[j]

思路

n 个方法,编号为 0 ~ n - 1invocations[i] = [ai, bi] 表示方法 ai 调用了方法 bi。已知方法 k 是可疑的,所有被 k 直接或间接调用的方法也都是可疑的。这些可疑方法如果没有被其它非可疑方法调用可以全部移除,返回剩余的方法。

先标记 k 直接或间接调用的方法,再从所有非可疑方法出发,判断是否会调用可疑方法。如果会调用,不可移除,否则直接返回所有非可疑方法。

代码


/**
 * @date 2026-08-05 9:17
 */
public class RemainingMethods3310 {

    public List<Integer> remainingMethods(int n, int k, int[][] invocations) {
        List<Integer>[] g = new ArrayList[n];
        Arrays.setAll(g, x -> new ArrayList<>());
        for (int[] i : invocations) {
            g[i[0]].add(i[1]);
        }
        boolean[] remove = new boolean[n];
        boolean[] visited = new boolean[n];
        dfs(k, g, remove);
        boolean canRemove = true;
        for (int i = 0; i < n; i++) {
            if (remove[i]) {
                continue;
            }
            if (dfs(i, g, visited, remove)) {
                canRemove = false;
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (!canRemove) {
                res.add(i);
            } else if (!remove[i]) {
                res.add(i);
            }
        }
        return res;
    }

    public void dfs(int m, List<Integer>[] g, boolean[] remove) {
        if (remove[m]) {
            return;
        }
        remove[m] = true;
        for (Integer next : g[m]) {
            dfs(next, g, remove);
        }
    }

    public boolean dfs(int m, List<Integer>[] g, boolean[] visited, boolean[] remove) {
        visited[m] = true;
        if (remove[m]) {
            return true;
        }
        boolean res = false;
        for (Integer next : g[m]) {
            if (!visited[next]) {
                res = res || dfs(next, g, visited, remove);
            }
        }
        return res;
    }

}

性能

3731.找出缺失的元素

目标

给你一个整数数组 nums ,数组由若干 互不相同 的整数组成。

数组 nums 原本包含了某个范围内的 所有整数 。但现在,其中可能 缺失 部分整数。

该范围内的 最小 整数和 最大 整数仍然存在于 nums 中。

返回一个 有序 列表,包含该范围内缺失的所有整数,并 按从小到大排序。如果没有缺失的整数,返回一个 空 列表。

示例 1:

输入: nums = [1,4,2,5]
输出: [3]
解释:
最小整数为 1,最大整数为 5,因此完整的范围应为 [1,2,3,4,5]。其中只有 3 缺失。

示例 2:

输入: nums = [7,8,6,9]
输出: []
解释:
最小整数为 6,最大整数为 9,因此完整的范围为 [6,7,8,9]。所有整数均已存在,因此没有缺失的整数。

示例 3:

输入: nums = [5,1]
输出: [2,3,4]
解释:
最小整数为 1,最大整数为 5,因此完整的范围应为 [1,2,3,4,5]。缺失的整数为 2、3 和 4。

说明:

  • 2 <= nums.length <= 100
  • 1 <= nums[i] <= 100

思路

有一个元素互不相同的数组,原本包含了 [min, max] 之间的所有整数,现在缺失了 (min, max) 中的一些元素,找到并返回缺失的元素。

首先找到数组的 minmax,将元素放入哈希表,遍历 (min, max) 判断数字是否在集合中。

代码


/**
 * @date 2026-08-04 8:49
 */
public class FindMissingElements3731 {

    public List<Integer> findMissingElements(int[] nums) {
        int min = 101, max = 0;
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {
            min = Math.min(min, num);
            max = Math.max(max, num);
            set.add(num);
        }
        List<Integer> res = new ArrayList<>();
        for (int i = min + 1; i < max; i++) {
            if (!set.contains(i)) {
                res.add(i);
            }
        }
        return res;
    }
}

性能

1406.石子游戏III

目标

Alice 和 Bob 继续他们的石子游戏。几堆石子 排成一行 ,每堆石子都对应一个得分,由数组 stoneValue 给出。

Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,该玩家可以拿走剩下石子中的的前 1、2 或 3 堆石子 。比赛一直持续到所有石头都被拿走。

每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0 。

比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。

假设 Alice 和 Bob 都采取 最优策略 。

如果 Alice 赢了就返回 "Alice" ,Bob 赢了就返回 "Bob",分数相同返回 "Tie" 。

示例 1:

输入:values = [1,2,3,7]
输出:"Bob"
解释:Alice 总是会输,她的最佳选择是拿走前三堆,得分变成 6 。但是 Bob 的得分为 7,Bob 获胜。

示例 2:

输入:values = [1,2,3,-9]
输出:"Alice"
解释:Alice 要想获胜就必须在第一个回合拿走前三堆石子,给 Bob 留下负分。
如果 Alice 只拿走第一堆,那么她的得分为 1,接下来 Bob 拿走第二、三堆,得分为 5 。之后 Alice 只能拿到分数 -9 的石子堆,输掉比赛。
如果 Alice 拿走前两堆,那么她的得分为 3,接下来 Bob 拿走第三堆,得分为 3 。之后 Alice 只能拿到分数 -9 的石子堆,同样会输掉比赛。
注意,他们都应该采取 最优策略 ,所以在这里 Alice 将选择能够使她获胜的方案。

示例 3:

输入:values = [1,2,3,6]
输出:"Tie"
解释:Alice 无法赢得比赛。如果她决定选择前三堆,她可以以平局结束比赛,否则她就会输。

说明:

  • 1 <= stoneValue.length <= 5 * 10^4
  • -1000 <= stoneValue[i] <= 1000

思路

几堆石子排成一排,stoneValue[i] 表示第 i 堆石子的个数。Alice 与 Bob 轮流取石子,Alice 先取,每次可以从剩余的石子堆中取走前 12 或者 3 堆石子,直到石子均被取走。返回结束时取走石子最多的玩家姓名,如果相同则返回 Tie

定义 dp[i] 表示剩余 [i, n - 1] 堆石子,先手与后手所能获得的石子个数之差的最大值。dp[i] = max(sumj - dp[j + 1]),其中 j ∈ [i, min(i + 2, n - 1)]sumj 表示 [i, j] 的石子堆数量之和。倒序遍历,初始化 dp[n] = 0, dp[n - 1] = stoneValue[n - 1]

代码


/**
 * @date 2026-08-03 15:22
 */
public class StoneGameIII1406 {

    public String stoneGameIII(int[] stoneValue) {
        int n = stoneValue.length;
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MIN_VALUE);
        dp[n] = 0;
        dp[n - 1] = stoneValue[n - 1];
        for (int i = n - 1; i >= 0; i--) {
            int sum = 0;
            for (int j = i; j < Math.min(n, i + 3); j++) {
                sum += stoneValue[j];
                dp[i] = Math.max(dp[i], sum - dp[j + 1]);
            }
        }
        if (dp[0] == 0) {
            return "Tie";
        }
        return dp[0] > 0 ? "Alice" : "Bob";
    }
}

性能

877.石子游戏

目标

Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子,排成一行;每堆都有 正 整数颗石子,数目为 piles[i] 。

游戏以谁手中的石子最多来决出胜负。石子的 总数 是 奇数 ,所以没有平局。

Alice 和 Bob 轮流进行,Alice 先开始 。 每回合,玩家从行的 开始 或 结束 处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中 石子最多 的玩家 获胜 。

假设 Alice 和 Bob 都发挥出最佳水平,当 Alice 赢得比赛时返回 true ,当 Bob 赢得比赛时返回 false 。

示例 1:

输入:piles = [5,3,4,5]
输出:true
解释:
Alice 先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果 Bob 拿走前 3 颗,那么剩下的是 [4,5],Alice 拿走后 5 颗赢得 10 分。
如果 Bob 拿走后 5 颗,那么剩下的是 [3,4],Alice 拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对 Alice 来说是一个胜利的举动,所以返回 true 。

示例 2:

输入:piles = [3,7,2,3]
输出:true

说明:

  • 2 <= piles.length <= 500
  • piles.length 是 偶数
  • 1 <= piles[i] <= 500
  • sum(piles[i]) 是 奇数

思路

有偶数堆石子排成一行,piles[i] 表示第 i 堆石子的数量,石子总数为奇数。从 Alice 开始,与 Bob 轮流取走左侧或右侧的整堆石子,直到取走所有石子堆为止。获得石子最多的玩家获胜。假设 AliceBob 都能做出最优的选择,判断 Alice 能否获胜。

定义 dp[i][j] 表示剩余石堆为 [i, j] 时,先手与后手获得的石子数量之差的最大值。dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])

  • 先手 A 选择 i,剩余问题变成 B 先手 [i + 1, j],而当前问题的先手是 A,剩余问题则是 B - A,应该取相反数。
  • 先手 A 选择 j 同理。

外层倒序遍历,内存正序遍历。因为 i 依赖 i + 1j 依赖 j - 1。初始化 dp[i][i] = pilesp[i]

本题使用了空间优化,当前状态是从下方与左侧转移而来,从右下角到左上对角线开始,从左向右更新。每次状态更新只用到了下方与左侧的值,因此可以使用滚动数组保存下方的值。

先手玩家必定可以获胜,首先石子总数为奇数(不可能平局),奇数堆与偶数堆的石子数量必定不同,先手可以控制自己只选择偶数堆或者奇数堆,因此必胜。

因为开始时首尾的奇偶性不同,偶,……,奇,如果奇数堆的石子更多,那么先手就选奇数堆,这时留给后手的只有首尾的两个偶数下标。

代码


/**
 * @date 2026-08-03 14:45
 */
public class StoneGame877 {

    public boolean stoneGame_v1(int[] piles) {
        return true;
    }

    public boolean stoneGame(int[] piles) {
        int n = piles.length;
        int[] dp = new int[n];
        for (int i = n - 1; i >= 0; i--) {
            dp[i] = piles[i];
            for (int j = i + 1; j < n; j++) {
                dp[j] = Math.max(piles[i] - dp[j], piles[j] - dp[j - 1]);
            }
        }
        return dp[n - 1] > 0;
    }
}

性能

486.预测赢家

目标

给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。

玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手。开始时,两个玩家的初始分值都是 0 。每一回合,玩家从数组的任意一端取一个数字(即,nums[0] 或 nums[nums.length - 1]),取到的数字将会从数组中移除(数组长度减 1 )。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。

如果玩家 1 能成为赢家,返回 true 。如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true 。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入:nums = [1,5,2]
输出:false
解释:一开始,玩家 1 可以从 1 和 2 中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。 
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 false 。

示例 2:

输入:nums = [1,5,233,7]
输出:true
解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 5 和 7 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。
最终,玩家 1(234 分)比玩家 2(12 分)获得更多的分数,所以返回 true,表示玩家 1 可以成为赢家。

说明:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 10^7

思路

有一个整数数组 nums玩家1 先手,从数组任意一端取走一个元素累加到他的得分上,最终积分高的玩家获胜(如果积分相同则先手玩家获胜),每个玩家的玩法都会使他的积分最大,判断 玩家1 能否取得胜利。

定义 dp[i][j] 表示子数组 nums[i:j] 先手玩家与后手玩家所能获得的最大积分之差的最大值。dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])

代码


/**
 * @date 2026-08-03 14:12
 */
public class PredictTheWinner486 {

    public boolean predictTheWinner_v1(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = nums[i];
            for (int j = i + 1; j < n; j++) {
                dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
            }
        }
        return dp[0][n - 1] >= 0;
    }

}

性能