3666.使二进制字符串全为1的最少操作次数

目标

给你一个二进制字符串 s 和一个整数 k。

在一次操作中,你必须选择 恰好 k 个 不同的 下标,并将每个 '0' 翻转 为 '1',每个 '1' 翻转为 '0'。

返回使字符串中所有字符都等于 '1' 所需的 最少 操作次数。如果不可能,则返回 -1。

示例 1:

输入: s = "110", k = 1
输出: 1
解释:
s 中有一个 '0'。
由于 k = 1,我们可以直接在一次操作中翻转它。

示例 2:

输入: s = "0101", k = 3
输出: 2
解释:
每次操作选择 k = 3 个下标的一种最优操作方案是:
操作 1:翻转下标 [0, 1, 3]。s 从 "0101" 变为 "1000"。
操作 2:翻转下标 [1, 2, 3]。s 从 "1000" 变为 "1111"。
因此,最少操作次数为 2。

示例 3:

输入: s = "101", k = 2
输出: -1
解释:
由于 k = 2 且 s 中只有一个 '0',因此不可能通过翻转恰好 k 个位来使所有字符变为 '1'。因此,答案是 -1。

说明:

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

思路

代码

性能

1404.将二进制表示减到1的步骤数

目标

给你一个以二进制形式表示的数字 s 。请你返回按下述规则将其减少到 1 所需要的步骤数:

如果当前数字为偶数,则将其除以 2 。

如果当前数字为奇数,则将其加上 1 。

题目保证你总是可以按上述规则将测试用例变为 1 。

示例 1:

输入:s = "1101"
输出:6
解释:"1101" 表示十进制数 13 。
Step 1) 13 是奇数,加 1 得到 14 
Step 2) 14 是偶数,除 2 得到 7
Step 3) 7  是奇数,加 1 得到 8
Step 4) 8  是偶数,除 2 得到 4  
Step 5) 4  是偶数,除 2 得到 2 
Step 6) 2  是偶数,除 2 得到 1  

示例 2:

输入:s = "10"
输出:1
解释:"10" 表示十进制数 2 。
Step 1) 2 是偶数,除 2 得到 1 

示例 3:

输入:s = "1"
输出:0

说明:

  • 1 <= s.length <= 500
  • s 由字符 '0' 或 '1' 组成。
  • s[0] == '1'

思路

有一个二进制表示的数字,如果当前是偶数,可以将数字除以 2,即右移;如果当前是奇数,将数字加 1。求将数字变为 1 需要的操作数。

根据题意模拟,使用变量 carry 保存进位,从右向左遍历,判断最后一位数字 dcarry 相加后的奇偶性,即 sum = d + carry。如果 sum 是偶数(0 或者 2),右移后这一位消失,只需考虑 carry = sum / 2;如果 sum 是奇数,加一后进位 carry = 1,此时当前数字变为偶数,可以将这两个操作合并。

代码


/**
 * @date 2026-02-26 8:51
 */
public class NumSteps1404 {

    public int numSteps(String s) {
        int res = 0;
        int n = s.length();
        char[] chars = s.toCharArray();
        int carry = 0;
        for (int i = n - 1; i > 0; i--) {
            int d = chars[i] - '0';
            int sum = d + carry;
            if (sum % 2 == 0) {
                carry = sum / 2;
                res++;
            } else {
                carry = 1;
                res += 2;
            }
        }
        return res + carry;
    }

}

性能

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

}

性能

67.二进制求和

目标

给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。

示例 1:

输入:a = "11", b = "1"
输出:"100"

示例 2:

输入:a = "1010", b = "1011"
输出:"10101"

说明:

  • 1 <= a.length, b.length <= 10^4
  • a 和 b 仅由字符 '0' 或 '1' 组成
  • 字符串如果不是 "0" ,就不含前导零

思路

对二进制字符串求和,将结果以二进制字符串的形式返回。

代码


/**
 * @date 2024-05-25 21:50
 */
public class AddBinary67 {

    public String addBinary_new(String a, String b) {
        int al = a.length();
        int bl = b.length();
        int n = Math.max(a.length(), b.length());
        int i = 1;
        StringBuilder sb = new StringBuilder();
        // 进位
        int j = 0;
        while (i <= n) {
            int av = 0;
            // 计算从右侧起第 i 个位置的下标
            int ai = al - i;
            if (ai >= 0) {
                av = a.charAt(ai) - '0';
            }
            int bv = 0;
            int bi = bl - i;
            if (bi >= 0) {
                bv = b.charAt(bi) - '0';
            }
            int sum = av + bv + j;
            if (sum > 1) {
                sb.append(sum - 2);
                j = 1;
            } else {
                sb.append(sum);
                j = 0;
            }
            i++;
        }
        if (j == 1) {
            sb.append(j);
        }
        return sb.reverse().toString();
    }

}

性能

3047.求交集区域内的最大正方形面积

目标

在二维平面上存在 n 个矩形。给你两个下标从 0 开始的二维整数数组 bottomLeft 和 topRight,两个数组的大小都是 n x 2 ,其中 bottomLeft[i] 和 topRight[i] 分别代表第 i 个矩形的 左下角 和 右上角 坐标。

我们定义 向右 的方向为 x 轴正半轴(x 坐标增加),向左 的方向为 x 轴负半轴(x 坐标减少)。同样地,定义 向上 的方向为 y 轴正半轴(y 坐标增加),向下 的方向为 y 轴负半轴(y 坐标减少)。

你可以选择一个区域,该区域由两个矩形的 交集 形成。你需要找出能够放入该区域 内 的 最大 正方形面积,并选择最优解。

返回能够放入交集区域的正方形的 最大 可能面积,如果矩形之间不存在任何交集区域,则返回 0。

示例 1:

输入:bottomLeft = [[1,1],[2,2],[3,1]], topRight = [[3,3],[4,4],[6,6]]
输出:1
解释:边长为 1 的正方形可以放入矩形 0 和矩形 1 的交集区域,或矩形 1 和矩形 2 的交集区域。因此最大面积是边长 * 边长,即 1 * 1 = 1。
可以证明,边长更大的正方形无法放入任何交集区域。

示例 2:

输入:bottomLeft = [[1,1],[2,2],[1,2]], topRight = [[3,3],[4,4],[3,4]]
输出:1
解释:边长为 1 的正方形可以放入矩形 0 和矩形 1,矩形 1 和矩形 2,或所有三个矩形的交集区域。因此最大面积是边长 * 边长,即 1 * 1 = 1。
可以证明,边长更大的正方形无法放入任何交集区域。
请注意,区域可以由多于两个矩形的交集构成。

示例 3:

输入:bottomLeft = [[1,1],[3,3],[3,1]], topRight = [[2,2],[4,4],[4,2]]
输出:0
解释:不存在相交的矩形,因此,返回 0 。

说明:

  • n == bottomLeft.length == topRight.length
  • 2 <= n <= 10^3
  • bottomLeft[i].length == topRight[i].length == 2
  • 1 <= bottomLeft[i][0], bottomLeft[i][1] <= 10^7
  • 1 <= topRight[i][0], topRight[i][1] <= 10^7
  • bottomLeft[i][0] < topRight[i][0]
  • bottomLeft[i][1] < topRight[i][1]

思路

二维平面上有一些矩形,第 i 个矩形的左下坐标为 bottomLeft[i],右上坐标为 topRight[i],求其中任意两个矩形交集区域的最大正方形面积。

针对每一个矩形,枚举其它矩形,计算交集区域最大的正方形边长。

令 bl1 表示矩形 1 的左下坐标,tr1 表示矩形 1 的右上坐标,bl2、tr2 同理。

  • 相交区域的垂直边长为 Math.min(tr1[1], tr2[1]) - Math.max(bl1[1], bl2[1])
  • 相交区域的水平边长为 Math.min(tr1[0], tr2[0]) - Math.max(bl1[0], bl2[0])

代码


/**
 * @date 2026-01-21 9:08
 */
public class LargestSquareArea3047 {

    public long largestSquareArea_v1(int[][] bottomLeft, int[][] topRight) {
        long res = 0L;
        int n = bottomLeft.length;
        for (int i = 0; i < n; i++) {
            int[] bl1 = bottomLeft[i], tr1 = topRight[i];
            for (int j = i + 1; j < n; j++) {
                int[] bl2 = bottomLeft[j], tr2 = topRight[j];
                res = Math.max(res, Math.min(Math.min(tr1[1], tr2[1]) - Math.max(bl1[1], bl2[1]), Math.min(tr1[0], tr2[0]) - Math.max(bl1[0], bl2[0])));
            }
        }
        return res * res;
    }

}

性能

1266.访问所有点的最小时间

目标

平面上有 n 个点,点的位置用整数坐标表示 points[i] = [xi, yi] 。请你计算访问所有这些点需要的 最小时间(以秒为单位)。

你需要按照下面的规则在平面上移动:

  • 每一秒内,你可以:
    • 沿水平方向移动一个单位长度,或者
    • 沿竖直方向移动一个单位长度,或者
    • 跨过对角线移动 sqrt(2) 个单位长度(可以看作在一秒内向水平和竖直方向各移动一个单位长度)。
  • 必须按照数组中出现的顺序来访问这些点。
  • 在访问某个点时,可以经过该点后面出现的点,但经过的那些点不算作有效访问。

示例 1:

输入:points = [[1,1],[3,4],[-1,0]]
输出:7
解释:一条最佳的访问路径是: [1,1] -> [2,2] -> [3,3] -> [3,4] -> [2,3] -> [1,2] -> [0,1] -> [-1,0]   
从 [1,1] 到 [3,4] 需要 3 秒 
从 [3,4] 到 [-1,0] 需要 4 秒
一共需要 7 秒

示例 2:

输入:points = [[3,2],[-2,2]]
输出:5

说明:

  • points.length == n
  • 1 <= n <= 100
  • points[i].length == 2
  • -1000 <= points[i][0], points[i][1] <= 1000

思路

二维平面上有一些点 points,按顺序访问这些点,每一秒可以沿 x 轴、 y 轴 或者 格子的对角线移动,求访问所有点的最小时间。

优先走斜线,直到与下一个坐标点的 横坐标 或者 纵坐标 相等,然后再走直线。两点之间最短时间为 Math.max(dx, dy),即切比雪夫距离。

代码


/**
 * @date 2026-01-12 8:50
 */
public class MinTimeToVisitAllPoints1266 {

    public int minTimeToVisitAllPoints(int[][] points) {
        int res = 0;
        for (int i = 1; i < points.length; i++) {
            int dx = Math.abs(points[i][0] - points[i - 1][0]);
            int dy = Math.abs(points[i][1] - points[i - 1][1]);
            res += Math.max(dx, dy);
        }
        return res;
    }
}

性能

1390.四因数

目标

给你一个整数数组 nums,请你返回该数组中恰有四个因数的这些整数的各因数之和。如果数组中不存在满足题意的整数,则返回 0 。

示例 1:

输入:nums = [21,4,7]
输出:32
解释:
21 有 4 个因数:1, 3, 7, 21
4 有 3 个因数:1, 2, 4
7 有 2 个因数:1, 7
答案仅为 21 的所有因数的和。

示例 2:

输入: nums = [21,21]
输出: 64

示例 3:

输入: nums = [1,2,3,4,5]
输出: 0

说明:

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

思路

有一个整数数组 nums,返回恰好有 4 个因数的元素的这些因数之和,如果不存在,返回 0

首先数组元素至少有两个因数(1 和它本身),只需要再找到两个因数即可。从 2 ~ sqrt(num) 判断能否整除 num,如果可以加上 inum / i,需要特殊处理 i * i = num 的情况,这时因数只能算一个。

代码


/**
 * @date 2026-01-04 9:05
 */
public class SumFourDivisors1390 {

    public int sumFourDivisors(int[] nums) {
        int res = 0;
        for (int num : nums) {
            res += sum(num);
        }
        return res;
    }

    public int sum(int num) {
        int cnt = 2;
        int sum = num + 1;
        for (int i = 2; i * i <= num; i++) {
            if (num % i == 0) {
                if (cnt == 4) {
                    sum = 0;
                    break;
                }
                if (i * i == num) {
                    sum += i;
                    cnt++;
                } else {
                    sum += i + num / i;
                    cnt += 2;
                }

            }
        }
        return cnt == 4 ? sum : 0;
    }

}

性能

66.加一

目标

给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0。

将大整数加 1,并返回结果的数字数组。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
加 1 后得到 123 + 1 = 124。
因此,结果应该是 [1,2,4]。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
加 1 后得到 4321 + 1 = 4322。
因此,结果应该是 [4,3,2,2]。

示例 3:

输入:digits = [9]
输出:[1,0]
解释:输入数组表示数字 9。
加 1 得到了 9 + 1 = 10。
因此,结果应该是 [1,0]。

说明:

  • 1 <= digits.length <= 100
  • 0 <= digits[i] <= 9
  • digits 不包含任何前导 0。

思路

数组 digits 表示一个数字,高位在前低位在后,不含前导零,将该数字加一后返回。

由于存在进位,数组长度可能增加 1 位。仔细想想,第一位是 1 后面全是 0

从后向前遍历,如数字不是 9,将数字加一直接返回,否则,将当前位置零,继续向前进位。如果遍历结束还没有返回,说明数组长度不够,新建数组,将第一位置 1 即可。

代码


/**
 * @date 2024-06-27 0:40
 */
public class PlusOne66 {

    public int[] plusOne(int[] digits) {
        int n = digits.length;
        for (int i = n - 1; i >= 0; i--) {
            int sum = digits[i] + 1;
            if (sum == 10) {
                digits[i] = 0;
            } else {
                digits[i] = sum;
                return digits;
            }
        }
        int[] res = new int[n + 1];
        res[0] = 1;
        return res;
    }

}

性能

840.矩阵中的幻方

目标

3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。

给定一个由整数组成的row x col 的 grid,其中有多少个 3 × 3 的 “幻方” 子矩阵?

注意:虽然幻方只能包含 1 到 9 的数字,但 grid 可以包含最多15的数字。

示例 1:

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

输出: 1

解释:

下面的子矩阵是一个 3 x 3 的幻方:

而这一个不是:

总的来说,在本示例所给定的矩阵中只有一个 3 x 3 的幻方子矩阵。

示例 2:

输入: grid = [[8]]
输出: 0

说明:

  • row == grid.length
  • col == grid[i].length
  • 1 <= row, col <= 10
  • 0 <= grid[i][j] <= 15

思路

判断给定矩阵中幻方的数量,幻方是一个九宫格,元素为 1 ~ 9 且行/列/对角线的元素和相等。

如果数字是 1 ~ 9,所有元素和为 45,幻方和为 sum / 3 = 15。将过中心的四条线相加,刚好等于 sum + 3 * center = 4 * 15 = 60,求得 center = 5

使用 mask 记录出现过的数字,全部出现的二进制表示为 1111111110,即 2^10 - 1 - 1

在保证数字是 1 ~ 9 的前提下,如果判断了前两行满足条件,则无需判断最后一行,同理,如果判断了前两列满足条件,无需判断最后一列。因为总和是 45,剩余的行/列和等于 45 - 30 = 15。在此基础上,对角线也无需判断,由于中间元素是 5,对角和一定是 10

a b c
d e f
g h i

由于行/列和为 15,那么 b + h = d + f = 101 ~ 9 范围内和为 10 的组合只有四种 1 92 83 74 6。剩余四个位置 a c g i,如果对角和不等于 10,有 a + ca + g 等于 10,但是 bd 不可能为 5,矛盾。而如果 a i 和为 10,剩余的 c g 和也为 10

代码


/**
 * @date 2025-12-30 9:10
 */
public class NumMagicSquaresInside840 {

    public int numMagicSquaresInside(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (m < 3 || n < 3) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < m - 1; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (check(grid, i, j)) {
                    res++;
                }
            }
        }
        return res;
    }

    public boolean check(int[][] grid, int i, int j) {
        if (grid[i][j] != 5) {
            return false;
        }
        int[] rowSum = new int[3];
        int[] colSum = new int[3];
        int mask = 0;
        int r = 0;
        for (int row = i - 1; row <= i + 1; row++) {
            int c = 0;
            for (int col = j - 1; col <= j + 1; col++) {
                rowSum[r] += grid[row][col];
                colSum[c++] += grid[row][col];
                mask |= 1 << grid[row][col];
            }
            r++;
        }
        return rowSum[0] == 15 && rowSum[1] == 15 && colSum[0] == 15 && colSum[1] == 15 && mask == (1 << 10) - 2;
    }

}

性能

2147.分隔长廊的方案数

目标

在一个图书馆的长廊里,有一些座位和装饰植物排成一列。给你一个下标从 0 开始,长度为 n 的字符串 corridor ,它包含字母 'S' 和 'P' ,其中每个 'S' 表示一个座位,每个 'P' 表示一株植物。

在下标 0 的左边和下标 n - 1 的右边 已经 分别各放了一个屏风。你还需要额外放置一些屏风。每一个位置 i - 1 和 i 之间(1 <= i <= n - 1),至多能放一个屏风。

请你将走廊用屏风划分为若干段,且每一段内都 恰好有两个座位 ,而每一段内植物的数目没有要求。可能有多种划分方案,如果两个方案中有任何一个屏风的位置不同,那么它们被视为 不同 方案。

请你返回划分走廊的方案数。由于答案可能很大,请你返回它对 10^9 + 7 取余 的结果。如果没有任何方案,请返回 0 。

示例 1:

输入:corridor = "SSPPSPS"
输出:3
解释:总共有 3 种不同分隔走廊的方案。
上图中黑色的竖线表示已经放置好的屏风。
上图每种方案中,每一段都恰好有 两个 座位。

示例 2:

输入:corridor = "PPSPSP"
输出:1
解释:只有 1 种分隔走廊的方案,就是不放置任何屏风。
放置任何的屏风都会导致有一段无法恰好有 2 个座位。

示例 3:

输入:corridor = "S"
输出:0
解释:没有任何方案,因为总是有一段无法恰好有 2 个座位。

说明:

  • n == corridor.length
  • 1 <= n <= 10^5
  • corridor[i] 要么是 'S' ,要么是 'P' 。

思路

有一个仅由 SP 组成的长廊布局示意图 corridor,其中 S 表示座位,P 表示植物, 位置 0 的左边与 n - 1 的右边已经放置了一个屏风。现在需要使用屏风将走廊进行划分,要求屏风之间只能有两个座位,求可行的划分方案数。

首先找到第二个座位的下标,再找到下一个两个座位的第一个座位的下标,它们之间的植物数量 + 1 就是可插入的屏风方案,使用乘法原理计算总方案数。

代码


/**
 * @date 2025-12-22 16:28
 */
public class NumberOfWays2147 {

    public int numberOfWays(String corridor) {
        int mod = 1000000007;
        int[] prev = new int[]{-1, 0};
        int i = 0;
        char[] chars = corridor.toCharArray();
        int n = chars.length;
        while (i < n && chars[i] == 'P') {
            i++;
        }
        prev[1] = i - 1;
        long res = 0L;
        long prevCnt = 1L;
        while (i < n) {
            int[] cur = new int[2];
            int cnt = 0;
            while (i < n && cnt < 2) {
                if (chars[i] == 'S') {
                    cur[cnt++] = i;
                }
                i++;
            }
            if (cnt == 2) {
                prevCnt = (prevCnt * (cur[0] - prev[1])) % mod;
                res = prevCnt;
                prev = cur;
            } else if (cnt == 1) {
                res = 0;
            }
        }
        return (int) res;
    }

}

性能