135.分发糖果

目标

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

说明:

  • n == ratings.length
  • 1 <= n <= 2 * 10^4
  • 0 <= ratings[i] <= 2 * 10^4

思路

n 个孩子站成一排,ratings 表示每个孩子的评分。现在需要给孩子分发糖果,要求每个孩子至少分配一个糖果,并且相邻的两个孩子,评分高的孩子分得的糖果大于评分低的孩子分得的糖果,返回需要准备的最少糖果数目。

官网题解提供了一种常数空间复杂度的写法,记录评分严格递增与严格递减序列长度:

  • 当处于严格递增序列时,分配的糖果比前面的多一个即可
  • 如果评分相同,则分配一个糖果并重置序列长度
  • 当处于严格递减序列时,分配递减序列长度个糖果,相当于递增序列的逆过程
  • 需要特殊处理递增序列与递减序列长度相同的情况

看下面的例子:

评分 4 5 4 3 2 1,
    a b c d e f
    1 2 1
当处理到 c 时,处于下降序列,我们给他分配 1 个糖果
    1 3 2 1
当处理到 d 时,仍处于下降序列,需要给 c 多分配一个糖果,即 2 个,发现这时与 b 分配的相同,不满足要求,需要给 b 也多分配一个,共 3 个
    1 4 3 2 1
当处理到 e 时,仍处于下降序列,需要给 b c d 多分配一个,共 4 个
    1 5 4 3 2 1
当处理到 f 时,仍处于下降序列,需要给 b c d e 多分配一个糖果,共 5 个

相当于往递减序列的头部插入递减序列长度个糖果,视为反向的递增,当递增序列与递减序列长度相同时需要特殊处理。

代码


/**
 * @date 2024-12-11 9:18
 */
public class Candy135 {

    public int candy_v3(int[] ratings) {
        int n = ratings.length;
        int increase = 1;
        int decrease = 0;
        int res = 1;
        int prev = 1;
        for (int i = 1; i < n; i++) {
            if (ratings[i] >= ratings[i - 1]) {
                prev = ratings[i] == ratings[i - 1] ? 1 : prev + 1;
                res += prev;
                increase = prev;
                decrease = 0;
            } else {
                decrease++;
                if (decrease == increase) {
                    decrease++;
                }
                res += decrease;
                prev = 1;
            }
        }
        return res;
    }

}

性能

2929.给小朋友们分糖果II

目标

给你两个正整数 n 和 limit 。

请你将 n 颗糖果分给 3 位小朋友,确保没有任何小朋友得到超过 limit 颗糖果,请你返回满足此条件下的 总方案数 。

示例 1:

输入:n = 5, limit = 2
输出:3
解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。

示例 2:

输入:n = 3, limit = 3
输出:10
解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。

说明:

  • 1 <= n <= 10^6
  • 1 <= limit <= 10^6

思路

n 颗糖果分给 3 位小朋友,每个小朋友分到的糖果数量不超过 limit,求分配的方案数。注意,糖果必须分完,比如示例 1,不存在分得糖果数量为 0 的情况。

  • 第一个小朋友分到的糖果数为 a ∈ [0, Math.min(n, limit)]
  • 第二个小朋友分到的糖果数为 b ∈ [Math.max(0, n - a - limit), Math.min(n - a, limit)]
  • 第三个小朋友分到的糖果数为 n - a - b

代码


/**
 * @date 2025-06-01 21:49
 */
public class DistributeCandies2929 {

    public long distributeCandies(int n, int limit) {
        long res = 0L;
        for (int i = 0; i <= Math.min(n, limit); i++) {
            if (n - i > 2 * limit) {
                continue;
            }
            res += Math.min(n - i, limit) - Math.max(0, n - i - limit) + 1;
        }
        return res;
    }
}

性能

909.蛇梯棋

目标

给你一个大小为 n x n 的整数矩阵 board ,方格按从 1 到 n2 编号,编号遵循 转行交替方式 ,从左下角开始 (即,从 board[n - 1][0] 开始)的每一行改变方向。

你一开始位于棋盘上的方格 1。每一回合,玩家需要从当前方格 curr 开始出发,按下述要求前进:

  • 选定目标方格 next ,目标方格的编号在范围 [curr + 1, min(curr + 6, n2)] 。
    • 该选择模拟了掷 六面体骰子 的情景,无论棋盘大小如何,玩家最多只能有 6 个目的地。
  • 传送玩家:如果目标方格 next 处存在蛇或梯子,那么玩家会传送到蛇或梯子的目的地。否则,玩家传送到目标方格 next 。
  • 当玩家到达编号 n2 的方格时,游戏结束。

如果 board[r][c] != -1 ,位于 r 行 c 列的棋盘格中可能存在 “蛇” 或 “梯子”。那个蛇或梯子的目的地将会是 board[r][c]。编号为 1 和 n2 的方格不是任何蛇或梯子的起点。

注意,玩家在每次掷骰的前进过程中最多只能爬过蛇或梯子一次:就算目的地是另一条蛇或梯子的起点,玩家也 不能 继续移动。

  • 举个例子,假设棋盘是 [[-1,4],[-1,3]] ,第一次移动,玩家的目标方格是 2 。那么这个玩家将会顺着梯子到达方格 3 ,但 不能 顺着方格 3 上的梯子前往方格 4 。(简单来说,类似飞行棋,玩家掷出骰子点数后移动对应格数,遇到单向的路径(即梯子或蛇)可以直接跳到路径的终点,但如果多个路径首尾相连,也不能连续跳多个路径)
-1(4) 4(3)
-1(1) 3(2)

返回达到编号为 n2 的方格所需的最少掷骰次数,如果不可能,则返回 -1。

示例 1:

输入:board = [[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,35,-1,-1,13,-1],[-1,-1,-1,-1,-1,-1],[-1,15,-1,-1,-1,-1]]
输出:4
解释:
首先,从方格 1 [第 5 行,第 0 列] 开始。 
先决定移动到方格 2 ,并必须爬过梯子移动到到方格 15 。
然后决定移动到方格 17 [第 3 行,第 4 列],必须爬过蛇到方格 13 。
接着决定移动到方格 14 ,且必须通过梯子移动到方格 35 。 
最后决定移动到方格 36 , 游戏结束。 
可以证明需要至少 4 次移动才能到达最后一个方格,所以答案是 4 。 

示例 2:

输入:board = [[-1,-1],[-1,3]]
输出:1

说明:

  • n == board.length == board[i].length
  • 2 <= n <= 20
  • board[i][j] 的值是 -1 或在范围 [1, n^2]
  • 编号为 1 和 n^2 的方格上没有蛇或梯子

思路

有一个 n x n 棋盘 board,左下角从 1 开始编号,换行时改变编号顺序,例如 4 x 4 棋盘格子编号如下:

16  15  14  13
 9  10  11  12
 8   7   6   5
 1   2   3   4

玩家投骰子 1 ~ 6,根据点数移动到编号为 cur + point 的格子上,如果目标格子的值不为 -1,则传送到对应的格子编号。求到达终点所需的最小步数。

首先要解决棋盘编号与格子坐标的转换关系,然后使用 bfs 尝试所有可能的走法,并记录步数。为了防止到达不了终点陷入死循环,需要记录已经访问过的编号。

  • int row = m - 1 - (no - 1) / n;
  • int col = (m - 1 - row) % 2 == 0 ? (no - 1) % n : n - 1 - (no - 1) % n;

代码


/**
 * @date 2025-04-21 8:46
 */
public class SnakesAndLadders909 {

    public int snakesAndLadders(int[][] board) {
        int m = board.length;
        int n = board[0].length;
        int end = m * n;
        int res = 0;
        boolean[] visited = new boolean[end + 1];
        Queue<Integer> q = new ArrayDeque<>();
        q.add(1);
        while (!q.isEmpty()) {
            res++;
            int size = q.size();
            for (int i = 0; i < size; i++) {
                Integer cur = q.poll();
                for (int no = cur + 1; no <= cur + 6; no++) {
                    if (no == end) {
                        return res;
                    }
                    if (visited[no]) {
                        continue;
                    }
                    visited[no] = true;
                    int row = m - 1 - (no - 1) / n;
                    int col = (m - 1 - row) % 2 == 0 ? (no - 1) % n : n - 1 - (no - 1) % n;
                    if (board[row][col] == -1) {
                        q.offer(no);
                    } else {
                        if (board[row][col] == end) {
                            return res;
                        }
                        q.offer(board[row][col]);
                    }
                }
            }
        }
        return -1;
    }

}

性能

2359.找到离给定两个节点最近的节点

目标

给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,每个节点 至多 有一条出边。

有向图用大小为 n 下标从 0 开始的数组 edges 表示,表示节点 i 有一条有向边指向 edges[i] 。如果节点 i 没有出边,那么 edges[i] == -1 。

同时给你两个节点 node1 和 node2 。

请你返回一个从 node1 和 node2 都能到达节点的编号,使节点 node1 和节点 node2 到这个节点的距离 较大值最小化。如果有多个答案,请返回 最小 的节点编号。如果答案不存在,返回 -1 。

注意 edges 可能包含环。

示例 1:

输入:edges = [2,2,3,-1], node1 = 0, node2 = 1
输出:2
解释:从节点 0 到节点 2 的距离为 1 ,从节点 1 到节点 2 的距离为 1 。
两个距离的较大值为 1 。我们无法得到一个比 1 更小的较大值,所以我们返回节点 2 。

示例 2:

输入:edges = [1,2,-1], node1 = 0, node2 = 2
输出:2
解释:节点 0 到节点 2 的距离为 2 ,节点 2 到它自己的距离为 0 。
两个距离的较大值为 2 。我们无法得到一个比 2 更小的较大值,所以我们返回节点 2 。

说明:

  • n == edges.length
  • 2 <= n <= 10^5
  • -1 <= edges[i] < n
  • edges[i] != i
  • 0 <= node1, node2 < n

思路

使用两个不同的起点公用一个dfs,出度最大为 1,使用两个集合记录访问路径,遇到环、共同访问过的节点或者出度为 0 则停止。

代码


/**
 * @date 2025-05-30 0:44
 */
public class ClosestMeetingNode2359 {

    public int closestMeetingNode(int[] edges, int node1, int node2) {
        return dfs(node1, node2, edges, new HashSet<>(), new HashSet<>());
    }

    public int dfs(int cur1, int cur2, int[] edges, Set<Integer> set1, Set<Integer> set2) {
        if (set1.contains(cur2) && set2.contains(cur1) || cur1 == cur2) {
            return Math.min(cur1, cur2);
        } else if (set1.contains(cur2)) {
            return cur2;
        } else if (set2.contains(cur1)) {
            return cur1;
        }
        if (cur1 != -1) {
            set1.add(cur1);
        }
        if (cur2 != -1) {
            set2.add(cur2);
        }
        if (cur1 != -1 && !set1.contains(edges[cur1])) {
            return dfs(edges[cur1], cur2 == -1 ? -1 : edges[cur2], edges, set1, set2);
        } else if (cur2 != -1 && !set2.contains(edges[cur2])) {
            return dfs(cur1 == -1 ? -1 : edges[cur1], edges[cur2], edges, set1, set2);
        }
        return -1;
    }

}

性能

3373.连接两棵树后最大目标节点数目II

目标

有两棵 无向 树,分别有 n 和 m 个树节点。两棵树中的节点编号分别为[0, n - 1] 和 [0, m - 1] 中的整数。

给你两个二维整数 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [ai, bi] 表示第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [ui, vi] 表示第二棵树中节点 ui 和 vi 之间有一条边。

如果节点 u 和节点 v 之间路径的边数是偶数,那么我们称节点 u 是节点 v 的 目标节点 。注意 ,一个节点一定是它自己的 目标节点 。

请你返回一个长度为 n 的整数数组 answer ,answer[i] 表示将第一棵树中的一个节点与第二棵树中的一个节点连接一条边后,第一棵树中节点 i 的 目标节点 数目的 最大值 。

注意 ,每个查询相互独立。意味着进行下一次查询之前,你需要先把刚添加的边给删掉。

示例 1:

输入:edges1 = [[0,1],[0,2],[2,3],[2,4]], edges2 = [[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]]
输出:[8,7,7,8,8]
解释:
对于 i = 0 ,连接第一棵树中的节点 0 和第二棵树中的节点 0 。
对于 i = 1 ,连接第一棵树中的节点 1 和第二棵树中的节点 4 。
对于 i = 2 ,连接第一棵树中的节点 2 和第二棵树中的节点 7 。
对于 i = 3 ,连接第一棵树中的节点 3 和第二棵树中的节点 0 。
对于 i = 4 ,连接第一棵树中的节点 4 和第二棵树中的节点 4 。

示例 2:

输入:edges1 = [[0,1],[0,2],[0,3],[0,4]], edges2 = [[0,1],[1,2],[2,3]]
输出:[3,6,6,6,6]
解释:
对于每个 i ,连接第一棵树中的节点 i 和第二棵树中的任意一个节点。

提示:

  • 2 <= n, m <= 10^5
  • edges1.length == n - 1
  • edges2.length == m - 1
  • edges1[i].length == edges2[i].length == 2
  • edges1[i] = [ai, bi]
  • 0 <= ai, bi < n
  • edges2[i] = [ui, vi]
  • 0 <= ui, vi < m
  • 输入保证 edges1 和 edges2 都表示合法的树。

思路

有两棵树,当节点 ab 之间的边数为偶数时,称 ab 的目标节点,在两棵树任意节点之间连一条边,针对第一棵树的所有节点,计算其目标节点的最大个数。

3372.连接两棵树后最大目标节点数目I 相比,目标节点的条件从边数小于等于 k 变为了 偶数。

同一颗树中,某一个节点经过 偶数 或者 奇数条边到达的节点个数是固定的。不妨固定一个节点为根节点,经过 偶数 条边到达的节点集合为 A,经过 奇数 条边到达的节点集合为 B。集合 A 中节点的目标节点还是 A 中的节点,同理 B 中节点的目标节点还是 B 中的节点。

因此问题转化为计算两棵树的集合 A B,判断第一颗树的节点所属集合,如果属于 A,则取 A1 + Math.max(A2, B2),否则取 B1 + Math.max(A2, B2)。

核心点是固定一个参考节点,根据到达该节点的边数将节点分类,可以快速计算出目标节点个数,如果针对每一节点暴力搜索目标节点个数肯定会超时。

代码


/**
 * @date 2025-05-29 23:50
 */
public class MaxTargetNodes3373 {

    public int[] maxTargetNodes(int[][] edges1, int[][] edges2) {
        int n = edges1.length + 1;
        int m = edges2.length + 1;
        List<Integer>[] g1 = new List[n];
        List<Integer>[] g2 = new List[m];
        Arrays.setAll(g1, x -> new ArrayList<>());
        Arrays.setAll(g2, x -> new ArrayList<>());
        for (int[] edge : edges1) {
            int a = edge[0];
            int b = edge[1];
            g1[a].add(b);
            g1[b].add(a);
        }
        for (int[] edge : edges2) {
            int a = edge[0];
            int b = edge[1];
            g2[a].add(b);
            g2[b].add(a);
        }
        Set<Integer>[] set1 = new HashSet[2];
        Arrays.setAll(set1, x -> new HashSet<>());
        int[] set2 = new int[]{0, 0};
        dfs(0, -1, g1, 0, set1);
        dfs(0, -1, g2, 0, set2);
        int max2 = Math.max(set2[0], set2[1]);
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            if (set1[0].contains(i)) {
                res[i] = set1[0].size() + max2;
            } else {
                res[i] = set1[1].size() + max2;
            }
        }
        return res;
    }

    public void dfs(int cur, int parent, List<Integer>[] g, int flag, Set<Integer>[] set) {
        set[flag].add(cur);
        for (Integer next : g[cur]) {
            if (next == parent) {
                continue;
            }
            dfs(next, cur, g, flag ^ 1, set);
        }
    }

    public void dfs(int cur, int parent, List<Integer>[] g, int flag, int[] res) {
        res[flag]++;
        for (Integer next : g[cur]) {
            if (next == parent) {
                continue;
            }
            dfs(next, cur, g, flag ^ 1, res);
        }
    }
}

性能

3372.连接两棵树后最大目标节点数目I

目标

有两棵 无向 树,分别有 n 和 m 个树节点。两棵树中的节点编号分别为[0, n - 1] 和 [0, m - 1] 中的整数。

给你两个二维整数 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [ai, bi] 表示第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [ui, vi] 表示第二棵树中节点 ui 和 vi 之间有一条边。同时给你一个整数 k 。

如果节点 u 和节点 v 之间路径的边数小于等于 k ,那么我们称节点 u 是节点 v 的 目标节点 。注意 ,一个节点一定是它自己的 目标节点 。

请你返回一个长度为 n 的整数数组 answer ,answer[i] 表示将第一棵树中的一个节点与第二棵树中的一个节点连接一条边后,第一棵树中节点 i 的 目标节点 数目的 最大值 。

注意 ,每个查询相互独立。意味着进行下一次查询之前,你需要先把刚添加的边给删掉。

示例 1:

输入:edges1 = [[0,1],[0,2],[2,3],[2,4]], edges2 = [[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]], k = 2
输出:[9,7,9,8,8]
解释:
对于 i = 0 ,连接第一棵树中的节点 0 和第二棵树中的节点 0 。
对于 i = 1 ,连接第一棵树中的节点 1 和第二棵树中的节点 0 。
对于 i = 2 ,连接第一棵树中的节点 2 和第二棵树中的节点 4 。
对于 i = 3 ,连接第一棵树中的节点 3 和第二棵树中的节点 4 。
对于 i = 4 ,连接第一棵树中的节点 4 和第二棵树中的节点 4 。

示例 2:

输入:edges1 = [[0,1],[0,2],[0,3],[0,4]], edges2 = [[0,1],[1,2],[2,3]], k = 1
输出:[6,3,3,3,3]
解释:
对于每个 i ,连接第一棵树中的节点 i 和第二棵树中的任意一个节点。

提示:

  • 2 <= n, m <= 1000
  • edges1.length == n - 1
  • edges2.length == m - 1
  • edges1[i].length == edges2[i].length == 2
  • edges1[i] = [ai, bi]
  • 0 <= ai, bi < n
  • edges2[i] = [ui, vi]
  • 0 <= ui, vi < m
  • 输入保证 edges1 和 edges2 都表示合法的树。
  • 0 <= k <= 1000

思路

有两棵树,当节点 ab 之间的边数小于等于 k 时,称 ab 的目标节点,在两棵树任意节点之间连一条边,针对第一棵树的所有节点,计算其目标节点的最大个数。

计算第一棵树的每个节点至多经过 k 条边相连的节点个数,当 k > 0 时,加上第二棵树每个节点至多经过 k - 1 条边相连的节点个数的最大值。

代码


/**
 * @date 2025-05-28 23:36
 */
public class MaxTargetNodes3372 {

    public int[] maxTargetNodes(int[][] edges1, int[][] edges2, int k) {
        int n = edges1.length + 1;
        int m = edges2.length + 1;
        List<Integer>[] g1 = new List[n];
        List<Integer>[] g2 = new List[m];
        Arrays.setAll(g1, x -> new ArrayList<>());
        Arrays.setAll(g2, x -> new ArrayList<>());
        for (int[] edge1 : edges1) {
            int a = edge1[0];
            int b = edge1[1];
            g1[a].add(b);
            g1[b].add(a);
        }
        for (int[] edge2 : edges2) {
            int a = edge2[0];
            int b = edge2[1];
            g2[a].add(b);
            g2[b].add(a);
        }
        int max = 0;
        for (int i = 0; i < m; i++) {
            max = Math.max(max, dfs(i, -1, g2, 0, k - 1));
        }
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = dfs(i, -1, g1, 0, k) + (k == 0 ? 0 : max);
        }
        return res;
    }

    public int dfs(int cur, int parent, List<Integer>[] g, int cnt, int k) {
        if (cnt >= k) {
            return 1;
        }
        int res = 0;
        for (Integer next : g[cur]) {
            if (next == parent) {
                continue;
            }
            res += dfs(next, cur, g, cnt + 1, k);
        }
        return res + 1;
    }

}

性能

2894.分类求和并作差

目标

给你两个正整数 n 和 m 。

现定义两个整数 num1 和 num2 ,如下所示:

  • num1:范围 [1, n] 内所有 无法被 m 整除 的整数之和。
  • num2:范围 [1, n] 内所有 能够被 m 整除 的整数之和。

返回整数 num1 - num2 。

示例 1:

输入:n = 10, m = 3
输出:19
解释:在这个示例中:
- 范围 [1, 10] 内无法被 3 整除的整数为 [1,2,4,5,7,8,10] ,num1 = 这些整数之和 = 37 。
- 范围 [1, 10] 内能够被 3 整除的整数为 [3,6,9] ,num2 = 这些整数之和 = 18 。
返回 37 - 18 = 19 作为答案。

示例 2:

输入:n = 5, m = 6
输出:15
解释:在这个示例中:
- 范围 [1, 5] 内无法被 6 整除的整数为 [1,2,3,4,5] ,num1 = 这些整数之和 =  15 。
- 范围 [1, 5] 内能够被 6 整除的整数为 [] ,num2 = 这些整数之和 = 0 。
返回 15 - 0 = 15 作为答案。

示例 3:

输入:n = 5, m = 1
输出:-15
解释:在这个示例中:
- 范围 [1, 5] 内无法被 1 整除的整数为 [] ,num1 = 这些整数之和 = 0 。 
- 范围 [1, 5] 内能够被 1 整除的整数为 [1,2,3,4,5] ,num2 = 这些整数之和 = 15 。
返回 0 - 15 = -15 作为答案。

说明:

  • 1 <= n, m <= 1000

思路

1 ~ n 中无法整除 m 的所有整数和 num1 与 能够整除 m 的所有整数和 num2 之差。

依题意模拟即可。

或者使用等差数列求和公式计算。

根据等差数列求和公式得到所有元素和为 total = (n + 1) * n / 2,能够被 m 整除的所有整数和为 首项为 m,公差为 m,个数为 cnt = n / m 的等差数列和,num2 = m * (cnt + 1) * cnt / 2。结果为 total - 2 * num2

代码


/**
 * @date 2025-05-27 1:00
 */
public class DifferenceOfSums2894 {

    public int differenceOfSums(int n, int m) {
        int total = (n + 1) * n / 2;
        int cnt = n / m;
        return total - m * (1 + cnt) * cnt;
    }

}

性能

1857.有向图中最大颜色值

目标

给你一个 有向图 ,它含有 n 个节点和 m 条边。节点编号从 0 到 n - 1 。

给你一个字符串 colors ,其中 colors[i] 是小写英文字母,表示图中第 i 个节点的 颜色 (下标从 0 开始)。同时给你一个二维数组 edges ,其中 edges[j] = [aj, bj] 表示从节点 aj 到节点 bj 有一条 有向边 。

图中一条有效 路径 是一个点序列 x1 -> x2 -> x3 -> ... -> xk ,对于所有 1 <= i < k ,从 xi 到 xi+1 在图中有一条有向边。路径的 颜色值 是路径中 出现次数最多 颜色的节点数目。

请你返回给定图中有效路径里面的 最大颜色值 。如果图中含有环,请返回 -1 。

示例 1:

输入:colors = "abaca", edges = [[0,1],[0,2],[2,3],[3,4]]
输出:3
解释:路径 0 -> 2 -> 3 -> 4 含有 3 个颜色为 "a" 的节点(上图中的红色节点)。

示例 2:

输入:colors = "a", edges = [[0,0]]
输出:-1
解释:从 0 到 0 有一个环。

说明:

  • n == colors.length
  • m == edges.length
  • 1 <= n <= 10^5
  • 0 <= m <= 10^5
  • colors 只含有小写英文字母。
  • 0 <= aj, bj < n

思路

// 今天没空做了 todo

代码

性能

2131.连接两字母单词得到的最长回文串

目标

给你一个字符串数组 words 。words 中每个元素都是一个包含 两个 小写英文字母的单词。

请你从 words 中选择一些元素并按 任意顺序 连接它们,并得到一个 尽可能长的回文串 。每个元素 至多 只能使用一次。

请你返回你能得到的最长回文串的 长度 。如果没办法得到任何一个回文串,请你返回 0 。

回文串 指的是从前往后和从后往前读一样的字符串。

示例 1:

输入:words = ["lc","cl","gg"]
输出:6
解释:一个最长的回文串为 "lc" + "gg" + "cl" = "lcggcl" ,长度为 6 。
"clgglc" 是另一个可以得到的最长回文串。

示例 2:

输入:words = ["ab","ty","yt","lc","cl","ab"]
输出:8
解释:最长回文串是 "ty" + "lc" + "cl" + "yt" = "tylcclyt" ,长度为 8 。
"lcyttycl" 是另一个可以得到的最长回文串。

示例 3:

输入:words = ["cc","ll","xx"]
输出:2
解释:最长回文串是 "cc" ,长度为 2 。
"ll" 是另一个可以得到的最长回文串。"xx" 也是。

说明:

  • 1 <= words.length <= 10^5
  • words[i].length == 2
  • words[i] 仅包含小写英文字母。

思路

有一个字符串数组 words,其元素字符个数为 2,求从中选择任意元素组成回文串的最大长度。

统计每个元素的出现次数,如果数组元素的两个字符不同,要组成回文只能左右对称,计算对称的元素对数 cnt,长度为 cnt * 4。如果元素的两个字符相同,它可以全部放到中间,为了使回文最长,当出现更长的相同字符元素时,可以将原来放中间的个数 centerCnt,放到对称的两边,centerCnt / 2 * 4

代码


/**
 * @date 2025-05-25 1:03
 */
public class LongestPalindrome2131 {

    public int longestPalindrome(String[] words) {
        Map<String, Integer> map = new HashMap<>();
        for (String word : words) {
            char a = word.charAt(0);
            char b = word.charAt(1);
            map.merge(a + String.valueOf(b), 1, Integer::sum);
        }
        int res = 0;
        int centerCnt = 0;
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            char a = key.charAt(0);
            char b = key.charAt(1);
            if (a == b) {
                Integer cnt = entry.getValue();
                if (cnt % 2 == 1) {
                    res += centerCnt / 2 * 4;
                    centerCnt = cnt;
                } else {
                    res += cnt / 2 * 4;
                }
            } else {
                int cnt = Math.min(entry.getValue(), map.getOrDefault(b + String.valueOf(a), 0));
                res += cnt * 4;
            }
            entry.setValue(0);
        }
        return res + centerCnt * 2;
    }

}

性能

2942.查找包含给定字符的单词

目标

给你一个下标从 0 开始的字符串数组 words 和一个字符 x 。

请你返回一个 下标数组,表示下标在数组中对应的单词包含字符 x 。

注意,返回的数组可以是 任意 顺序。

示例 1:

输入:words = ["leet","code"], x = "e"
输出:[0,1]
解释:"e" 在两个单词中都出现了:"leet" 和 "code" 。所以我们返回下标 0 和 1 。

示例 2:

输入:words = ["abc","bcd","aaaa","cbc"], x = "a"
输出:[0,2]
解释:"a" 在 "abc" 和 "aaaa" 中出现了,所以我们返回下标 0 和 2 。

示例 3:

输入:words = ["abc","bcd","aaaa","cbc"], x = "z"
输出:[]
解释:"z" 没有在任何单词中出现。所以我们返回空数组。

提示:

  • 1 <= words.length <= 50
  • 1 <= words[i].length <= 50
  • x 是一个小写英文字母。
  • words[i] 只包含小写英文字母。

思路

返回字符串数组中包含给定字符的下标。

代码


/**
 * @date 2025-05-24 10:09
 */
public class FindWordsContaining2942 {

    public List<Integer> findWordsContaining(String[] words, char x) {
        List<Integer> res = new ArrayList<>(words.length);
        for (int i = 0; i < words.length; i++) {
            String word = words[i];
            for (int j = 0; j < word.length(); j++) {
                if (word.charAt(j) == x) {
                    res.add(i);
                    break;
                }
            }
        }
        return res;
    }

}

性能