868.二进制间距

目标

给定一个正整数 n,找到并返回 n 的二进制表示中两个 相邻 1 之间的 最长距离 。如果不存在两个相邻的 1,返回 0 。

如果只有 0 将两个 1 分隔开(可能不存在 0 ),则认为这两个 1 彼此 相邻 。两个 1 之间的距离是它们的二进制表示中位置的绝对差。例如,"1001" 中的两个 1 的距离为 3 。

示例 1:

输入:n = 22
输出:2
解释:22 的二进制是 "10110" 。
在 22 的二进制表示中,有三个 1,组成两对相邻的 1 。
第一对相邻的 1 中,两个 1 之间的距离为 2 。
第二对相邻的 1 中,两个 1 之间的距离为 1 。
答案取两个距离之中最大的,也就是 2 。

示例 2:

输入:n = 8
输出:0
解释:8 的二进制是 "1000" 。
在 8 的二进制表示中没有相邻的两个 1,所以返回 0 。

示例 3:

输入:n = 5
输出:2
解释:5 的二进制是 "101" 。

说明:

  • 1 <= n <= 10^9

思路

判断正整数二进制表示中两个 1 之间 0 的个数,返回其长度 +1,如果不存在返回 0。

根据题意模拟,先去掉右侧的尾零,然后记录 1 前面连续 0 的个数,取最大值即可。

代码


/**
 * @date 2026-02-24 15:03
 */
public class BinaryGap868 {

    public int binaryGap(int n) {
        int res = 0;
        while (n > 0) {
            while (n > 0 && (n & 1) == 0) {
                n >>= 1;
            }
            if (n == 0) {
                break;
            }
            n >>= 1;
            int cnt = 0;
            while (n > 0 && (n & 1) == 0) {
                n >>= 1;
                cnt++;
            }
            if (n > 0) {
                res = Math.max(res, cnt + 1);
            }
        }
        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 中满足条件的子串个数。子串需满足 0 与 1 的个数相等,且子串中所有 0 都是相邻且连续的(由于数量相同,子串中所有的 1 也是相邻且连续的),即 0 与 1 都是成组连续的。

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

}

性能

190.颠倒二进制位

目标

颠倒给定的 32 位有符号整数的二进制位。

示例 1:

输入:n = 43261596
输出:964176192
解释:
整数       二进制
43261596  00000010100101000001111010011100
964176192 00111001011110000010100101000000

示例 2:

输入:n = 2147483644
输出:1073741822
解释:
整数        二进制
2147483644 01111111111111111111111111111100
1073741822 00111111111111111111111111111110

说明:

  • 0 <= n <= 2^31 - 2
  • n 为偶数

进阶: 如果多次调用这个函数,你将如何优化你的算法?

思路

颠倒 32 位有符号整数的二进制表示,返回其表示的数字。

题目限定 n 是正偶数,颠倒之后的最高位为 0,仍是非负数。模拟颠倒的过程,提前记录前导零的个数 lz,最后将结果左移 lz。

代码


/**
 * @date 2024-06-08 22:28
 */
public class ReverseBits190 {

    public int reverseBits_new(int n) {
        int res = 0;
        int lz = Integer.numberOfLeadingZeros(n);
        while (n > 0) {
            int b = n & 1;
            res = (res << 1) | b;
            n >>= 1;
        }
        return res << lz;
    }

}

性能

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

}

性能

799.香槟塔

目标

我们把玻璃杯摆成金字塔的形状,其中 第一层 有 1 个玻璃杯, 第二层 有 2 个,依次类推到第 100 层,每个玻璃杯将盛有香槟。

从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)

例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟,如下图所示。

现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例( i 和 j 都从0开始)。

示例 1:

输入: poured(倾倒香槟总杯数) = 1, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.00000
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻璃杯都是空的。

示例 2:

输入: poured(倾倒香槟总杯数) = 2, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.50000
解释: 我们在顶层(下标是(0,0)倒了两杯香槟后,有一杯量的香槟将从顶层溢出,位于(1,0)的玻璃杯和(1,1)的玻璃杯平分了这一杯香槟,所以每个玻璃杯有一半的香槟。

示例 3:

输入: poured = 100000009, query_row = 33, query_glass = 17
输出: 1.00000

说明:

  • 0 <= poured <= 10^9
  • 0 <= query_glass <= query_row < 100

思路

将玻璃杯摆成金字塔,第一层 1 个杯子,第二层 2 个杯子,依次类推。持续向第一层第一杯倒入 poured 杯香槟,当杯子满了之后,会等流量的流向左右两个玻璃杯,返回 query_row 行(从 0 开始计数),第 query_glass 杯(从 0 开始)中的香槟与容积的比例。

模拟香槟左右的流量即可。第 query_row 行有 query_row + 1 个杯子,初始化流量数组 vol,比如,总共倾倒 poured 杯,如果 vol[0] > 1,多余的流量会从左右两侧流下,vol[1] = (vol[0] - 1)/2,vol[0] = (vol[0] - 1)/2,依次类推,直到查询的杯子。

注意由于没有对流量数组分层,需要从后向前遍历才能保证数据更新的正确性。

代码


/**
 * @date 2026-02-25 15:06
 */
public class ChampagneTower799 {

    public double champagneTower(int poured, int query_row, int query_glass) {
        double[] vol = new double[query_row + 1];
        vol[0] = poured;
        for (int i = 0; i < query_row; i++) {
            for (int j = i; j >= 0; j--) {
                if (vol[j] > 1) {
                    vol[j]--;
                    double half = vol[j] / 2.0;
                    vol[j + 1] += half;
                    vol[j] = half;
                } else {
                    vol[j] = 0.0;
                }
            }
        }
        return Math.min(vol[query_glass], 1.0);
    }

}

性能

3379.转换数组

目标

给你一个整数数组 nums,它表示一个循环数组。请你遵循以下规则创建一个大小 相同 的新数组 result :

对于每个下标 i(其中 0 <= i < nums.length),独立执行以下操作:

  • 如果 nums[i] > 0:从下标 i 开始,向 右 移动 nums[i] 步,在循环数组中落脚的下标对应的值赋给 result[i]。
  • 如果 nums[i] < 0:从下标 i 开始,向 左 移动 abs(nums[i]) 步,在循环数组中落脚的下标对应的值赋给 result[i]。
  • 如果 nums[i] == 0:将 nums[i] 的值赋给 result[i]。

返回新数组 result。

注意:由于 nums 是循环数组,向右移动超过最后一个元素时将回到开头,向左移动超过第一个元素时将回到末尾。

示例 1:

输入: nums = [3,-2,1,1]
输出: [1,1,1,3]
解释:
对于 nums[0] 等于 3,向右移动 3 步到 nums[3],因此 result[0] 为 1。
对于 nums[1] 等于 -2,向左移动 2 步到 nums[3],因此 result[1] 为 1。
对于 nums[2] 等于 1,向右移动 1 步到 nums[3],因此 result[2] 为 1。
对于 nums[3] 等于 1,向右移动 1 步到 nums[0],因此 result[3] 为 3。

示例 2:

输入: nums = [-1,4,-1]
输出: [-1,-1,4]
解释:
对于 nums[0] 等于 -1,向左移动 1 步到 nums[2],因此 result[0] 为 -1。
对于 nums[1] 等于 4,向右移动 4 步到 nums[2],因此 result[1] 为 -1。
对于 nums[2] 等于 -1,向左移动 1 步到 nums[1],因此 result[2] 为 4。

说明:

  • 1 <= nums.length <= 100
  • -100 <= nums[i] <= 100

思路

将循环数组 nums 根据规则转换成一个新数组,新数组下标 i 的值是 nums[offset],当 nums[i] > 0 时,offset 取 i 右边 nums[i] 个位置上的值,当 nums[i] < 0 时,取 i 左边 nums[i] 个位置上的值,如果为 0,取 nums[i]。

代码


/**
 * @date 2026-02-05 8:43
 */
public class ConstructTransformedArray3379 {

    public int[] constructTransformedArray_v1(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = nums[((i + nums[i]) % n + n) % n];
        }
        return res;
    }

}

性能

3510.移除最小数对使数组有序II

目标

给你一个数组 nums,你可以执行以下操作任意次数:

  • 选择 相邻 元素对中 和最小 的一对。如果存在多个这样的对,选择最左边的一个。
  • 用它们的和替换这对元素。

返回将数组变为 非递减 所需的 最小操作次数 。

如果一个数组中每个元素都大于或等于它前一个元素(如果存在的话),则称该数组为非递减。

示例 1:

输入: nums = [5,2,3,1]
输出: 2
解释:
元素对 (3,1) 的和最小,为 4。替换后 nums = [5,2,4]。
元素对 (2,4) 的和为 6。替换后 nums = [5,6]。
数组 nums 在两次操作后变为非递减。

示例 2:

输入: nums = [1,2,2]
输出: 0
解释:
数组 nums 已经是非递减的。

说明:

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

提示:

  • We can perform the simulation using data structures.
  • Maintain an array index and value using a map since we need to find the next and previous ones.
  • Maintain the indices to be removed using a hash set.
  • Maintain the neighbor sums with the smaller indices (set or priority queue).
  • Keep the 3 structures in sync during the removals.

思路

代码

性能

3507.移除最小数对使数组有序I

目标

给你一个数组 nums,你可以执行以下操作任意次数:

  • 选择 相邻 元素对中 和最小 的一对。如果存在多个这样的对,选择最左边的一个。
  • 用它们的和替换这对元素。

返回将数组变为 非递减 所需的 最小操作次数 。

如果一个数组中每个元素都大于或等于它前一个元素(如果存在的话),则称该数组为非递减。

示例 1:

输入: nums = [5,2,3,1]
输出: 2
解释:
元素对 (3,1) 的和最小,为 4。替换后 nums = [5,2,4]。
元素对 (2,4) 的和为 6。替换后 nums = [5,6]。
数组 nums 在两次操作后变为非递减。

示例 2:

输入: nums = [1,2,2]
输出: 0
解释:
数组 nums 已经是非递减的。

说明:

  • 1 <= nums.length <= 50
  • -1000 <= nums[i] <= 1000

思路

有一个数组 nums,每一次操作可以将数组中和最小的相邻元素用它们的和替换掉,求使得数组非递减所需要的最少操作次数。

暴力解法是每次遍历找到和最小的数对 并替换,直到数组非递减。可以使用 next 数组模拟链表来删除元素。

代码


/**
 * @date 2026-01-22 9:02
 */
public class MinimumPairRemoval3507 {

    public int minimumPairRemoval_v1(int[] nums) {
        int res = 0;
        boolean decrease;
        int n = nums.length;
        int[] next = new int[n];
        Arrays.setAll(next, i -> i + 1);
        do {
            decrease = false;
            int index = 0, sum = Integer.MAX_VALUE;
            for (int i = 0; next[i] < n; i = next[i]) {
                if (nums[next[i]] < nums[i]) {
                    decrease = true;
                }
                int s = nums[i] + nums[next[i]];
                if (s < sum) {
                    sum = s;
                    index = i;
                }
            }
            if (decrease) {
                nums[index] = sum;
                next[index] = next[next[index]];
                res++;
            }
        } while (decrease);
        return res;
    }

}

性能

2402.会议室III

目标

给你一个整数 n ,共有编号从 0 到 n - 1 的 n 个会议室。

给你一个二维整数数组 meetings ,其中 meetings[i] = [starti, endi] 表示一场会议将会在 半闭 时间区间 [starti, endi) 举办。所有 starti 的值 互不相同 。

会议将会按以下方式分配给会议室:

  1. 每场会议都会在未占用且编号 最小 的会议室举办。
  2. 如果没有可用的会议室,会议将会延期,直到存在空闲的会议室。延期会议的持续时间和原会议持续时间 相同 。
  3. 当会议室处于未占用状态时,将会优先提供给原 开始 时间更早的会议。

返回举办最多次会议的房间 编号 。如果存在多个房间满足此条件,则返回编号 最小 的房间。

半闭区间 [a, b) 是 a 和 b 之间的区间,包括 a 但 不包括 b 。

示例 1:

输入:n = 2, meetings = [[0,10],[1,5],[2,7],[3,4]]
输出:0
解释:
- 在时间 0 ,两个会议室都未占用,第一场会议在会议室 0 举办。
- 在时间 1 ,只有会议室 1 未占用,第二场会议在会议室 1 举办。
- 在时间 2 ,两个会议室都被占用,第三场会议延期举办。
- 在时间 3 ,两个会议室都被占用,第四场会议延期举办。
- 在时间 5 ,会议室 1 的会议结束。第三场会议在会议室 1 举办,时间周期为 [5,10) 。
- 在时间 10 ,两个会议室的会议都结束。第四场会议在会议室 0 举办,时间周期为 [10,11) 。
会议室 0 和会议室 1 都举办了 2 场会议,所以返回 0 。

示例 2:

输入:n = 3, meetings = [[1,20],[2,10],[3,5],[4,9],[6,8]]
输出:1
解释:
- 在时间 1 ,所有三个会议室都未占用,第一场会议在会议室 0 举办。
- 在时间 2 ,会议室 1 和 2 未占用,第二场会议在会议室 1 举办。
- 在时间 3 ,只有会议室 2 未占用,第三场会议在会议室 2 举办。
- 在时间 4 ,所有三个会议室都被占用,第四场会议延期举办。 
- 在时间 5 ,会议室 2 的会议结束。第四场会议在会议室 2 举办,时间周期为 [5,10) 。
- 在时间 6 ,所有三个会议室都被占用,第五场会议延期举办。 
- 在时间 10 ,会议室 1 和 2 的会议结束。第五场会议在会议室 1 举办,时间周期为 [10,12) 。 
会议室 1 和会议室 2 都举办了 2 场会议,所以返回 1 。

说明:

  • 1 <= n <= 100
  • 1 <= meetings.length <= 10^5
  • meetings[i].length == 2
  • 0 <= starti < endi <= 5 * 10^5
  • starti 的所有值 互不相同

思路

有 n 个会议室,编号为 0 ~ n - 1。有一个二维数组,meetings[i] 表示 [starti, endi) 范围内需要举办一场会议,会优先使用未被占用且编号最小的会议室。如果没有会议室可用,则需要延后举办,如果同一时间有多个会议需要举办,原开始时间最小的优先举办。

这个题目的关键点是,当有多个满足条件的会议室时,选择编号最小的,而不是最早可用的。需要使用两个优先队列,一个追踪会议室的最早空闲时间,另一个则追踪空闲会议室中编号最小的。将会议按照开始时间排序,按顺序遍历,将可选的会议室加入空闲编号堆,如果这个堆非空,从中取一个会议室,并将结束时间(可用时间)放入空闲时间堆中;否则,从空闲时间堆中取一个会议室,将其空闲时间加上会议持续时间后再放入堆中。

代码


/**
 * @date 2025-12-30 14:01
 */
public class MostBooked2402 {

    public int mostBooked(int n, int[][] meetings) {
        int[] cnt = new int[n];
        PriorityQueue<int[]> rooms = new PriorityQueue<>((a, b) -> {
            int compare = a[0] - b[0];
            if (compare != 0) {
                return compare;
            }
            return a[1] - b[1];
        });
        PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        for (int i = 0; i < n; i++) {
            rooms.offer(new int[]{0, i});
        }
        Arrays.sort(meetings, (a, b) -> a[0] - b[0]);
        for (int[] meeting : meetings) {
            while (!rooms.isEmpty() && rooms.peek()[0] <= meeting[0]) {
                q.offer(rooms.poll());
            }
            int[] room;
            if (!q.isEmpty()) {
                room = q.poll();
                rooms.offer(new int[]{meeting[1], room[1]});
            } else {
                room = rooms.poll();
                rooms.offer(new int[]{room[0] + meeting[1] - meeting[0], room[1]});
            }
            cnt[room[1]]++;
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (cnt[i] > cnt[res]) {
                res = i;
            }
        }
        return res;
    }

}

性能

944.删列造序

目标

给你由 n 个小写字母字符串组成的数组 strs,其中每个字符串长度相等。

这些字符串可以每个一行,排成一个网格。例如,strs = ["abc", "bce", "cae"] 可以排列为:

abc
bce
cae

你需要找出并删除 不是按字典序非严格递增排列的 列。在上面的例子(下标从 0 开始)中,列 0('a', 'b', 'c')和列 2('c', 'e', 'e')都是按字典序非严格递增排列的,而列 1('b', 'c', 'a')不是,所以要删除列 1 。

返回你需要删除的列数。

示例 1:

输入:strs = ["cba","daf","ghi"]
输出:1
解释:网格示意如下:
  cba
  daf
  ghi
列 0 和列 2 按升序排列,但列 1 不是,所以只需要删除列 1 。

示例 2:

输入:strs = ["a","b"]
输出:0
解释:网格示意如下:
  a
  b
只有列 0 这一列,且已经按升序排列,所以不用删除任何列。

示例 3:

输入:strs = ["zyx","wvu","tsr"]
输出:3
解释:网格示意如下:
  zyx
  wvu
  tsr
所有 3 列都是非升序排列的,所以都要删除。

说明:

  • n == strs.length
  • 1 <= n <= 100
  • 1 <= strs[i].length <= 1000
  • strs[i] 由小写英文字母组成

思路

有一个元素长度相同的字符串数组 strs,删掉其中不是非严格递增的列,返回删除的列数。

枚举每一列,判断是否非严格递增。

代码


/**
 * @date 2025-12-21 20:11
 */
public class MinDeletionSize944 {

    public int minDeletionSize(String[] strs) {
        int m = strs[0].length();
        int res = 0;
        for (int i = 0; i < m; i++) {
            char prev = 0;
            for (String str : strs) {
                char cur = str.charAt(i);
                if (cur < prev) {
                    res++;
                    break;
                }
                prev = cur;
            }
        }
        return res;
    }
}

性能