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

性能

2833.距离原点最远的点

目标

给你一个长度为 n 的字符串 moves ,该字符串仅由字符 'L'、'R' 和 '_' 组成。字符串表示你在一条原点为 0 的数轴上的若干次移动。

你的初始位置就在原点(0),第 i 次移动过程中,你可以根据对应字符选择移动方向:

  • 如果 moves[i] = 'L' 或 moves[i] = '_' ,可以选择向左移动一个单位距离
  • 如果 moves[i] = 'R' 或 moves[i] = '_' ,可以选择向右移动一个单位距离

移动 n 次之后,请你找出可以到达的距离原点 最远 的点,并返回 从原点到这一点的距离 。

示例 1:

输入:moves = "L_RL__R"
输出:3
解释:可以到达的距离原点 0 最远的点是 -3 ,移动的序列为 "LLRLLLR" 。

示例 2:

输入:moves = "_R__LL_"
输出:5
解释:可以到达的距离原点 0 最远的点是 -5 ,移动的序列为 "LRLLLLL" 。

示例 3:

输入:moves = "_______"
输出:7
解释:可以到达的距离原点 0 最远的点是 7 ,移动的序列为 "RRRRRRR" 。

说明:

  • 1 <= moves.length == n <= 50
  • moves 仅由字符 'L'、'R' 和 '_' 组成

思路

开始时站在在数轴原点,根据数组 moves 移动,L -1 R +1 _ 可以任选方向,求所能到达的距离原点最远的距离。

LR 是固定的,要使距离最远 _ 应该都朝一个方向移动,可以先判断 LR 移动后位于原点的左侧还是右侧,然后 _ 的移动方向与之保持一致即可。

代码


/**
 * @date 2026-04-24 8:50
 */
public class FurthestDistanceFromOrigin2833 {

    public int furthestDistanceFromOrigin(String moves) {
        int n = moves.length();
        int d = 0;
        int c = 0;
        for (int i = 0; i < n; i++) {
            if (moves.charAt(i) == 'L') {
                d--;
            } else if (moves.charAt(i) == 'R') {
                d++;
            } else {
                c++;
            }
        }
        return d > 0 ? d + c : -d + c;
    }
}

性能

2078.两栋颜色不同且距离最远的房子

目标

街上有 n 栋房子整齐地排成一列,每栋房子都粉刷上了漂亮的颜色。给你一个下标从 0 开始且长度为 n 的整数数组 colors ,其中 colors[i] 表示第 i 栋房子的颜色。

返回 两栋 颜色 不同 房子之间的 最大 距离。

第 i 栋房子和第 j 栋房子之间的距离是 abs(i - j) ,其中 abs(x) 是 x 的绝对值。

示例 1:

输入:colors = [1,1,1,6,1,1,1]
输出:3
解释:上图中,颜色 1 标识成蓝色,颜色 6 标识成红色。
两栋颜色不同且距离最远的房子是房子 0 和房子 3 。
房子 0 的颜色是颜色 1 ,房子 3 的颜色是颜色 6 。两栋房子之间的距离是 abs(0 - 3) = 3 。
注意,房子 3 和房子 6 也可以产生最佳答案。

示例 2:

输入:colors = [1,8,3,8,3]
输出:4
解释:上图中,颜色 1 标识成蓝色,颜色 8 标识成黄色,颜色 3 标识成绿色。
两栋颜色不同且距离最远的房子是房子 0 和房子 4 。
房子 0 的颜色是颜色 1 ,房子 4 的颜色是颜色 3 。两栋房子之间的距离是 abs(0 - 4) = 4 。

示例 3:

输入:colors = [0,1]
输出:1
解释:两栋颜色不同且距离最远的房子是房子 0 和房子 1 。
房子 0 的颜色是颜色 0 ,房子 1 的颜色是颜色 1 。两栋房子之间的距离是 abs(0 - 1) = 1 。

说明:

  • n == colors.length
  • 2 <= n <= 100
  • 0 <= colors[i] <= 100
  • 生成的测试数据满足 至少 存在 2 栋颜色不同的房子

思路

有一排房子,colors[i] 表示房子 i 的颜色,求不同颜色的房子之间的最远距离。

暴力解法是写一个双重循环,内层从后往前遍历,直到找到第一个不同的颜色下标。

O(n) 的解法是找到不等于首尾颜色的最小与最大下标,想清楚首尾房子一定参与最远距离的计算。如果 colors[0] != colors[n - 1] 直接返回 n - 1,否则,假设首尾的颜色均为 c,找到颜色不为 c 的下标最大值 r 与最小值 l,取 max(r, n - 1 - l)

可以使用反证法证明,假设最远距离 i, j 在中间 0 < i < j < n - 1colors[i] = a, colors[j] = b, colors[0] == colors[n - 1] == c

  • 如果 a != c && b != c,显然 (0, j) 或者 (i, n - 1) 距离更远
  • 如果 a == c && b != c,那么 (0, j) 距离更远
  • 如果 a != c && b == c,那么 (i, n - 1) 更远

代码


/**
 * @date 2026-04-20 8:55
 */
public class MaxDistance2078 {

    public int maxDistance(int[] colors) {
        int n = colors.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = n - 1; j > i; j--) {
                if (colors[i] != colors[j]) {
                    res = Math.max(res, j - i);
                    break;
                }
            }
        }
        return res;
    }
}

性能

2087.网格图中机器人回家的最小代价

目标

给你一个 m x n 的网格图,其中 (0, 0) 是最左上角的格子,(m - 1, n - 1) 是最右下角的格子。给你一个整数数组 startPos ,startPos = [startrow, startcol] 表示 初始 有一个 机器人 在格子 (startrow, startcol) 处。同时给你一个整数数组 homePos ,homePos = [homerow, homecol] 表示机器人的 家 在格子 (homerow, homecol) 处。

机器人需要回家。每一步它可以往四个方向移动:上,下,左,右,同时机器人不能移出边界。每一步移动都有一定代价。再给你两个下标从 0 开始的额整数数组:长度为 m 的数组 rowCosts 和长度为 n 的数组 colCosts 。

  • 如果机器人往 上 或者往 下 移动到第 r 行 的格子,那么代价为 rowCosts[r] 。
  • 如果机器人往 左 或者往 右 移动到第 c 列 的格子,那么代价为 colCosts[c] 。

请你返回机器人回家需要的 最小总代价 。

示例 1:

输入:startPos = [1, 0], homePos = [2, 3], rowCosts = [5, 4, 3], colCosts = [8, 2, 6, 7]
输出:18
解释:一个最优路径为:
从 (1, 0) 开始
-> 往下走到 (2, 0) 。代价为 rowCosts[2] = 3 。
-> 往右走到 (2, 1) 。代价为 colCosts[1] = 2 。
-> 往右走到 (2, 2) 。代价为 colCosts[2] = 6 。
-> 往右走到 (2, 3) 。代价为 colCosts[3] = 7 。
总代价为 3 + 2 + 6 + 7 = 18

示例 2:

输入:startPos = [0, 0], homePos = [0, 0], rowCosts = [5], colCosts = [26]
输出:0
解释:机器人已经在家了,所以不需要移动。总代价为 0 。

说明:

  • m == rowCosts.length
  • n == colCosts.length
  • 1 <= m, n <= 10^5
  • 0 <= rowCosts[r], colCosts[c] <= 10^4
  • startPos.length == 2
  • homePos.length == 2
  • 0 <= startrow, homerow < m
  • 0 <= startcol, homecol < n

思路

有一个 m x n 矩阵,其中有一个机器人在坐标 startPos,机器人家的坐标是 homePos,机器人可以上下左右移动,从上/下移动到第 i 行的成本为 rowCosts[i],从左/右移动到第 j 列的成本为 colCosts[j],求机器人回家需要的最小总代价。

根据题意代价均为非负数,只要不折返代价就是最小的,可以将横向与纵向移动分开考虑,分别计算 startPos[0]homePos[0] 以及 startPos[1]homePos[1] 的代价。

由于起点与家的相对位置是不确定的,循环的步长(+1 还是 -1)、结束条件(大于等于 还是 小于等于)需要动态定义。

网友题解则是直接减掉了起点的代价,统一由小坐标到大坐标累加成本。

代码


/**
 * @date 2026-04-07 11:21
 */
public class MinCost2087 {

    public int minCost(int[] startPos, int[] homePos, int[] rowCosts, int[] colCosts) {
        int res = 0;
        int sx = startPos[0];
        int sy = startPos[1];
        int hx = homePos[0];
        int hy = homePos[1];
        int dx = sx <= hx ? 1 : -1;
        int dy = sy <= hy ? 1 : -1;
        for (int i = sx + dx; dx == 1 ? i <= hx : i >= hx; i += dx) {
            res += rowCosts[i];
        }
        for (int i = sy + dy; dy == 1 ? i <= hy : i >= hy; i += dy) {
            res += colCosts[i];
        }
        return res;
    }

}

性能

3474.字典序最小的生成字符串

目标

给你两个字符串,str1 和 str2,其长度分别为 n 和 m 。

如果一个长度为 n + m - 1 的字符串 word 的每个下标 0 <= i <= n - 1 都满足以下条件,则称其由 str1 和 str2 生成:

  • 如果 str1[i] == 'T',则长度为 m 的 子字符串(从下标 i 开始)与 str2 相等,即 word[i..(i + m - 1)] == str2。
  • 如果 str1[i] == 'F',则长度为 m 的 子字符串(从下标 i 开始)与 str2 不相等,即 word[i..(i + m - 1)] != str2。

返回可以由 str1 和 str2 生成 的 字典序最小 的字符串。如果不存在满足条件的字符串,返回空字符串 ""。

如果字符串 a 在第一个不同字符的位置上比字符串 b 的对应字符在字母表中更靠前,则称字符串 a 的 字典序 小于 字符串 b。

如果前 min(a.length, b.length) 个字符都相同,则较短的字符串字典序更小。

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

示例 1:

输入: str1 = "TFTF", str2 = "ab"
输出: "ababa"
解释:
下表展示了字符串 "ababa" 的生成过程:
下标  T/F   长度为 m 的子字符串
0    'T'        "ab"
1    'F'        "ba"
2    'T'        "ab"
3    'F'        "ba"
字符串 "ababa" 和 "ababb" 都可以由 str1 和 str2 生成。
返回 "ababa",因为它的字典序更小。

示例 2:

输入: str1 = "TFTF", str2 = "abc"
输出: ""
解释:
无法生成满足条件的字符串。

示例 3:

输入: str1 = "F", str2 = "d"
输出: "a"

说明:

  • 1 <= n == str1.length <= 10^4
  • 1 <= m == str2.length <= 500
  • str1 仅由 'T' 或 'F' 组成。
  • str2 仅由小写英文字母组成。

思路

有两个字符串 str1str2,长度分别为 nm。对于长度为 n + m - 1 的字符串 word,如果对于每一个位置 i 都满足以下条件,则称其由 str1str2 生成:

  • 如果 str1[i] == T,那么从 i 开始长度为 m 的子串 word[i ~ i + m - 1]str2 相同。
  • 如果 str1[i] == F,那么从 i 开始长度为 m 的子串 word[i ~ i + m - 1]str2 不同。

返回由 str1str2 生成的字典序最小的字符串,如果无法生成返回空字符。

// todo

代码

性能

2573.找出对应LCP矩阵的字符串

目标

对任一由 n 个小写英文字母组成的字符串 word ,我们可以定义一个 n x n 的矩阵,并满足:

  • lcp[i][j] 等于子字符串 word[i,...,n-1] 和 word[j,...,n-1] 之间的最长公共前缀的长度。

给你一个 n x n 的矩阵 lcp 。返回与 lcp 对应的、按字典序最小的字符串 word 。如果不存在这样的字符串,则返回空字符串。

对于长度相同的两个字符串 a 和 b ,如果在 a 和 b 不同的第一个位置,字符串 a 的字母在字母表中出现的顺序先于 b 中的对应字母,则认为字符串 a 按字典序比字符串 b 小。例如,"aabd" 在字典上小于 "aaca" ,因为二者不同的第一位置是第三个字母,而 'b' 先于 'c' 出现。

示例 1:

输入:lcp = [[4,0,2,0],[0,3,0,1],[2,0,2,0],[0,1,0,1]]
输出:"abab"
解释:lcp 对应由两个交替字母组成的任意 4 字母字符串,字典序最小的是 "abab" 。

示例 2:

输入:lcp = [[4,3,2,1],[3,3,2,1],[2,2,2,1],[1,1,1,1]]
输出:"aaaa"
解释:lcp 对应只有一个不同字母的任意 4 字母字符串,字典序最小的是 "aaaa" 。 

示例 3:

输入:lcp = [[4,3,2,1],[3,3,2,1],[2,2,2,1],[1,1,1,3]]
输出:""
解释:lcp[3][3] 无法等于 3 ,因为 word[3,...,3] 仅由单个字母组成;因此,不存在答案。

说明:

  • 1 <= n == lcp.length == lcp[i].length <= 1000
  • 0 <= lcp[i][j] <= n

思路

代码

性能

1727.重新排列后的最大子矩阵

目标

给你一个二进制矩阵 matrix ,它的大小为 m x n ,你可以将 matrix 中的 列 按任意顺序重新排列。

请你返回最优方案下将 matrix 重新排列后,全是 1 的子矩阵面积。

示例 1:

输入:matrix = [[0,0,1],[1,1,1],[1,0,1]]
输出:4
解释:你可以按照上图方式重新排列矩阵的每一列。
最大的全 1 子矩阵是上图中加粗的部分,面积为 4 。

示例 2:

输入:matrix = [[1,0,1,0,1]]
输出:3
解释:你可以按照上图方式重新排列矩阵的每一列。
最大的全 1 子矩阵是上图中加粗的部分,面积为 3 。

示例 3:

输入:matrix = [[1,1,0],[1,0,1]]
输出:2
解释:由于你只能整列整列重新排布,所以没有比面积为 2 更大的全 1 子矩形。

示例 4:

输入:matrix = [[0,0],[0,0]]
输出:0
解释:由于矩阵中没有 1 ,没有任何全 1 的子矩阵,所以面积为 0 。

说明:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m * n <= 10^5
  • matrix[i][j] 要么是 0 ,要么是 1 。

提示:

  • For each column, find the number of consecutive ones ending at each position.
  • For each row, sort the cumulative ones in non-increasing order and "fit" the largest submatrix.

思路

有一个 m x n 二进制矩阵 matrix,可以重新排列矩阵的列,求全 1 子矩阵的最大面积。

如果不允许重新排列,统计全1子矩阵的个数,参考 1504.统计全1子矩形 枚举底边与右边界。

针对每一列记录以当前行为终点连续 1 的高度,然后按高度排序,那么以当前行为底的最大子矩阵就可以求出来了。

代码


/**
 * @date 2026-03-17 9:37
 */
public class LargestSubmatrix1727 {

    public int largestSubmatrix(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] ones = new int[m][n];
        System.arraycopy(matrix[0], 0, ones[0], 0, n);
        for (int i = 1; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 1) {
                    ones[i][j] = ones[i - 1][j] + 1;
                }
            }
        }
        int res = 0;
        for (int[] row : ones) {
            Arrays.sort(row);
            int i = n - 1;
            int l = 1;
            int max = 0;
            while (i >= 0 && row[i] > 0) {
                max = Math.max(max, row[i--] * l++);
            }
            res = Math.max(res, max);
        }
        return res;
    }
}

性能

3600.升级后最大生成树稳定性

目标

给你一个整数 n,表示编号从 0 到 n - 1 的 n 个节点,以及一个 edges 列表,其中 edges[i] = [ui, vi, si, musti]:

  • ui 和 vi 表示节点 ui 和 vi 之间的一条无向边。
  • si 是该边的强度。
  • musti 是一个整数(0 或 1)。如果 musti == 1,则该边 必须 包含在生成树中,且 不能升级 。

你还有一个整数 k,表示你可以执行的最多 升级 次数。每次升级会使边的强度 翻倍 ,且每条可升级边(即 musti == 0)最多只能升级一次。

一个生成树的 稳定性 定义为其中所有边的 最小 强度。

返回任何有效生成树可能达到的 最大 稳定性。如果无法连接所有节点,返回 -1。

注意: 图的一个 生成树(spanning tree)是该图中边的一个子集,它满足以下条件:

  • 将所有节点连接在一起(即图是 连通的 )。
  • 不 形成任何环。
  • 包含 恰好 n - 1 条边,其中 n 是图中节点的数量。

示例 1:

输入: n = 3, edges = [[0,1,2,1],[1,2,3,0]], k = 1
输出: 2
解释:
边 [0,1] 强度为 2,必须包含在生成树中。
边 [1,2] 是可选的,可以使用一次升级将其强度从 3 提升到 6。
最终的生成树包含这两条边,强度分别为 2 和 6。
生成树中的最小强度是 2,即最大可能稳定性。

示例 2:

输入: n = 3, edges = [[0,1,4,0],[1,2,3,0],[0,2,1,0]], k = 2
输出: 6
解释:
所有边都是可选的,且最多可以进行 k = 2 次升级。
将边 [0,1] 从 4 升级到 8,将边 [1,2] 从 3 升级到 6。
生成树包含这两条边,强度分别为 8 和 6。
生成树中的最小强度是 6,即最大可能稳定性。

示例 3:

输入: n = 3, edges = [[0,1,1,1],[1,2,1,1],[2,0,1,1]], k = 0
输出: -1
解释:
所有边都是必选的,构成了一个环,这违反了生成树无环的性质。因此返回 -1。

说明:

  • 2 <= n <= 10^5
  • 1 <= edges.length <= 10^5
  • edges[i] = [ui, vi, si, musti]
  • 0 <= ui, vi < n
  • ui != vi
  • 1 <= si <= 10^5
  • musti 是 0 或 1。
  • 0 <= k <= n
  • 没有重复的边。

思路

// todo

代码

性能

1536.排布二进制网格的最少交换次数

目标

给你一个 n x n 的二进制网格 grid,每一次操作中,你可以选择网格的 相邻两行 进行交换。

一个符合要求的网格需要满足主对角线以上的格子全部都是 0 。

请你返回使网格满足要求的最少操作次数,如果无法使网格符合要求,请你返回 -1 。

主对角线指的是从 (1, 1) 到 (n, n) 的这些格子。

示例 1:

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

示例 2:

输入:grid = [[0,1,1,0],[0,1,1,0],[0,1,1,0],[0,1,1,0]]
输出:-1
解释:所有行都是一样的,交换相邻行无法使网格符合要求。

示例 3:

输入:grid = [[1,0,0],[1,1,0],[1,1,1]]
输出:0

说明:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 200
  • grid[i][j] 要么是 0 要么是 1 。

提示:

  • For each row of the grid calculate the most right 1 in the grid in the array maxRight.
  • To check if there exist answer, sort maxRight and check if maxRight[i] ≤ i for all possible i's.
  • If there exist an answer, simulate the swaps.

思路

有一个 n x n 的二进制矩阵,每次操作可以交换相邻的两行,求使得矩阵主对角线 之上 的所有格子变为 0 所需的最小操作次数。

i 行最右侧的 1 的下标不能超过 i,如果不满足条件,找到第一个满足条件的行进行交换。这种贪心策略之所以可行,是因为如果存在多个满足条件的行,由于行从上到下的条件越来越宽松,满足当前行的条件也必定满足后续行,因此选最近的行交换即可。

代码


/**
 * @date 2026-03-02 8:45
 */
public class MinSwaps1536 {

    public int minSwaps(int[][] grid) {
        int n = grid.length;
        int[] maxRight = new int[n];
        for (int i = 0; i < n; i++) {
            for (int j = n - 1; j >= 0; j--) {
                if (grid[i][j] == 1) {
                    maxRight[i] = j;
                    break;
                }
            }
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (maxRight[i] <= i) {
                continue;
            }
            boolean flag = false;
            int prev = maxRight[i];
            for (int j = i + 1; j < n; j++) {
                if (maxRight[j] <= i) {
                    res += j - i;
                    flag = true;
                    maxRight[j] = prev;
                    break;
                }
                int tmp = maxRight[j];
                maxRight[j] = prev;
                prev = tmp;
            }
            if (!flag) {
                return -1;
            }
        }
        return res;
    }

}

性能

1689.十-二进制数的最少数目

目标

如果一个十进制数字不含任何前导零,且每一位上的数字不是 0 就是 1 ,那么该数字就是一个 十-二进制数 。例如,101 和 1100 都是 十-二进制数,而 112 和 3001 不是。

给你一个表示十进制整数的字符串 n ,返回和为 n 的 十-二进制数 的最少数目。

示例 1:

输入:n = "32"
输出:3
解释:10 + 11 + 11 = 32

示例 2:

输入:n = "82734"
输出:8

示例 3:

输入:n = "27346209830709182346"
输出:9

说明:

  • 1 <= n.length <= 10^5
  • n 仅由数字组成
  • n 不含任何前导零并总是表示正整数

思路

返回整数 n 中的最大数字即可。

代码


/**
 * @date 2026-04-17 16:06
 */
public class MinPartitions1689 {

    public int minPartitions(String n) {
        int res = 0;
        char[] chars = n.toCharArray();
        for (char c : chars) {
            res = Math.max(res, c - '0');
        }
        return res;
    }
}

性能