1018.可被5整除的二进制前缀

目标

给定一个二进制数组 nums ( 索引从0开始 )。

我们将 xi 定义为其二进制表示形式为子数组 nums[0..i] (从最高有效位到最低有效位)。

  • 例如,如果 nums =[1,0,1] ,那么 x0 = 1, x1 = 2, 和 x2 = 5。

返回布尔值列表 answer,只有当 xi 可以被 5 整除时,答案 answer[i] 为 true,否则为 false。

示例 1:

输入:nums = [0,1,1]
输出:[true,false,false]
解释:
输入数字为 0, 01, 011;也就是十进制中的 0, 1, 3 。只有第一个数可以被 5 整除,因此 answer[0] 为 true 。

示例 2:

输入:nums = [1,1,1]
输出:[false,false,false]

说明:

  • 1 <= nums.length <= 10^5
  • nums[i] 仅为 0 或 1

思路

有一个二进制数组 nums,定义 xi 为二进制表示为 子数组 [0, i] 的数字,判断 x0 ~ x_(n-1) 能否被 5 整除。

模拟存在的问题是左移多次会溢出。假设 num = k * 5 + mod,左移 1 位相当于乘以 22 * num = k * 10 + 2 * mod,能否被 5 整除只需考虑 2 * mod | nums[i]

代码


/**
 * @date 2025-11-24 8:52
 */
public class PrefixesDivBy5_1018 {

    public List<Boolean> prefixesDivBy5_v1(int[] nums) {
        List<Boolean> res = new ArrayList<>();
        int mod = 0;
        for (int num : nums) {
            mod = (mod << 1) % 5 + num;
            res.add(mod % 5 == 0);
        }
        return res;
    }

}

性能

3190.使所有元素都可以被3整除的最少操作数

目标

给你一个整数数组 nums 。一次操作中,你可以将 nums 中的 任意 一个元素增加或者减少 1 。

请你返回将 nums 中所有元素都可以被 3 整除的 最少 操作次数。

示例 1:

输入:nums = [1,2,3,4]
输出:3
解释:
通过以下 3 个操作,数组中的所有元素都可以被 3 整除:
将 1 减少 1 。
将 2 增加 1 。
将 4 减少 1 。

示例 2:

输入:nums = [3,6,9]
输出:0

说明:

1 <= nums.length <= 50
1 <= nums[i] <= 50

思路

统计数组中不能被 3 整除的元素个数。

代码


/**
 * @date 2025-11-22 2:02
 */
public class MinimumOperations3190 {

    public int minimumOperations(int[] nums) {
        int res = 0;
        for (int num : nums) {
            if (num % 3 != 0){
                res++;
            }
        }
        return res;
    }
}

性能

1513.仅含1的子串数

目标

给你一个二进制字符串 s(仅由 '0' 和 '1' 组成的字符串)。

返回所有字符都为 1 的子字符串的数目。

由于答案可能很大,请你将它对 10^9 + 7 取模后返回。

示例 1:

输入:s = "0110111"
输出:9
解释:共有 9 个子字符串仅由 '1' 组成
"1" -> 5 次
"11" -> 3 次
"111" -> 1 次

示例 2:

输入:s = "101"
输出:2
解释:子字符串 "1" 在 s 中共出现 2 次

示例 3:

输入:s = "111111"
输出:21
解释:每个子字符串都仅由 '1' 组成

示例 4:

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

说明:

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

思路

统计全一子串的个数。

根据等差数列求和公式计算即可。

代码


/**
 * @date 2025-11-16 22:17
 */
public class NumSub1513 {

    public int numSub(String s) {
        int mod = 1000000007;
        int n = s.length();
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '0') {
                continue;
            }
            int start = i;
            while (i < n && s.charAt(i) == '1'){
                i++;
            }
            int cnt = i - start;
            res += ((cnt + 1L) * cnt / 2) % mod;

        }
        return res;
    }

}

性能

2654.使数组所有元素变成1的最少操作次数

目标

给你一个下标从 0 开始的 正 整数数组 nums 。你可以对数组执行以下操作 任意 次:

  • 选择一个满足 0 <= i < n - 1 的下标 i ,将 nums[i] 或者 nums[i+1] 两者之一替换成它们的最大公约数。

请你返回使数组 nums 中所有元素都等于 1 的 最少 操作次数。如果无法让数组全部变成 1 ,请你返回 -1 。

两个正整数的最大公约数指的是能整除这两个数的最大正整数。

示例 1:

输入:nums = [2,6,3,4]
输出:4
解释:我们可以执行以下操作:
- 选择下标 i = 2 ,将 nums[2] 替换为 gcd(3,4) = 1 ,得到 nums = [2,6,1,4] 。
- 选择下标 i = 1 ,将 nums[1] 替换为 gcd(6,1) = 1 ,得到 nums = [2,1,1,4] 。
- 选择下标 i = 0 ,将 nums[0] 替换为 gcd(2,1) = 1 ,得到 nums = [1,1,1,4] 。
- 选择下标 i = 2 ,将 nums[3] 替换为 gcd(1,4) = 1 ,得到 nums = [1,1,1,1] 。

示例 2:

输入:nums = [2,10,6,14]
输出:-1
解释:无法将所有元素都变成 1 。

说明:

  • 2 <= nums.length <= 50
  • 1 <= nums[i] <= 10^6

思路

有一个正整数数组 nums,每次操作可以将 nums[i] 或者 nums[i + 1] 替换成它们的最大公约数。求将 nums 所有元素变为 1 的最小操作次数,如果无法完成,返回 -1

显然只要存在相邻的互质元素,就可以将其中的一个设置为 1,然后就可以将左右的元素按顺序替换为 1,总共需要操作 n 次。目标是如何用最小的操作次数使得存在一对相邻元素互质。暴力枚举 子数组 的起点与终点,计算所有元素的最大公约数,记录最小的子数组长度。

代码


/**
 * @date 2025-11-12 9:07
 */
public class MinOperations2654 {

    public int minOperations(int[] nums) {
        int n = nums.length;
        int res = Integer.MAX_VALUE;
        int oneCnt = 0;
        int gcdAll = nums[0];
        for (int num : nums) {
            if (num == 1) {
                oneCnt++;
            }
            gcdAll = gcd(gcdAll, num);
        }
        if (gcdAll > 1) {
            return -1;
        }
        if (oneCnt > 0) {
            return n - oneCnt;
        }
        for (int i = 0; i < n; i++) {
            int g = nums[i];
            for (int j = i; j < n; j++) {
                g = gcd(g, nums[j]);
                if (g == 1) {
                    res = Math.min(res, j - i + 1);
                    break;
                }
            }
        }
        return res - 2 + n;
    }

    public int gcd(int a, int b) {
        if (b == 0) {
            return a;
        }
        return gcd(b, a % b);
    }

}

性能

2169.得到0的操作数

目标

给你两个 非负 整数 num1 和 num2 。

每一步 操作 中,如果 num1 >= num2 ,你必须用 num1 减 num2 ;否则,你必须用 num2 减 num1 。

  • 例如,num1 = 5 且 num2 = 4 ,应该用 num1 减 num2 ,因此,得到 num1 = 1 和 num2 = 4 。然而,如果 num1 = 4且 num2 = 5 ,一步操作后,得到 num1 = 4 和 num2 = 1 。

返回使 num1 = 0 或 num2 = 0 的 操作数 。

示例 1:

输入:num1 = 2, num2 = 3
输出:3
解释:
- 操作 1 :num1 = 2 ,num2 = 3 。由于 num1 < num2 ,num2 减 num1 得到 num1 = 2 ,num2 = 3 - 2 = 1 。
- 操作 2 :num1 = 2 ,num2 = 1 。由于 num1 > num2 ,num1 减 num2 。
- 操作 3 :num1 = 1 ,num2 = 1 。由于 num1 == num2 ,num1 减 num2 。
此时 num1 = 0 ,num2 = 1 。由于 num1 == 0 ,不需要再执行任何操作。
所以总操作数是 3 。

示例 2:

输入:num1 = 10, num2 = 10
输出:1
解释:
- 操作 1 :num1 = 10 ,num2 = 10 。由于 num1 == num2 ,num1 减 num2 得到 num1 = 10 - 10 = 0 。
此时 num1 = 0 ,num2 = 10 。由于 num1 == 0 ,不需要再执行任何操作。
所以总操作数是 1 。

说明:

  • 0 <= num1, num2 <= 10^5

思路

两个非负整数 num1num2,如果 num1 >= num2num1 -= num2,否则 num2 -= num1,直到 num1num2 变为 0

依题意模拟即可。

代码


/**
 * @date 2025-11-09 23:53
 */
public class CountOperations2169 {

    public int countOperations(int num1, int num2) {
        int res = 0;
        while (num1 != 0 && num2 != 0) {
            if (num1 >= num2) {
                num1 -= num2;
            } else {
                num2 -= num1;
            }
            res++;
        }
        return res;
    }
}

性能

3289.数字小镇中的捣蛋鬼

目标

数字小镇 Digitville 中,存在一个数字列表 nums,其中包含从 0 到 n - 1 的整数。每个数字本应 只出现一次,然而,有 两个 顽皮的数字额外多出现了一次,使得列表变得比正常情况下更长。

为了恢复 Digitville 的和平,作为小镇中的名侦探,请你找出这两个顽皮的数字。

返回一个长度为 2 的数组,包含这两个数字(顺序任意)。

示例 1:

输入: nums = [0,1,1,0]
输出: [0,1]
解释:
数字 0 和 1 分别在数组中出现了两次。

示例 2:

输入: nums = [0,3,2,1,3,2]
输出: [2,3]
解释:
数字 2 和 3 分别在数组中出现了两次。

示例 3:

输入: nums = [7,1,5,4,3,4,6,0,9,5,8,2]
输出: [4,5]
解释:
数字 4 和 5 分别在数组中出现了两次。

说明:

  • 2 <= n <= 100
  • nums.length == n + 2
  • 0 <= nums[i] < n
  • 输入保证 nums 中 恰好 包含两个重复的元素。

思路

长度为 n + 2 的数组,其元素由 0 1 2 …… n - 1 n 个数字以及该范围内的两个任意数字组成,找出这两个重复数字。

简单的做法是对每个元素计数,找出出现次数为 2 的元素。但是空间复杂度为 O(n)

进阶的做法是使用位运算得到这两个重复数字的异或值,这两个数字至少有一个 bit 位不同,根据该 bit 位分组,最终得到这两个数字。

代码


/**
 * @date 2025-10-31 9:05
 */
public class GetSneakyNumbers3289 {

    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length;
        int[] cnt = new int[n];
        List<Integer> list = new ArrayList<>();
        for (int num : nums) {
            cnt[num]++;
            if (cnt[num] == 2){
                list.add(num);
            }
        }
        int[] res = new int[2];
        for (int i = 0; i < 2; i++) {
            res[i] = list.get(i);
        }
        return res;
    }
}

性能

3370.仅含置位位的最小整数

目标

给你一个正整数 n。

返回 大于等于 n 且二进制表示仅包含 置位 位的 最小 整数 x 。

置位 位指的是二进制表示中值为 1 的位。

示例 1:

输入: n = 5
输出: 7
解释:
7 的二进制表示是 "111"。

示例 2:

输入: n = 10
输出: 15
解释:
15 的二进制表示是 "1111"。

示例 3:

输入: n = 3
输出: 3
解释:
3 的二进制表示是 "11"。

说明:

  • 1 <= n <= 1000

思路

返回大于等于 n 并且所有 bit 位均为 1 的最小整数。

代码


/**
 * @date 2025-10-29 8:40
 */
public class SmallestNumber3370 {

    public int smallestNumber(int n) {
        int l = 32 - Integer.numberOfLeadingZeros(n);
        return (1 << l) - 1;
    }

}

性能

2125.银行中的激光束数量

目标

银行内部的防盗安全装置已经激活。给你一个下标从 0 开始的二进制字符串数组 bank ,表示银行的平面图,这是一个大小为 m x n 的二维矩阵。 bank[i] 表示第 i 行的设备分布,由若干 '0' 和若干 '1' 组成。'0' 表示单元格是空的,而 '1' 表示单元格有一个安全设备。

对任意两个安全设备而言,如果同时 满足下面两个条件,则二者之间存在 一个 激光束:

  • 两个设备位于两个 不同行 :r1 和 r2 ,其中 r1 < r2 。
  • 满足 r1 < i < r2 的 所有 行 i ,都 没有安全设备 。

激光束是独立的,也就是说,一个激光束既不会干扰另一个激光束,也不会与另一个激光束合并成一束。

返回银行中激光束的总数量。

示例 1:

输入:bank = ["011001","000000","010100","001000"]
输出:8
解释:在下面每组设备对之间,存在一条激光束。总共是 8 条激光束:
 * bank[0][1] -- bank[2][1]
 * bank[0][1] -- bank[2][3]
 * bank[0][2] -- bank[2][1]
 * bank[0][2] -- bank[2][3]
 * bank[0][5] -- bank[2][1]
 * bank[0][5] -- bank[2][3]
 * bank[2][1] -- bank[3][2]
 * bank[2][3] -- bank[3][2]
注意,第 0 行和第 3 行上的设备之间不存在激光束。
这是因为第 2 行存在安全设备,这不满足第 2 个条件。

示例 2:

输入:bank = ["000","111","000"]
输出:0
解释:不存在两个位于不同行的设备

提示:

  • m == bank.length
  • n == bank[i].length
  • 1 <= m, n <= 500
  • bank[i][j] 为 '0' 或 '1'

思路

跳过中间的空行,将相邻的非空行上的装置数量相乘。

代码


/**
 * @date 2025-10-27 10:34
 */
public class NumberOfBeams2125 {

    public int numberOfBeams(String[] bank) {
        int prev = 0, cur = 0;
        int res = 0;
        for (String b : bank) {
            for (char c : b.toCharArray()) {
                if (c == '1') {
                    cur++;
                }
            }
            if (cur == 0) {
                continue;
            }
            res += cur * prev;
            prev = cur;
            cur = 0;
        }
        return res;
    }

}

性能

1716.计算力扣银行的钱

目标

Hercy 想要为购买第一辆车存钱。他 每天 都往力扣银行里存钱。

最开始,他在周一的时候存入 1 块钱。从周二到周日,他每天都比前一天多存入 1 块钱。在接下来每一个周一,他都会比 前一个周一 多存入 1 块钱。

给你 n ,请你返回在第 n 天结束的时候他在力扣银行总共存了多少块钱。

示例 1:

输入:n = 4
输出:10
解释:第 4 天后,总额为 1 + 2 + 3 + 4 = 10 。

示例 2:

输入:n = 10
输出:37
解释:第 10 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4) = 37 。注意到第二个星期一,Hercy 存入 2 块钱。

示例 3:

输入:n = 20
输出:96
解释:第 20 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8) + (3 + 4 + 5 + 6 + 7 + 8) = 96 。

说明:

  • 1 <= n <= 1000

思路

week 0 28 + 7 * 0
week 1 28 + 7 * 1
week 2 28 + 7 * 2
week 3 28 + 7 * 3
...

0 ~ n - 1 周的累加和 n * 28 + (n - 1) * n / 2 * 7

week n 1 + 2 + …… + rem       + n * rem
即     (rem * (rem + 1) / 2)  + n * rem
          当前周的前rem项和

代码


/**
 * @date 2025-10-25 22:11
 */
public class TotalMoney1716 {

    public int totalMoney(int n) {
        int oneWeek = 28;
        int times = n / 7;
        int rem = n % 7;
        return (1 + rem) * rem / 2 + times * oneWeek + Math.max(0, (times - 1) * times / 2) * 7 + times * rem;
    }

}

性能

2598.执行操作后的最大MEX

目标

给你一个下标从 0 开始的整数数组 nums 和一个整数 value 。

在一步操作中,你可以对 nums 中的任一元素加上或减去 value 。

  • 例如,如果 nums = [1,2,3] 且 value = 2 ,你可以选择 nums[0] 减去 value ,得到 nums = [-1,2,3] 。

数组的 MEX (minimum excluded) 是指其中数组中缺失的最小非负整数。

  • 例如,[-1,2,3] 的 MEX 是 0 ,而 [1,0,3] 的 MEX 是 2 。

返回在执行上述操作 任意次 后,nums 的最大 MEX 。

示例 1:

输入:nums = [1,-10,7,13,6,8], value = 5
输出:4
解释:执行下述操作可以得到这一结果:
- nums[1] 加上 value 两次,nums = [1,0,7,13,6,8]
- nums[2] 减去 value 一次,nums = [1,0,2,13,6,8]
- nums[3] 减去 value 两次,nums = [1,0,2,3,6,8]
nums 的 MEX 是 4 。可以证明 4 是可以取到的最大 MEX 。

示例 2:

输入:nums = [1,-10,7,13,6,8], value = 7
输出:2
解释:执行下述操作可以得到这一结果:
- nums[2] 减去 value 一次,nums = [1,-10,0,13,6,8]
nums 的 MEX 是 2 。可以证明 2 是可以取到的最大 MEX 。

说明:

  • 1 <= nums.length, value <= 10^5
  • -10^9 <= nums[i] <= 10^9

思路

有一个数组,可以执行 任意 次操作,每次操作可以将任意元素加上或者减去 value,求数组中缺失的最小非负整数的最大值。

题目要求返回缺失的最小非负整数,可以从 0 开始枚举每一个整数 target,问题是如何判断数组中是否存在元素 nums[i] + k * value == target。两边同时对 value 取余可得 nums[i] ≡ target (mod value)

可以对 nums[i] % value 计数,枚举 target 判断是否还剩有同余元素即可。

代码


/**
 * @date 2025-10-16 8:46
 */
public class FindSmallestInteger2598 {

    public int findSmallestInteger(int[] nums, int value) {
        int[] cnt = new int[value];
        for (int num : nums) {
            int rem = num % value;
            rem = rem >= 0 ? rem : rem + value;
            cnt[rem]++;
        }
        int res = 0;
        while (true) {
            int rem = res % value;
            if (cnt[rem] > 0) {
                cnt[rem]--;
            } else {
                return res;
            }
            res++;
        }
    }

}

性能