1861.旋转盒子

目标

给你一个 m x n 的字符矩阵 boxGrid ,它表示一个箱子的侧视图。箱子的每一个格子可能为:

  • '#' 表示石头
  • '*' 表示固定的障碍物
  • '.' 表示空位置

这个箱子被 顺时针旋转 90 度 ,由于重力原因,部分石头的位置会发生改变。每个石头会垂直掉落,直到它遇到障碍物,另一个石头或者箱子的底部。重力 不会 影响障碍物的位置,同时箱子旋转不会产生惯性 ,也就是说石头的水平位置不会发生改变。

题目保证初始时 boxGrid 中的石头要么在一个障碍物上,要么在另一个石头上,要么在箱子的底部。

请你返回一个 n x m 的矩阵,表示按照上述旋转后,箱子内的结果。

示例 1:

输入:box = [["#",".","#"]]
输出:[["."],
      ["#"],
      ["#"]]

示例 2:

输入:box = [["#",".","*","."],
            ["#","#","*","."]]
输出:[["#","."],
      ["#","#"],
      ["*","*"],
      [".","."]]

示例 3:

输入:box = [["#","#","*",".","*","."],
            ["#","#","#","*",".","."],
            ["#","#","#",".","#","."]]
输出:[[".","#","#"],
      [".","#","#"],
      ["#","#","*"],
      ["#","*","."],
      ["#",".","*"],
      ["#",".","."]]

说明:

  • m == boxGrid.length
  • n == boxGrid[i].length
  • 1 <= m, n <= 500
  • boxGrid[i][j] 只可能是 '#' ,'*' 或者 '.' 。

思路

有一个 m x n 矩阵,. 表示空位,# 表示石头,* 表示障碍物。将矩阵顺时针旋转 90°,石头遇到障碍物或者底部会停止下落,返回最终的结果。

根据题意模拟,旋转矩阵,然后分别处理每一列,将石头加入栈中,并将当前格子置空,如果遇到障碍物或者底部则向上填充石头。

代码


/**
 * @date 2026-05-06 9:42
 */
public class RotateTheBox1861 {

    public char[][] rotateTheBox(char[][] boxGrid) {
        boxGrid = rotate(boxGrid);
        int m = boxGrid.length;
        int n = boxGrid[0].length;
        for (int j = 0; j < n; j++) {
            Deque<Character> q = new ArrayDeque<>();
            for (int i = 0; i < m; i++) {
                char c = boxGrid[i][j];
                if (c == '#') {
                    q.push(c);
                    boxGrid[i][j] = '.';
                } else if (c == '*') {
                    int k = i - 1;
                    while (!q.isEmpty()){
                        boxGrid[k--][j] = q.pop();
                    }
                }
            }
            int i = m - 1;
            while (!q.isEmpty()){
                boxGrid[i--][j] = q.pop();
            }
        }
        return boxGrid;
    }

    public char[][] rotate(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        char[][] rotated = new char[n][m];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                rotated[j][m - 1 - i] = grid[i][j];
            }
        }
        return rotated;
    }
}

性能

61.旋转链表

目标

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

示例 2:

输入:head = [0,1,2], k = 4
输出:[2,0,1]

说明:

  • 链表中节点的数目在范围 [0, 500] 内
  • -100 <= Node.val <= 100
  • 0 <= k <= 2 * 10^9

思路

将链表的每个节点右移 k 个位置,即将链表最后 k % list.size() 个元素放到列表头。

可以使用快慢指针,slow 指向最后 k 个节点的前面一个节点,slow 指针移到到 fast 指针需要移动 k + 1 次,即后面 k 个节点和 null。先让 fast 移动 k + 1 次,再让 slowfast 同时移动。

代码


/**
 * @date 2024-11-24 16:15
 */
public class RotateRight61 {

    public ListNode rotateRight_v1(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        int length = 1;
        ListNode last = head;
        while (last.next != null) {
            last = last.next;
            length++;
        }
        k = k % length + 1;
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null) {
            fast = fast.next;
            k--;
            if (k < 0) {
                slow = slow.next;
            }
        }
        last.next = head;
        head = slow.next;
        slow.next = null;
        return head;
    }

}

性能

48.旋转图像

目标

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

说明:

n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000

思路

将 n x n 矩阵顺时针 原地 旋转 90°。

顺时针旋转 90° 的规律:交换行列坐标,将列坐标变为 n - 1 - ?

i, j -> j, n - 1 - i
     -> n - 1 - i, n - 1 - j
     -> n - 1 - j, i,
     -> i, j

代码


/**
 * @date 2025-01-26 22:02
 */
public class Rotate48 {

    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n / 2; i++) {
            for (int j = 0; j < (n + 1) / 2; j++) {
                int tmp = matrix[n - 1 - j][i];
                matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
                matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
                matrix[j][n - 1 - i] = matrix[i][j];
                matrix[i][j] = tmp;
            }
        }
    }

}

性能

796.旋转字符串

目标

给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 。

s 的 旋转操作 就是将 s 最左边的字符移动到最右边。

  • 例如, 若 s = 'abcde',在旋转一次之后结果就是'bcdea' 。

示例 1:

输入: s = "abcde", goal = "cdeab"
输出: true

示例 2:

输入: s = "abcde", goal = "abced"
输出: false

说明:

  • 1 <= s.length, goal.length <= 100
  • s 和 goal 由小写英文字母组成

思路

判断能否将 s 经过任意次旋转变为 goal,每次旋转指将最左边的字符放到最右边。

首先判断 sgoal 的长度是否相等,然后再判断 s + s 是否包含 goal

代码


/**
 * @date 2026-05-06 16:30
 */
public class RotateString796 {

    public boolean rotateString(String s, String goal) {
        return s.length() == goal.length() && (s + s).contains(goal);
    }
}

性能

3742.网格中得分最大的路径

目标

给你一个 m x n 的网格 grid,其中每个单元格包含以下值之一:0、1 或 2。另给你一个整数 k。

你从左上角 (0, 0) 出发,目标是到达右下角 (m - 1, n - 1),只能向 右 或 下 移动。

每个单元格根据其值对路径有以下贡献:

  • 值为 0 的单元格:分数增加 0,花费 0。
  • 值为 1 的单元格:分数增加 1,花费 1。
  • 值为 2 的单元格:分数增加 2,花费 1。

返回在总花费不超过 k 的情况下可以获得的 最大分数 ,如果不存在有效路径,则返回 -1。

注意: 如果到达最后一个单元格时总花费超过 k,则该路径无效。

示例 1:

输入: grid = [[0, 1],[2, 0]], k = 1
输出: 2
解释:
最佳路径为:
单元格  grid[i][j] 当前分数 累计分数 当前花费 累计花费
(0, 0)     0         0      0        0       0
(1, 0)     2         2      2        1       1
(1, 1)     0         0      2        0       1
因此,可获得的最大分数为 2。

示例 2:

输入: grid = [[0, 1],[1, 2]], k = 1
输出: -1
解释:
不存在在总花费不超过 k 的情况下到达单元格 (1, 1) 的路径,因此答案是 -1。

说明:

  • 1 <= m, n <= 200
  • 0 <= k <= 10^3
  • grid[0][0] == 0
  • 0 <= grid[i][j] <= 2

思路

有一个 m x n 网格 grid,其元素值可取 0、1、2,从左上角 (0, 0) 移动到 (m - 1, n - ),每次移动只能向右或向下,移动的代价为 grid[i][j] == 0 ? 0 : 1。求总代价不超过 k 的条件下,从左上移动到右下的最大得分,如果不存在有效路径返回 -1

定义 dp[i + 1][j + 1][c] 表示到达坐标 (i, j) 且代价不超过 c - 1 的最大得分。状态转移方程为 dp[i + 1][j + 1][c] = Math.max(dp[i][j + 1][c + cost], dp[i + 1][j][c + cost]) + grid[i][j],其中 cost = grid[i][j] == 0 ? 0 : -1

可以进行空间优化,直接将第一个维度去掉,内层倒序遍历即可。

代码


/**
 * @date 2026-04-30 8:57
 */
public class MaxPathScore3742 {

    public int maxPathScore_v2(int[][] grid, int k) {
        int n = grid[0].length;
        int[][] dp = new int[n + 1][k + 2];
        for (int[] row : dp) {
            Arrays.fill(row, Integer.MIN_VALUE);
        }
        Arrays.fill(dp[1], 1, k + 2, 0);
        for (int[] row : grid) {
            for (int j = 0; j < n; j++) {
                int cost = row[j] == 0 ? 0 : -1;
                for (int c = k + 1; c >=1; c--) {
                    dp[j + 1][c] = Math.max(dp[j + 1][c + cost], dp[j][c + cost]) + row[j];
                }
            }
        }
        return dp[n][k + 1] < 0 ? -1 : dp[n][k + 1];
    }

}

性能

3225.网格图操作后的最大分数

目标

给你一个大小为 n x n 的二维矩阵 grid ,一开始所有格子都是白色的。一次操作中,你可以选择任意下标为 (i, j) 的格子,并将第 j 列中从最上面到第 i 行所有格子改成黑色。

如果格子 (i, j) 为白色,且左边或者右边的格子至少一个格子为黑色,那么我们将 grid[i][j] 加到最后网格图的总分中去。

请你返回执行任意次操作以后,最终网格图的 最大 总分数。

示例 1:

输入:grid = [[0,0,0,0,0],[0,0,3,0,0],[0,1,0,0,0],[5,0,0,3,0],[0,0,0,0,2]]
输出:11
解释:
第一次操作中,我们将第 1 列中,最上面的格子到第 3 行的格子染成黑色。第二次操作中,我们将第 4 列中,最上面的格子到最后一行的格子染成黑色。最后网格图总分为 grid[3][0] + grid[1][2] + grid[3][3] 等于 11 。

示例 2:

输入:grid = [[10,9,0,0,15],[7,1,0,8,0],[5,20,0,11,0],[0,0,0,1,2],[8,12,1,10,3]]
输出:94
解释:
我们对第 1 ,2 ,3 列分别从上往下染黑色到第 1 ,4, 0 行。最后网格图总分为 grid[0][0] + grid[1][0] + grid[2][1] + grid[4][1] + grid[1][3] + grid[2][3] + grid[3][3] + grid[4][3] + grid[0][4] 等于 94 。

说明:

  • 1 <= n == grid.length <= 100
  • n == grid[i].length
  • 0 <= grid[i][j] <= 10^9

思路

代码

性能

2033.获取单值网格的最小操作数

目标

给你一个大小为 m x n 的二维整数网格 grid 和一个整数 x 。每一次操作,你可以对 grid 中的任一元素 加 x 或 减 x 。

单值网格 是全部元素都相等的网格。

返回使网格化为单值网格所需的 最小 操作数。如果不能,返回 -1 。

示例 1:

输入:grid = [[2,4],[6,8]], x = 2
输出:4
解释:可以执行下述操作使所有元素都等于 4 : 
- 2 加 x 一次。
- 6 减 x 一次。
- 8 减 x 两次。
共计 4 次操作。

示例 2:

输入:grid = [[1,5],[2,3]], x = 1
输出:5
解释:可以使所有元素都等于 3 。

示例 3:

输入:grid = [[1,2],[3,4]], x = 2
输出:-1
解释:无法使所有元素相等。

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10^5
  • 1 <= m * n <= 10^5
  • 1 <= x, grid[i][j] <= 10^4

思路

有一个 m x n 矩阵,每次操作可以将矩阵中的任意元素 +x 或者 -x,求使得矩阵所有元素相等所需的最小操作,如果不能返回 -1

所有元素对 x 取模余数相同才有解,因为操作无法改变这个余数,而目标是将元素变成同一个数。

如果有解,可以将共同的余数减去,将每个元素都除以 x,然后令 x = 1,问题变成找到一个值,使得所有元素到这个值的距离之和最小。

这是一个 L1 优化问题,它的解是中位数,可以先排序,再取中间的元素即可。

代码


/**
 * @date 2026-04-28 9:35
 */
public class MinOperations2033 {

    public int minOperations(int[][] grid, int x) {
        int m = grid.length;
        int n = grid[0].length;
        int[] arr = new int[m * n];
        int rem = grid[0][0] % x;
        int k = 0;
        for (int[] row : grid) {
            for (int e : row) {
                if (e % x != rem) {
                    return -1;
                }
                arr[k++] = e / x;
            }
        }
        Arrays.sort(arr);
        int median = arr[arr.length / 2];
        int res = 0;
        for (int num : arr) {
            res += Math.abs(num - median);
        }
        return res;
    }
}

性能

1391.检查网格中是否存在有效路径

目标

给你一个 m x n 的网格 grid。网格里的每个单元都代表一条街道。grid[i][j] 的街道可以是:

  • 1 表示连接左单元格和右单元格的街道。
  • 2 表示连接上单元格和下单元格的街道。
  • 3 表示连接左单元格和下单元格的街道。
  • 4 表示连接右单元格和下单元格的街道。
  • 5 表示连接左单元格和上单元格的街道。
  • 6 表示连接右单元格和上单元格的街道。

你最开始从左上角的单元格 (0,0) 开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0) 开始、一直到右下方的 (m-1,n-1) 结束的路径。该路径必须只沿着街道走。

注意:你 不能 变更街道。

如果网格中存在有效的路径,则返回 true,否则返回 false 。

示例 1:

输入:grid = [[2,4,3],[6,5,2]]
输出:true
解释:如图所示,你可以从 (0, 0) 开始,访问网格中的所有单元格并到达 (m - 1, n - 1) 。

示例 2:

输入:grid = [[1,2,1],[1,2,1]]
输出:false
解释:如图所示,单元格 (0, 0) 上的街道没有与任何其他单元格上的街道相连,你只会停在 (0, 0) 处。

示例 3:

输入:grid = [[1,1,2]]
输出:false
解释:你会停在 (0, 1),而且无法到达 (0, 2) 。

示例 4:

输入:grid = [[1,1,1,1,1,1,3]]
输出:true

示例 5:

输入:grid = [[2],[2],[2],[2],[2],[2],[6]]
输出:true

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • 1 <= grid[i][j] <= 6

思路

有一个二维矩阵 grid,每个单元格的值都代表一种街道类型,判断能否从左上角出发沿着街道走到右下角。

根据当前的街道类型,初始化可走的方向,同时判断下一个位置是否超出了边界,是否访问过,是否与当前街道连通。

代码


/**
 * @date 2026-04-27 9:08
 */
public class HasValidPath1391 {

    public boolean hasValidPath(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        return dfs(0, 0, new int[]{1, 2, 3, 4, 5, 6}, visited, grid);
    }

    public static int[][][] directions = new int[][][]{
            {{0, 0}, {0, 0}},
            {{0, -1}, {0, 1}},
            {{-1, 0}, {1, 0}},
            {{0, -1}, {1, 0}},
            {{0, 1}, {1, 0}},
            {{0, -1}, {-1, 0}},
            {{0, 1}, {-1, 0}},
    };

    public static int[][][] allowedTypes = new int[][][]{
            {{}},
            {{1, 4, 6}, {1, 3, 5}},
            {{2, 3, 4}, {2, 5, 6}},
            {{1, 4, 6}, {2, 5, 6}},
            {{1, 3, 5}, {2, 5, 6}},
            {{1, 4, 6}, {2, 3, 4}},
            {{1, 3, 5}, {2, 3, 4}},
    };

    public boolean dfs(int row, int col, int[] types, boolean[][] visited, int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (row == m || row < 0 || col == n || col < 0 || visited[row][col]) {
            return false;
        }
        visited[row][col] = true;
        int type = grid[row][col];
        boolean valid = false;
        for (int t : types) {
            if (t == type) {
                valid = true;
                break;
            }
        }
        if (!valid) {
            return false;
        }
        boolean res = row == m - 1 && col == n - 1;
        for (int i = 0; i < directions[type].length; i++) {
            res = res || dfs(row + directions[type][i][0], col + directions[type][i][1], allowedTypes[type][i], visited, grid);
        }
        return res;
    }

}

性能

1559.二维网格图中探测环

目标

给你一个二维字符网格数组 grid ,大小为 m x n ,你需要检查 grid 中是否存在 相同值 形成的环。

一个环是一条开始和结束于同一个格子的长度 大于等于 4 的路径。对于一个给定的格子,你可以移动到它上、下、左、右四个方向相邻的格子之一,可以移动的前提是这两个格子有 相同的值 。

同时,你也不能回到上一次移动时所在的格子。比方说,环 (1, 1) -> (1, 2) -> (1, 1) 是不合法的,因为从 (1, 2) 移动到 (1, 1) 回到了上一次移动时的格子。

如果 grid 中有相同值形成的环,请你返回 true ,否则返回 false 。

示例 1:

输入:grid = [["a","a","a","a"],["a","b","b","a"],["a","b","b","a"],["a","a","a","a"]]
输出:true
解释:如下图所示,有 2 个用不同颜色标出来的环:

示例 2:

输入:grid = [["c","c","c","a"],["c","d","c","c"],["c","c","e","c"],["f","c","c","c"]]
输出:true
解释:如下图所示,只有高亮所示的一个合法环:

示例 3:

输入:grid = [["a","b","b"],["b","z","b"],["b","b","a"]]
输出:false

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m <= 500
  • 1 <= n <= 500
  • grid 只包含小写英文字母。

思路

判断网格图中是否存在相同元素值形成的环,要求环的长度大于等于 4。沿着环移动总是能回到起点(沿着环顺时针或者逆时针移动,不能往回走,环中的元素只能访问一次)。

从每个格子出发 dfs,记录已访问的坐标 visited[i][j],假设上一个访问的坐标 (px, py),针对每一个格子枚举上下左右四个方向,下一个格子坐标为 (nx, ny),如果不是 (px, py),并且没越界且元素值相等,返回 visited[nx][ny] || dfs(nx, ny, x, y, visited, grid)。对于网格图,如果 (nx, ny) 不等于 (px, py) 并且已访问过说明存在环,并且环的长度至少是 4

代码


/**
 * @date 2026-04-26 23:14
 */
public class ContainsCycle1559 {

    public static int[][] directions = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    public boolean containsCycle(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (visited[i][j]) {
                    continue;
                }
                if (dfs(i, j, -1, -1, visited, grid)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean dfs(int x, int y, int px, int py, boolean[][] visited, char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        visited[x][y] = true;
        for (int[] d : directions) {
            int nx = x + d[0];
            int ny = y + d[1];
            if ((nx != px || ny != py) && nx >= 0 && nx < m && ny >= 0 && ny < n && grid[x][y] == grid[nx][ny] &&
                    (
                            visited[nx][ny] || dfs(nx, ny, x, y, visited, grid)
                    )
            ) {
                return true;
            }
        }
        return false;
    }
}

性能

3464.正方形上的点之间的最大距离

目标

给你一个整数 side,表示一个正方形的边长,正方形的四个角分别位于笛卡尔平面的 (0, 0) ,(0, side) ,(side, 0) 和 (side, side) 处。

同时给你一个 正整数 k 和一个二维整数数组 points,其中 points[i] = [xi, yi] 表示一个点在正方形边界上的坐标。

你需要从 points 中选择 k 个元素,使得任意两个点之间的 最小 曼哈顿距离 最大化 。

返回选定的 k 个点之间的 最小 曼哈顿距离的 最大 可能值。

两个点 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离为 |xi - xj| + |yi - yj|。

示例 1:

输入: side = 2, points = [[0,2],[2,0],[2,2],[0,0]], k = 4
输出: 2
解释:
选择所有四个点。

示例 2:

输入: side = 2, points = [[0,0],[1,2],[2,0],[2,2],[2,1]], k = 4
输出: 1
解释:
选择点 (0, 0) ,(2, 0) ,(2, 2) 和 (2, 1)。

示例 3:

输入: side = 2, points = [[0,0],[0,1],[0,2],[1,2],[2,0],[2,2],[2,1]], k = 5
输出: 1
解释:
选择点 (0, 0) ,(0, 1) ,(0, 2) ,(1, 2) 和 (2, 2)。

说明:

  • 1 <= side <= 10^9
  • 4 <= points.length <= min(4 side, 15 10^3)
  • points[i] == [xi, yi]
  • 输入产生方式如下:
    • points[i] 位于正方形的边界上。
    • 所有 points[i] 都 互不相同 。
  • 4 <= k <= min(25, points.length)

思路

代码

性能