3546.等和矩阵分割I

目标

给你一个由正整数组成的 m x n 矩阵 grid。你的任务是判断是否可以通过 一条水平或一条垂直分割线 将矩阵分割成两部分,使得:

  • 分割后形成的每个部分都是 非空 的。
  • 两个部分中所有元素的和 相等 。

如果存在这样的分割,返回 true;否则,返回 false。

示例 1:

输入: grid = [[1,4],[2,3]]
输出: true
解释:
在第 0 行和第 1 行之间进行水平分割,得到两个非空部分,每部分的元素之和为 5。因此,答案是 true。

示例 2:

输入: grid = [[1,3],[2,4]]
输出: false
解释:
无论是水平分割还是垂直分割,都无法使两个非空部分的元素之和相等。因此,答案是 false。

说明:

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

思路

有一个 m x n 矩阵,判断能否用一条水平线或者垂直线将矩阵分割成两部分,使得两部分的和相等,并且每一部分非空。

先求出总和,根据题意枚举所有分割水平线与垂直线判断两部分的和是否相等。

代码


/**
 * @date 2026-03-25 9:03
 */
public class CanPartitionGrid3546 {

    public boolean canPartitionGrid(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        long sum = 0L;
        for (int[] row : grid) {
            for (int num : row) {
                sum += num;
            }
        }
        if (sum % 2 == 1) {
            return false;
        }
        long half = sum / 2;
        long prefix = 0L;
        for (int i = 0; i < m - 1; i++) {
            for (int num : grid[i]) {
                prefix += num;
            }
            if (prefix << 1 == sum) {
                return true;
            } else if (prefix > half) {
                break;
            }
        }
        prefix = 0L;
        for (int j = 0; j < n - 1; j++) {
            for (int i = 0; i < m; i++) {
                prefix += grid[i][j];
            }
            if (prefix << 1 == sum) {
                return true;
            } else if (prefix > half) {
                break;
            }
        }
        return false;
    }

}

性能

1878.矩阵中最大的三个菱形和

目标

给你一个 m x n 的整数矩阵 grid 。

菱形和 指的是 grid 中一个正菱形 边界 上的元素之和。本题中的菱形必须为正方形旋转45度,且四个角都在一个格子当中。下图是四个可行的菱形,每个菱形和应该包含的格子都用了相应颜色标注在图中。

注意,菱形可以是一个面积为 0 的区域,如上图中右下角的紫色菱形所示。

请你按照 降序 返回 grid 中三个最大的 互不相同的菱形和 。如果不同的和少于三个,则将它们全部返回。

示例 1:

输入:grid = [[3,4,5,1,3],[3,3,4,2,3],[20,30,200,40,10],[1,5,5,4,1],[4,3,2,2,5]]
输出:[228,216,211]
解释:最大的三个菱形和如上图所示。
- 蓝色:20 + 3 + 200 + 5 = 228
- 红色:200 + 2 + 10 + 4 = 216
- 绿色:5 + 200 + 4 + 2 = 211

示例 2:

输入:grid = [[1,2,3],[4,5,6],[7,8,9]]
输出:[20,9,8]
解释:最大的三个菱形和如上图所示。
- 蓝色:4 + 2 + 6 + 8 = 20
- 红色:9 (右下角红色的面积为 0 的菱形)
- 绿色:8 (下方中央面积为 0 的菱形)

示例 3:

输入:grid = [[7,7,7]]
输出:[7]
解释:所有三个可能的菱形和都相同,所以返回 [7] 。

提示:

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

思路

有一个 m x n 矩阵 grid,返回矩阵中最大的三个 不相同 的菱形和。满足条件的菱形指矩阵内部的正方形顺时针旋转 45°,并且四个角必须占一个格子的正方形。菱形和就是其边界上的元素之和。

枚举所有合法的正菱形。针对每一个元素 (i, j),枚举以它作为对角线交点的所有可能的正菱形(正方形)。

经过观察发现,如果边上元素个数为 k + 1,那么上顶点坐标为 (i - k, j),下顶点坐标为 (i + k, j),左顶点坐标为 (i, j - k),右顶点坐标为 (i, j + k)

枚举 (i, j) 所在行 [j - k, j + k] 范围内的元素。注意左右顶点的上下元素重合到自身,不能重复累加。特殊处理两端点,中间枚举 x ∈ [j - k + 1, j + k - 1] 累加上下 h 的元素值,即 (i - h, x)(i + h, x)。其中 h = k - Math.abs(j - x)k 表示中点到四个角的距离,x 表示列标,j - x 表示列标距离中点的距离。

上面的解法针对每一个中点重复累加了边上的元素和。更好做法是使用两个二维数组分别表示两个方向 上截止到 (u, v) 的前缀和。中点确定之后,可以确定边的四个角,进而可以计算前缀和。注意不要重复计算四个角。

代码


/**
 * @date 2026-03-16 9:37
 */
public class GetBiggestThree1878 {

    public int[] getBiggestThree(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        TreeSet<Integer> set = new TreeSet<>((a, b) -> b - a);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                for (int k = 0; i + k < m && i - k >= 0 && j - k >= 0 && j + k < n; k++) {
                    int sum = 0;
                    if (k == 0) {
                        set.add(grid[i][j]);
                        continue;
                    }
                    sum += grid[i][j - k] + grid[i][j + k];
                    for (int x = j - k + 1; x <= j + k - 1; x++) {
                        int h = k - Math.abs(j - x);
                        sum += grid[i - h][x] + grid[i + h][x];
                    }
                    set.add(sum);
                }
            }
        }
        int size = Math.min(3, set.size());
        int[] res = new int[size];
        Iterator<Integer> iterator = set.iterator();
        for (int i = 0; i < size; i++) {
            res[i] = iterator.next();
        }
        return res;
    }

}

性能

1980.找出不同的二进制字符串

目标

给你一个字符串数组 nums ,该数组由 n 个 互不相同 的二进制字符串组成,且每个字符串长度都是 n 。请你找出并返回一个长度为 n 且 没有出现 在 nums 中的二进制字符串。如果存在多种答案,只需返回 任意一个 即可。

示例 1:

输入:nums = ["01","10"]
输出:"11"
解释:"11" 没有出现在 nums 中。"00" 也是正确答案。

示例 2:

输入:nums = ["00","01"]
输出:"11"
解释:"11" 没有出现在 nums 中。"10" 也是正确答案。

示例 3:

输入:nums = ["111","011","001"]
输出:"101"
解释:"101" 没有出现在 nums 中。"000"、"010"、"100"、"110" 也是正确答案。

说明:

  • n == nums.length
  • 1 <= n <= 16
  • nums[i].length == n
  • nums[i] 为 '0' 或 '1'
  • nums 中的所有字符串 互不相同

思路

n 个长度为 n 的二进制字符串数组,构造一个长度为 n 的字符串,使它与数组中的字符串不同,答案可能有多个返回任一一个即可。

康托对角线,构造字符串的第 i 个位置 与 nums[i] 的第 i 个位置不同,这样可以保证构造出的字符串与数组中的所有字符串都不同。

代码


/**
 * @date 2026-03-09 18:08
 */
public class FindDifferentBinaryString1980 {

    public String findDifferentBinaryString_v1(String[] nums) {
        int n = nums.length;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            int c = nums[i].charAt(i) - '0';
            sb.append(c ^ 1);
        }
        return sb.toString();
    }

}

性能

1758.生成交替二进制字符串的最少操作数

目标

给你一个仅由字符 '0' 和 '1' 组成的字符串 s 。一步操作中,你可以将任一 '0' 变成 '1' ,或者将 '1' 变成 '0' 。

交替字符串 定义为:如果字符串中不存在相邻两个字符相等的情况,那么该字符串就是交替字符串。例如,字符串 "010" 是交替字符串,而字符串 "0100" 不是。

返回使 s 变成 交替字符串 所需的 最少 操作数。

示例 1:

输入:s = "0100"
输出:1
解释:如果将最后一个字符变为 '1' ,s 就变成 "0101" ,即符合交替字符串定义。

示例 2:

输入:s = "10"
输出:0
解释:s 已经是交替字符串。

示例 3:

输入:s = "1111"
输出:2
解释:需要 2 步操作得到 "0101" 或 "1010" 。

说明:

  • 1 <= s.length <= 10^4
  • s[i] 是 '0' 或 '1'

思路

有一个字符串 s,每次操作可以任选一个 0 变成 1 或者 1 变成 0,返回将字符串变为交替字符串的最小操作次数。

对于特定长度的交替字符串只有两种可能,考虑第一个字符是 1 还是 0,记录将 s 变为交替字符的操作次数,取其最小值即可。

一般地,如果变成 t0=010101⋯ 需要修改 k 次,那么由于 t1=101010⋯ 每个位置都和 t0 不同,变成 t0 需要修改的字符,变成 t1 无需修改;变成 t0 不需要修改的字符,变成 t1 需要修改。所以变成 t1 需要修改 n−k 次。答案为 min(k, n−k)

代码


/**
 * @date 2026-03-05 8:51
 */
public class MinOperations1758 {

    public int minOperations(String s) {
        int res1 = 0, res2 = 0;
        char[] chars = s.toCharArray();
        char prev1 = '0', prev2 = '1';
        for (char c : chars) {
            if (c == prev1) {
                res1++;
            }
            if (c == prev2) {
                res2++;
            }
            prev1 ^= 1;
            prev2 ^= 1;
        }
        return Math.min(res1, res2);
    }

}

性能

1582.二进制矩阵中的特殊位置

目标

给定一个 m x n 的二进制矩阵 mat,返回矩阵 mat 中特殊位置的数量。

如果位置 (i, j) 满足 mat[i][j] == 1 并且行 i 与列 j 中的所有其他元素都是 0(行和列的下标从 0 开始计数),那么它被称为 特殊 位置。

示例 1:

输入:mat = [[1,0,0],[0,0,1],[1,0,0]]
输出:1
解释:位置 (1, 2) 是一个特殊位置,因为 mat[1][2] == 1 且第 1 行和第 2 列的其他所有元素都是 0。

示例 2:

输入:mat = [[1,0,0],[0,1,0],[0,0,1]]
输出:3
解释:位置 (0, 0),(1, 1) 和 (2, 2) 都是特殊位置。

说明:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 100
  • mat[i][j] 是 0 或 1。

思路

返回二进制矩阵 mat 的特殊位置个数,所谓特殊位置指,当前位置的值为 1,且所在行列的其它元素均为 0

计算每行每列中 1 的个数,如果当前单元格的值为 1,且行列 1 的个数也是 1,累加结果。

代码


/**
 * @date 2026-03-04 9:50
 */
public class NumSpecial1582 {

    public int numSpecial(int[][] mat) {
        int m = mat.length, n = mat[0].length;
        int[] rc = new int[m];
        int[] cc = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                rc[i] += mat[i][j];
                cc[j] += mat[i][j];
            }
        }
        int res = 0;
        for (int i = 0; i < m; i++) {
            if (rc[i] > 1) {
                continue;
            }
            for (int j = 0; j < n; j++) {
                if (cc[j] == 1 && mat[i][j] == 1) {
                    res++;
                }
            }
        }
        return res;
    }
}

性能

1461.检查一个字符串是否包含所有长度为K的二进制子串

目标

给你一个二进制字符串 s 和一个整数 k 。如果所有长度为 k 的二进制字符串都是 s 的子串,请返回 true ,否则请返回 false 。

示例 1:

输入:s = "00110110", k = 2
输出:true
解释:长度为 2 的二进制串包括 "00","01","10" 和 "11"。它们分别是 s 中下标为 0,1,3,2 开始的长度为 2 的子串。

示例 2:

输入:s = "0110", k = 1
输出:true
解释:长度为 1 的二进制串包括 "0" 和 "1",显然它们都是 s 的子串。

示例 3:

输入:s = "0110", k = 2
输出:false
解释:长度为 2 的二进制串 "00" 没有出现在 s 中。

说明:

  • 1 <= s.length <= 5 * 10^5
  • s[i] 不是 '0' 就是 '1'
  • 1 <= k <= 20

思路

检查一个二进制字符串是否包含所有长度为 k 的二进制子串。

长度为 k 的二进制子串的个数有 2^k 个,将子串视为二进制数字,使用长度为 k 的滑动窗口,记录窗口内子串表示的数字,判断是否所有数字都出现过即可。将左边界移出窗口需要将左边第一位置零 num = (num & (1 << (k - 1))) ^ num。先提取左边第一位,其余位均为 0,然后与 num 异或即可。

代码


/**
 * @date 2026-02-24 11:31
 */
public class HasAllCodes1461 {

    public boolean hasAllCodes(String s, int k) {
        if (k > 18) {
            return false;
        }
        int n = 1 << k;
        boolean[] visited = new boolean[n];
        int num = 0;
        char[] chars = s.toCharArray();
        int l = 0;
        for (int r = 0; r < chars.length; r++) {
            num = (num << 1) | (chars[r] - '0');
            if (r - l + 1 == k) {
                visited[num] = true;
                num = (num & (1 << (k - 1))) ^ num;
                l++;
            }
        }
        for (boolean b : visited) {
            if (!b) {
                return false;
            }
        }
        return true;
    }

}

性能

762.二进制表示中质数个计算置位

目标

给你两个整数 left 和 right ,在闭区间 [left, right] 范围内,统计并返回 计算置位位数为质数 的整数个数。

计算置位位数 就是二进制表示中 1 的个数。

例如, 21 的二进制表示 10101 有 3 个计算置位。

示例 1:

输入:left = 6, right = 10
输出:4
解释:
6 -> 110 (2 个计算置位,2 是质数)
7 -> 111 (3 个计算置位,3 是质数)
9 -> 1001 (2 个计算置位,2 是质数)
10-> 1010 (2 个计算置位,2 是质数)
共计 4 个计算置位为质数的数字。

示例 2:

输入:left = 10, right = 15
输出:5
解释:
10 -> 1010 (2 个计算置位, 2 是质数)
11 -> 1011 (3 个计算置位, 3 是质数)
12 -> 1100 (2 个计算置位, 2 是质数)
13 -> 1101 (3 个计算置位, 3 是质数)
14 -> 1110 (3 个计算置位, 3 是质数)
15 -> 1111 (4 个计算置位, 4 不是质数)
共计 5 个计算置位为质数的数字。

说明:

  • 1 <= left <= right <= 10^6
  • 0 <= right - left <= 10^4

思路

返回 [left, right] 范围内的数字中,二进制表示中 1 的个数为质数的数字个数。

1 ~ 10^6 的数位长度不超过 201 ~ 20 之间的质数有 2, 3, 5, 7, 11, 13, 17, 19。枚举每个数字计算其二进制表示中 1 的个数,判断是否是质数。

代码


/**
 * @date 2026-02-24 15:46
 */
public class CountPrimeSetBits762 {

    public static Set<Integer> primes;

    static {
        primes = new HashSet<>();
        primes.add(2);
        primes.add(3);
        primes.add(5);
        primes.add(7);
        primes.add(11);
        primes.add(13);
        primes.add(17);
        primes.add(19);
    }

    public int countPrimeSetBits(int left, int right) {
        int res = 0;
        for (int i = left; i <= right; i++) {
            int bitCount = Integer.bitCount(i);
            if (primes.contains(bitCount)) {
                res++;
            }
        }
        return res;
    }

}

性能

696.计数二进制子串

目标

给定一个字符串 s,统计并返回具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是成组连续的。

重复出现(不同位置)的子串也要统计它们出现的次数。

示例 1:

输入:s = "00110011"
输出:6
解释:6 个子串满足具有相同数量的连续 1 和 0 :"0011"、"01"、"1100"、"10"、"0011" 和 "01" 。
注意,一些重复出现的子串(不同位置)要统计它们出现的次数。
另外,"00110011" 不是有效的子串,因为所有的 0(还有 1 )没有组合在一起。

示例 2:

输入:s = "10101"
输出:4
解释:有 4 个子串:"10"、"01"、"10"、"01" ,具有相同数量的连续 1 和 0 。

说明:

  • 1 <= s.length <= 10^5
  • s[i] 为 '0' 或 '1'

思路

统计二进制字符串 s 中满足条件的子串个数。子串需满足 01 的个数相等,且子串中所有 0 都是相邻且连续的(由于数量相同,子串中所有的 1 也是相邻且连续的),即 01 都是成组连续的。

00011 满足条件的子串个数为 min(3, 2),可以分组循环字符串,记录前一组的个数,累加二者最小值即可。

代码


/**
 * @date 2026-02-24 16:14
 */
public class CountBinarySubstrings696 {

    public int countBinarySubstrings(String s) {
        char[] chars = s.toCharArray();
        int n = chars.length;
        int preCnt = 0;
        int i = 0;
        int res = 0;
        while (i < n) {
            int start = i;
            while (i < n && chars[start] == chars[i]) {
                i++;
            }
            int cnt = i - start;
            res += Math.min(preCnt, cnt);
            preCnt = cnt;
        }
        return res;
    }

}

性能

401.二进制手表

目标

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。

  • 例如,下面的二进制手表读取 "4:51" 。

给你一个整数 turnedOn ,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以 按任意顺序 返回答案。

小时不会以零开头:

  • 例如,"01:00" 是无效的时间,正确的写法应该是 "1:00" 。

分钟必须由两位数组成,可能会以零开头:

  • 例如,"10:2" 是无效的时间,正确的写法应该是 "10:02" 。

示例 1:

输入:turnedOn = 1
输出:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]

示例 2:

输入:turnedOn = 9
输出:[]

说明:

  • 0 <= turnedOn <= 10

思路

有一个二进制手表,用 4 bit 表示小时, 6 bit 代表分钟。已知 bit 位为 1 的数量 turnedOn,返回所有可能的时间。

将数字按照置位数量分组,枚举不同的划分即可。

代码


/**
 * @date 2026-02-24 17:34
 */
public class ReadBinaryWatch401 {

    public List<String> readBinaryWatch(int turnedOn) {
        Map<Integer, List<Integer>> map = new HashMap<>();
        for (int i = 0; i < 60; i++) {
            int bc = Integer.bitCount(i);
            map.putIfAbsent(bc, new ArrayList<>());
            map.get(bc).add(i);
        }
        List<String> res = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            List<Integer> hours = map.get(i);
            for (Integer h : hours) {
                if (h > 11) {
                    break;
                }
                List<Integer> minutes = map.get(turnedOn - i);
                if (minutes == null) {
                    continue;
                }
                for (Integer m : minutes) {
                    res.add(h + ":" + ((m < 10) ? "0" : "") + m);
                }
            }
        }
        return res;
    }
}

性能

3713.最长的平衡子串I

目标

给你一个由小写英文字母组成的字符串 s。

如果一个 子串 中所有 不同 字符出现的次数都 相同 ,则称该子串为 平衡 子串。

请返回 s 的 最长平衡子串 的 长度 。

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

示例 1:

输入: s = "abbac"
输出: 4
解释:
最长的平衡子串是 "abba",因为不同字符 'a' 和 'b' 都恰好出现了 2 次。

示例 2:

输入: s = "zzabccy"
输出: 4
解释:
最长的平衡子串是 "zabc",因为不同字符 'z'、'a'、'b' 和 'c' 都恰好出现了 1 次。

示例 3:

输入: s = "aba"
输出: 2
解释:
最长的平衡子串之一是 "ab",因为不同字符 'a' 和 'b' 都恰好出现了 1 次。另一个最长的平衡子串是 "ba"。

说明:

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

思路

定义平衡子串是字符出现次数相同的字符串,给定一个由小写英文字母组成的字符串,返回最长的平衡子串长度。

3719.最长平衡子数组I 类似,本题需要保证子串中字母出现的次数都相等,可以统计子串内字母的出现次数并判断是否完全相同。

在遍历的过程中,如何判断出现的字符是否都达到 k 个?字母种类数 * k 应当等于子串长度。

代码


/**
 * @date 2026-02-12 9:19
 */
public class LongestBalanced3713 {

    public int longestBalanced(String s) {
        int n = s.length();
        int res = 0;
        for (int i = 0; i < n; i++) {
            Map<Character, Integer> cnt = new HashMap<>();
            here:
            for (int j = i; j < n; j++) {
                char c = s.charAt(j);
                cnt.merge(c, 1, Integer::sum);
                int t = cnt.get(c);
                for (Integer value : cnt.values()) {
                    if (value != t) {
                        continue here;
                    }
                }
                res = Math.max(res, j - i + 1);
            }
        }
        return res;
    }
}

性能