3258.统计满足K约束的子字符串数量I

目标

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

如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:

  • 字符串中 0 的数量最多为 k。
  • 字符串中 1 的数量最多为 k。

返回一个整数,表示 s 的所有满足 k 约束 的 子字符串 的数量。

示例 1:

输入:s = "10101", k = 1
输出:12
解释:
s 的所有子字符串中,除了 "1010"、"10101" 和 "0101" 外,其余子字符串都满足 k 约束。

示例 2:

输入:s = "1010101", k = 2
输出:25
解释:
s 的所有子字符串中,除了长度大于 5 的子字符串外,其余子字符串都满足 k 约束。

示例 3:

输入:s = "11111", k = 1
输出:15
解释:
s 的所有子字符串都满足 k 约束。

说明:

  • 1 <= s.length <= 50
  • 1 <= k <= s.length
  • s[i] 是 '0' 或 '1'。

思路

定义二进制字符串满足 k 约束的条件是字符串中 0 的个数 或者 1 的个数都不超过 k。求给定字符串满足 k 约束的子字符串数量。子字符串 是字符串中 连续非空 字符序列。

对于非定长滑动窗口,我们可以枚举左端点,扩展右端点;也可以枚举右端点,收缩左端点。对于这个题,我们需要累加的是子数组个数,即以枚举的元素为起点或终点的子数组个数,实际就是区间内元素个数。如果枚举左端点,右端点指向的是不满足条件的点,需要复杂的判断,非常容易出错。因此选择枚举右端点,收缩左端点。

代码


/**
 * @date 2024-11-12 9:19
 */
public class CountKConstraintSubstrings3258 {

    public int countKConstraintSubstrings(String s, int k) {
        int n = s.length();
        char[] chars = s.toCharArray();
        int[] cnt = new int[2];
        int res = 0, l = 0;
        for (int i = 0; i < n; i++) {
            cnt[chars[i] - '0']++;
            while (l <= i && cnt[0] > k && cnt[1] > k) {
                cnt[chars[l++] - '0']--;
            }
            res += i - l + 1;
        }
        return res;
    }

}

性能

3242.设计相邻元素求和服务

目标

给你一个 n x n 的二维数组 grid,它包含范围 [0, n^2 - 1] 内的不重复元素。

实现 neighborSum 类:

  • neighborSum(int [][]grid) 初始化对象。
  • int adjacentSum(int value) 返回在 grid 中与 value 相邻的元素之和,相邻指的是与 value 在上、左、右或下的元素。
  • int diagonalSum(int value) 返回在 grid 中与 value 对角线相邻的元素之和,对角线相邻指的是与 value 在左上、右上、左下或右下的元素。

示例 1:

输入:
["neighborSum", "adjacentSum", "adjacentSum", "diagonalSum", "diagonalSum"]
[[[[0, 1, 2], [3, 4, 5], [6, 7, 8]]], [1], [4], [4], [8]]
输出: [null, 6, 16, 16, 4]
解释:
1 的相邻元素是 0、2 和 4。
4 的相邻元素是 1、3、5 和 7。
4 的对角线相邻元素是 0、2、6 和 8。
8 的对角线相邻元素是 4。

示例 2:

输入:
["neighborSum", "adjacentSum", "diagonalSum"]
[[[[1, 2, 0, 3], [4, 7, 15, 6], [8, 9, 10, 11], [12, 13, 14, 5]]], [15], [9]]
输出: [null, 23, 45]
解释:
15 的相邻元素是 0、10、7 和 6。
9 的对角线相邻元素是 4、12、14 和 15。

说明:

  • 3 <= n == grid.length == grid[0].length <= 10
  • 0 <= grid[i][j] <= n^2 - 1
  • 所有 grid[i][j] 值均不重复。
  • adjacentSum 和 diagonalSum 中的 value 均在范围 [0, n^2 - 1] 内。
  • 最多会调用 adjacentSum 和 diagonalSum 总共 2 * n^2 次。

思路

有一个 n x n 的二维矩阵,其元素值的范围是 0 ~ n^2 - 1,且元素值互不相同。给定一个元素值,求其上下左右的元素和以及对角线元素和(左上、右上、左下和右下)。

首先要找到这个给定的元素,再根据其下标计算元素和,对于大量重复地查询,可以记录每个元素值对应的下标。

每次定位的时间复杂度为 100,由于存在缓存,最多搜索 100 次,总复杂度为 10^4

我们也可以在初始化的时候直接建立 元素值 与 坐标的映射,时间复杂度降为 O(n^2) 即 100

还可以进一步优化,由于数据值是不可变的,可以提前将 相邻元素和 都计算好,查的时候无需重复计算。此外,由于元素各不相同,可以直接将元素值映射为下标,将元素值与坐标的映射改为元素值与相邻元素和的映射。

代码


/**
 * @date 2024-11-09 11:21
 */
public class NeighborSum3242 {

    class NeighborSum {

        int[][] adjacent = new int[][]{
                new int[]{-1, 0},
                new int[]{1, 0},
                new int[]{0, -1},
                new int[]{0, 1},
        };
        int[][] diagonal = new int[][]{
                new int[]{-1, -1},
                new int[]{-1, 1},
                new int[]{1, -1},
                new int[]{1, 1},
        };
        int[] adjacentSum;
        int[] diagonalSum;

        public NeighborSum(int[][] grid) {
            int index = 0;
            int n = grid.length;
            int length = n * n;
            adjacentSum = new int[length];
            diagonalSum = new int[length];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    int value = grid[i][j];
                    adjacentSum[value] = sum(grid, i, j, adjacent);
                    diagonalSum[value] = sum(grid, i, j, diagonal);
                }
            }
        }

        public int adjacentSum(int value) {
            return adjacentSum[value];
        }

        public int diagonalSum(int value) {
            return diagonalSum[value];
        }

        private int sum(int[][] grid, int i, int j, int[][] directions){
            int res = 0;
            for (int[] direction : directions) {
                int row = direction[0] + i;
                int col = direction[1] + j;
                if (row < 0 || col < 0 || row >= grid.length || col >= grid.length) {
                    continue;
                }
                res += grid[row][col];
            }
            return res;
        }
    }

}

性能

3222.求出硬币游戏的赢家

目标

给你两个 正 整数 x 和 y ,分别表示价值为 75 和 10 的硬币的数目。

Alice 和 Bob 正在玩一个游戏。每一轮中,Alice 先进行操作,Bob 后操作。每次操作中,玩家需要拿出价值 总和 为 115 的硬币。如果一名玩家无法执行此操作,那么这名玩家 输掉 游戏。

两名玩家都采取 最优 策略,请你返回游戏的赢家。

示例 1:

输入:x = 2, y = 7
输出:"Alice"
解释:
游戏一次操作后结束:
Alice 拿走 1 枚价值为 75 的硬币和 4 枚价值为 10 的硬币。

示例 2:

输入:x = 4, y = 11
输出:"Bob"
解释:
游戏 2 次操作后结束:
Alice 拿走 1 枚价值为 75 的硬币和 4 枚价值为 10 的硬币。
Bob 拿走 1 枚价值为 75 的硬币和 4 枚价值为 10 的硬币。

说明:

  • 1 <= x, y <= 100

思路

有价值 75 的硬币 x 个,价值 10 的硬币 y 个。AliceBob 轮流进行操作,每一次操作需要从中取出价值 115 的硬币,如果无法执行此操作则玩家输掉游戏。如果 Alice 先进行操作,最后的赢家是谁?

每次操作需要取 1x4y,可以直接模拟,直到 x < 1y < 4

这道题也有数学解法,输赢实际上取决于可以进行的操作次数。总共可以进行的操作次数是 Math.min(x,y/4),如果为偶数 Bob 胜,奇数 Alice 胜。

代码


/**
 * @date 2024-11-05 9:14
 */
public class LosingPlayer3222 {

    public String losingPlayer_v1(int x, int y) {
        return Math.min(x, y / 4) % 2 == 0 ? "Bob" : "Alice";
    }

    public String losingPlayer(int x, int y) {
        int cnt = 0;
        while (x >= 1 && y >= 4) {
            x--;
            y -= 4;
            cnt++;
        }
        return cnt % 2 == 0 ? "Bob" : "Alice";
    }

}

性能

3226.使两个整数相等的位更改次数

目标

给你两个正整数 n 和 k。

你可以选择 n 的 二进制表示 中任意一个值为 1 的位,并将其改为 0。

返回使得 n 等于 k 所需要的更改次数。如果无法实现,返回 -1。

示例 1:

输入: n = 13, k = 4
输出: 2
解释:
最初,n 和 k 的二进制表示分别为 n = (1101)2 和 k = (0100)2,
我们可以改变 n 的第一位和第四位。结果整数为 n = (0100)2 = k。

示例 2:

输入: n = 21, k = 21
输出: 0
解释:
n 和 k 已经相等,因此不需要更改。

示例 3:

输入: n = 14, k = 13
输出: -1
解释:
无法使 n 等于 k。

说明:

  • 1 <= n, k <= 10^6

思路

有两个整数 nk,每次操作可以将 n 的二进制位从 1 改为 0,求使 n 等于 k 所需的操作次数,如果无法实现返回 -1。

注意到这两个整数最大为 10^6,而 2^20 = 1048576,因此最高 bit 位不会超过 20

依次比较这两个数的第 19 位到第 0 位:

  • 如果相等则跳过
  • 如果 n 的 bit 位为 0 则返回 -1,因为这时 k 对应位置的 bit 位为 1,无法通过操作使之相等
  • 否则累加操作次数

官网题解提供了另一种解法,将每个 bit 为 1 的位置视为一个元素,如果可以通过操作将 n 变为 k, 说明 k 的 bit 1 的集合是 n 的 bit 1 集合的子集。因此 n & k = k,这时我们需要统计 nk bit 位不同的个数,直接使用异或运算统计 bit 1 的个数即可。

return (n & k) == k ? Integer.bitCount(n ^ k) : -1;

代码


/**
 * @date 2024-11-02 5:00
 */
public class MinChanges3226 {

    public int minChanges(int n, int k) {
        int res = 0;
        for (int i = 19; i >= 0; i--) {
            int bitn = n & 1 << i;
            int bitk = k & 1 << i;
            if (bitn == bitk) {
                continue;
            }
            if (bitn == 0) {
                return -1;
            } else {
                res++;
            }
        }
        return res;
    }
}

性能

3216.交换后字典序最小的字符串

目标

给你一个仅由数字组成的字符串 s,在最多交换一次 相邻 且具有相同 奇偶性 的数字后,返回可以得到的 字典序 最小的字符串。

如果两个数字都是奇数或都是偶数,则它们具有相同的奇偶性。例如,5 和 9、2 和 4 奇偶性相同,而 6 和 9 奇偶性不同。

示例 1:

输入: s = "45320"
输出: "43520"
解释:
s[1] == '5' 和 s[2] == '3' 都具有相同的奇偶性,交换它们可以得到字典序最小的字符串。

示例 2:

输入: s = "001"
输出: "001"
解释:
无需进行交换,因为 s 已经是字典序最小的。

说明:

  • 2 <= s.length <= 100
  • s 仅由数字组成。

思路

有一个仅由数字组成的字符串 s,最多可以执行一次操作,交换字符串中相邻并且奇偶性相同的元素,返回可以得到的字典序最小的字符串。

实际上就是交换第一个满足 i < j && s.charAt(i) - '0' > s.charAt(j) - '0 && (s.charAt(i) - '0') % 2 == (s.charAt(j) - '0') % 2 的相邻字符。

注意到 0 的 ASCII码为 48,数字的 ASCII码的奇偶性与数字本身的奇偶性相同,并且数字大的对应的 ASCII 码也大,不影响判断,可以不用减字符 '0'。

代码


/**
 * @date 2024-10-30 0:11
 */
public class GetSmallestString3216 {

    public String getSmallestString(String s) {
        char[] chars = s.toCharArray();
        int n = s.length();
        int prev = s.charAt(0);
        for (int i = 1; i < n; i++) {
            int cur = s.charAt(i);
            if (prev > cur && prev % 2 == cur % 2) {
                chars[i] = s.charAt(i - 1);
                chars[i - 1] = s.charAt(i);
                break;
            }
            prev = cur;
        }
        return new String(chars);
    }

}

性能

3184.构成整天的下标对数目I

目标

给你一个整数数组 hours,表示以 小时 为单位的时间,返回一个整数,表示满足 i < j 且 hours[i] + hours[j] 构成 整天 的下标对 i, j 的数目。

整天 定义为时间持续时间是 24 小时的 整数倍 。

例如,1 天是 24 小时,2 天是 48 小时,3 天是 72 小时,以此类推。

示例 1:

输入: hours = [12,12,30,24,24]
输出: 2
解释:
构成整天的下标对分别是 (0, 1) 和 (3, 4)。

示例 2:

输入: hours = [72,48,24,3]
输出: 3
解释:
构成整天的下标对分别是 (0, 1)、(0, 2) 和 (1, 2)。

说明:

  • 1 <= hours.length <= 100
  • 1 <= hours[i] <= 10^9

思路

有一个整数数组 hours,返回 hours[i] + hours[j] % 24 == 0i < j 的下标对的个数。

n 个元素中枚举两个元素可能组合的时间复杂度为 O(C(n,2)),即 O(n^2),数据规模不大,直接依题意枚举判断并计数即可。

代码


/**
 * @date 2024-10-22 8:46
 */
public class CountCompleteDayPairs3184 {

    public int countCompleteDayPairs(int[] hours) {
        int n = hours.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if ((hours[i] + hours[j]) % 24 == 0) {
                    res++;
                }
            }
        }
        return res;
    }

}

性能

908.最小差值I

目标

给你一个整数数组 nums,和一个整数 k 。

在一个操作中,您可以选择 0 <= i < nums.length 的任何索引 i 。将 nums[i] 改为 nums[i] + x ,其中 x 是一个范围为 [-k, k] 的任意整数。对于每个索引 i ,最多 只能 应用 一次 此操作。

nums 的 分数 是 nums 中最大和最小元素的差值。

在对 nums 中的每个索引最多应用一次上述操作后,返回 nums 的最低 分数 。

示例 1:

输入:nums = [1], k = 0
输出:0
解释:分数是 max(nums) - min(nums) = 1 - 1 = 0。

示例 2:

输入:nums = [0,10], k = 2
输出:6
解释:将 nums 改为 [2,8]。分数是 max(nums) - min(nums) = 8 - 2 = 6。

示例 3:

输入:nums = [1,3,6], k = 3
输出:0
解释:将 nums 改为 [4,4,4]。分数是 max(nums) - min(nums) = 4 - 4 = 0。

说明:

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

思路

有一个整数数组,可以对其中的每一个元素执行操作 加上 [-k, k] 范围内的任意整数。针对同一元素只能执行一次操作,求最大值与最小值差值的最小值。

先找出最大值 max 与最小值 min,返回 min + k >= max - k ? 0 : max - min - 2*k

代码


/**
 * @date 2024-10-20 16:36
 */
public class SmallestRangeI908 {

    public int smallestRangeI_v1(int[] nums, int k) {
        int min = Integer.MAX_VALUE;
        int max = 0;
        for (int num : nums) {
            if (num < min) {
                min = num;
            }
            if (num > max) {
                max = num;
            }
        }
        return Math.max(max - min - 2 * k, 0);
    }

}

性能

3194.最小元素和最大元素的最小平均值

目标

你有一个初始为空的浮点数数组 averages。另给你一个包含 n 个整数的数组 nums,其中 n 为偶数。

你需要重复以下步骤 n / 2 次:

  • 从 nums 中移除 最小 的元素 minElement 和 最大 的元素 maxElement。
  • 将 (minElement + maxElement) / 2 加入到 averages 中。

返回 averages 中的 最小 元素。

示例 1:

输入: nums = [7,8,3,4,15,13,4,1]
输出: 5.5
解释:
步骤 nums               averages
0   [7,8,3,4,15,13,4,1] []
1   [7,8,3,4,13,4]      [8]
2   [7,8,4,4]           [8,8]
3   [7,4]               [8,8,6]
4   []                  [8,8,6,5.5]
返回 averages 中最小的元素,即 5.5。

示例 2:

输入: nums = [1,9,8,3,10,5]
输出: 5.5
解释:
步骤 nums           averages
0   [1,9,8,3,10,5]  []
1   [9,8,3,5]       [5.5]
2   [8,5]           [5.5,6]
3   []              [5.5,6,6.5]

示例 3:

输入: nums = [1,2,3,7,8,9]
输出: 5.0
解释:
步骤 nums           averages
0   [1,2,3,7,8,9]   []
1   [2,3,7,8]       [5]
2   [3,7]           [5,5]
3   []              [5,5,5]

说明:

  • 2 <= n == nums.length <= 50
  • n 为偶数。
  • 1 <= nums[i] <= 50

思路

有一个数组 nums,元素个数为偶数,取其最大值与最小值的平均数加入 averages 浮点数数组,并将其从 nums 中删除。求 averages 数组的最小值。

先将数组 nums 排序,然后取前后两个元素计算平均数,取最小值即可。

代码


/**
 * @date 2024-10-16 8:49
 */
public class MinimumAverage3194 {

    public double minimumAverage(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int end = n / 2;
        n = n - 1;
        double res = Double.MAX_VALUE;
        for (int i = 0; i < end; i++) {
            res = Math.min(res, (nums[i] + nums[n - i]) / 2.0);
        }
        return res;
    }
}

性能

3200.三角形的最大高度

目标

给你两个整数 red 和 blue,分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形,满足第 1 行有 1 个球,第 2 行有 2 个球,第 3 行有 3 个球,依此类推。

每一行的球必须是 相同 颜色,且相邻行的颜色必须 不同。

返回可以实现的三角形的 最大 高度。

示例 1:

输入: red = 2, blue = 4
输出: 3
解释:
上图显示了唯一可能的排列方式。

示例 2:

输入: red = 2, blue = 1
输出: 2
解释:
上图显示了唯一可能的排列方式。

示例 3:

输入: red = 1, blue = 1
输出: 1

示例 4:

输入: red = 10, blue = 1
输出: 2
解释:
上图显示了唯一可能的排列方式。

说明:

  • 1 <= red, blue <= 100

思路

有红球 red 个,蓝球 blue 个,使用这两种球组成三角形,要求每一行只能由同一种颜色的球组成,且相邻行球的颜色不同,问三角形的最大高度。

三角形第 i 行球的个数为 i,奇数行的总个数为 1 + 3 + 5 + …… 偶数行的总个数为 2 + 4 + 6 + ……,根据等差数列求和公式,奇数行所需球的总个数为 oddRowCnt^2,偶数行所需球的总个数为 evenRowCnt^2 + evenRowCnt

假设 red 放在奇数行,代入可得 redOddRowCnt = sqrt(red),如果放在偶数行,则 redEvenRowCnt = (sqrt(1 + 4 * red) - 1) / 2。同理可求出 blueOddRowCnt blueEvenRowCnt

接下来分两种情况讨论:

  1. 红色球放第一行:如果 |redOddRowCnt - blueEvenRowCnt| > 1,取 Math.min(redOddRowCnt, blueEvenRowCnt) * 2 + 1,否则取 Math.min(redOddRowCnt, blueEvenRowCnt) * 2
  2. 蓝色球放第一行:如果 |blueOddRowCnt - redEvenRowCnt| > 1,取 Math.min(blueOddRowCnt, redEvenRowCnt) * 2 + 1,否则取 Math.min(blueOddRowCnt, redEvenRowCnt) * 2

取上面两种情况的最大值即可。

代码


/**
 * @date 2024-10-15 9:25
 */
public class MaxHeightOfTriangle3200 {

    public int maxHeightOfTriangle(int red, int blue) {
        int redOddRowCnt = (int) Math.sqrt(red);
        int redEvenRowCnt = (int) ((Math.sqrt(1 + 4 * red) - 1) / 2);
        int blueOddRowCnt = (int) Math.sqrt(blue);
        int blueEvenRowCnt = (int) ((Math.sqrt(1 + 4 * blue) - 1) / 2);
        int r, b;
        if (redOddRowCnt - blueEvenRowCnt >= 1) {
            r = 2 * blueEvenRowCnt + 1;
        } else {
            r = 2 * redOddRowCnt;
        }
        if (blueOddRowCnt - redEvenRowCnt >= 1) {
            b = 2 * redEvenRowCnt + 1;
        } else {
            b = 2 * blueOddRowCnt;
        }
        return Math.max(r, b);
    }

}

性能

3158.求出出现两次数字的XOR值

目标

给你一个数组 nums ,数组中的数字 要么 出现一次,要么 出现两次。

请你返回数组中所有出现两次数字的按位 XOR 值,如果没有数字出现过两次,返回 0 。

示例 1:

输入:nums = [1,2,1,3]
输出:1
解释:
nums 中唯一出现过两次的数字是 1 。

示例 2:

输入:nums = [1,2,3]
输出:0
解释:
nums 中没有数字出现两次。

示例 3:

输入:nums = [1,2,2,1]
输出:3
解释:
数字 1 和 2 出现过两次。1 XOR 2 == 3 。

说明:

  • 1 <= nums.length <= 50
  • 1 <= nums[i] <= 50
  • nums 中每个数字要么出现过一次,要么出现过两次。

思路

有一个数组 nums,其中的元素要么出现一次,要么出现两次。求出现两次的元素的异或值,如果没有则返回 0。

由于 nums[i] <= 50,可以使用数组记录元素是否出现过,如果它第二次出现则计算异或值。

网友题解则使用了 long 型变量作为 bitmap 标识是否已经出现过。空间复杂度降为 O(1)

代码


/**
 * @date 2024-10-12 8:46
 */
public class DuplicateNumbersXOR3158 {

    public int duplicateNumbersXOR(int[] nums) {
        // nums[i] <= 50
        boolean[] seen = new boolean[51];
        int res = 0;
        for (int num : nums) {
            if (seen[num]) {
                res ^= num;
            } else {
                seen[num] = true;
            }
        }
        return res;
    }
}

性能