2.技巧※(0x3f:从周赛中学算法 2022下)

news2024/11/8 19:35:37

来自0x3f【从周赛中学算法 - 2022 年周赛题目总结(下篇)】:https://leetcode.cn/circle/discuss/WR1MJP/

技巧指一些比较套路的算法,包括双指针、滑动窗口、二分(主要指二分答案)、前缀和、差分、前后缀分解、位运算、二进制枚举、贡献法等。这些技巧相对容易掌握,想在周赛上分的同学可以优先学习这些内容。
顺带一提,我一般把窗口大小不固定的叫做双指针,窗口大小固定的叫做滑动窗口。
注:常见于周赛第二题(约占 18%)和第三题(约占 27%)。

题目难度备注
2483. 商店的最少代价1495前后缀分解
2461. 长度为 K 子数组中的最大和1553非常标准的滑动窗口题
2425. 所有数对的异或和1622贡献法
2420. 找到所有好下标1695前后缀分解
2397. 被列覆盖的最多行数1719二进制枚举
2401. 最长优雅子数组1750位运算与双指针结合的好题(暴力也可以过)
2381. 字母移位 II1793差分
2516. 每种字符至少取 K 个1947绝大多数双指针题目都是算子数组/子串,而这题是算的前缀+后缀,如此变形后要怎么做呢?
2439. 最小化数组中的最大值1954二分答案之最小化最大值(看到最小和最大就要往二分答案上想)
2517. 礼盒的最大甜蜜度2020二分答案
2444. 统计定界子数组的数目2093较为复杂的多指针题目,你能写出简洁的代码吗?

文章目录

  • 零神-从周赛中学算法(技巧)
    • [2483. 商店的最少代价](https://leetcode.cn/problems/minimum-penalty-for-a-shop/)
    • [2461. 长度为 K 子数组中的最大和](https://leetcode.cn/problems/maximum-sum-of-distinct-subarrays-with-length-k/)
    • 🔺[2425. 所有数对的异或和](https://leetcode.cn/problems/bitwise-xor-of-all-pairings/)
    • [2420. 找到所有好下标](https://leetcode.cn/problems/find-all-good-indices/)
    • 🔺[2397. 被列覆盖的最多行数](https://leetcode.cn/problems/maximum-rows-covered-by-columns/)
    • [2401. 最长优雅子数组](https://leetcode.cn/problems/longest-nice-subarray/)
    • 🔺[2381. 字母移位 II](https://leetcode.cn/problems/shifting-letters-ii/)
    • 🔺[2516. 每种字符至少取 K 个](https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right/)
    • [2439. 最小化数组中的最大值](https://leetcode.cn/problems/minimize-maximum-of-array/)
    • 🔺[2517. 礼盒的最大甜蜜度](https://leetcode.cn/problems/maximum-tastiness-of-candy-basket/)
    • 🔺[2444. 统计定界子数组的数目](https://leetcode.cn/problems/count-subarrays-with-fixed-bounds/)

零神-从周赛中学算法(技巧)

2483. 商店的最少代价

难度中等11

给你一个顾客访问商店的日志,用一个下标从 0 开始且只包含字符 'N''Y' 的字符串 customers 表示:

  • 如果第 i 个字符是 'Y' ,它表示第 i 小时有顾客到达。
  • 如果第 i 个字符是 'N' ,它表示第 i 小时没有顾客到达。

如果商店在第 j 小时关门(0 <= j <= n),代价按如下方式计算:

  • 在开门期间,如果某一个小时没有顾客到达,代价增加 1
  • 在关门期间,如果某一个小时有顾客到达,代价增加 1

请你返回在确保代价 最小 的前提下,商店的 最早 关门时间。

注意,商店在第 j 小时关门表示在第 j 小时以及之后商店处于关门状态。

示例 1:

输入:customers = "YYNY"
输出:2
解释:
- 第 0 小时关门,总共 1+1+0+1 = 3 代价。
- 第 1 小时关门,总共 0+1+0+1 = 2 代价。
- 第 2 小时关门,总共 0+0+0+1 = 1 代价。
- 第 3 小时关门,总共 0+0+1+1 = 2 代价。
- 第 4 小时关门,总共 0+0+1+0 = 1 代价。
在第 2 或第 4 小时关门代价都最小。由于第 2 小时更早,所以最优关门时间是 2 。

示例 2:

输入:customers = "NNNNN"
输出:0
解释:最优关门时间是 0 ,因为自始至终没有顾客到达。

示例 3:

输入:customers = "YYYY"
输出:4
解释:最优关门时间是 4 ,因为每一小时均有顾客到达。

提示:

  • 1 <= customers.length <= 105
  • customers 只包含字符 'Y''N'

前缀和

class Solution {
    public int bestClosingTime(String customers) {
        int n = customers.length();
        int[] s = new int[n+1];
        // [0, 1, 2, 3, 4]
        for(int i = 0; i < n; i++){
            s[i+1] = s[i] + (customers.charAt(i) == 'Y' ? 1 : 0); 
        }
        int res = 0;
        int min = Integer.MAX_VALUE;
        for(int i = 0; i <= n; i++){
            int cur = 0;
            cur += i - s[i]; // i小时前没有顾客来 + 1
            cur += s[n] - s[i]; // i小时后有顾客来 + 1
            if(cur < min){
                min = cur;
                res = i;
            }
        }
        return res;
    }
}

2461. 长度为 K 子数组中的最大和

难度中等32

给你一个整数数组 nums 和一个整数 k 。请你从 nums 中满足下述条件的全部子数组中找出最大子数组和:

  • 子数组的长度是 k,且
  • 子数组中的所有元素 各不相同 。

返回满足题面要求的最大子数组和。如果不存在子数组满足这些条件,返回 0

子数组 是数组中一段连续非空的元素序列。

示例 1:

输入:nums = [1,5,4,2,9,9,9], k = 3
输出:15
解释:nums 中长度为 3 的子数组是:
- [1,5,4] 满足全部条件,和为 10 。
- [5,4,2] 满足全部条件,和为 11 。
- [4,2,9] 满足全部条件,和为 15 。
- [2,9,9] 不满足全部条件,因为元素 9 出现重复。
- [9,9,9] 不满足全部条件,因为元素 9 出现重复。
因为 15 是满足全部条件的所有子数组中的最大子数组和,所以返回 15 。

示例 2:

输入:nums = [4,4,4], k = 3
输出:0
解释:nums 中长度为 3 的子数组是:
- [4,4,4] 不满足全部条件,因为元素 4 出现重复。
因为不存在满足全部条件的子数组,所以返回 0 。

提示:

  • 1 <= k <= nums.length <= 105
  • 1 <= nums[i] <= 105

滑动窗口:当窗口值到达K的时候,统计答案,让左边界收缩

class Solution {
    public long maximumSubarraySum(int[] nums, int k) {
        long res = 0l, tmp = 0l;
        int left = 0, right = 0;
        int[] cnt = new int[100007];
        while(right < nums.length){
            cnt[nums[right]]++;
            tmp += nums[right];
            while(cnt[nums[right]] > 1){
                tmp -= nums[left];
                cnt[nums[left]]--;
                left++;
            }
            if(right - left + 1 == k){
                res = Math.max(res, tmp);
                tmp -= nums[left];
                cnt[nums[left]]--;
                left++;
            }
            right++;
        }
        return res;
    }
}

法二:用Map存储元素出现次数,当窗口值恒定为K,如果Map大小=k,则说明出现了k个不同的数字,统计答案

🔺2425. 所有数对的异或和

难度中等13

给你两个下标从 0 开始的数组 nums1nums2 ,两个数组都只包含非负整数。请你求出另外一个数组 nums3 ,包含 nums1nums2所有数对 的异或和(nums1 中每个整数都跟 nums2 中每个整数 恰好 匹配一次)。

请你返回 nums3 中所有整数的 异或和

示例 1:

输入:nums1 = [2,1,3], nums2 = [10,2,5,0]
输出:13
解释:
一个可能的 nums3 数组是 [8,0,7,2,11,3,4,1,9,1,6,3] 。
所有这些数字的异或和是 13 ,所以我们返回 13 。

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:0
解释:
所有数对异或和的结果分别为 nums1[0] ^ nums2[0] ,nums1[0] ^ nums2[1] ,nums1[1] ^ nums2[0] 和 nums1[1] ^ nums2[1] 。
所以,一个可能的 nums3 数组是 [2,5,1,6] 。
2 ^ 5 ^ 1 ^ 6 = 0 ,所以我们返回 0 。

提示:

  • 1 <= nums1.length, nums2.length <= 105
  • 0 <= nums1[i], nums2[j] <= 109

写几个找一下规律就好了:

对于 [a, b][c, d]
res = (a^c)^(a^d)^(b^c)^(b^d)
      = (a^a)^(b^b)^(c^c)^(d^d)
      = 0

对于 [a, b][c, d, e]
res = (a^c)^(a^d)^(a^e)^(b^c)^(b^d)^(b^e)
      = (a^a^a)^(b^b^b)^(c^c)^(d^d)^(e^e)
      = a^b

对于 [a,b,c][d, e]
res = (a^d)^(a^e)^(b^d)^(b^e)^(c^d)^(c^e)
      = (a^a)^(b^b)^(c^c)^(d^d^d)^(e^e^e)
      = d^e

可以发现:

  • 如果 nums1 数组长度是偶数,nums2 中元素最终都会消为 0
  • 如果 nums1 数组长度是奇数,nums2 会剩下 其每个元素的异或和

分别对 num1 和 num2 应用以上发现:

  • 对于 [a, b] 和 [c, d],nums1 偶 nums2 偶,结果 = 0 ^ 0 = 0
  • 对于 [a, b] 和 [c, d, e],nums1 偶 nums2 奇,结果 = 0 ^ nums1 每个元素的异或和 = a^b
  • 对于 [a, b, c] 和 [d, e],nums1 奇 nums2 偶,结果 = nums2 每个元素的异或和 ^ 0 = d^e
class Solution {
    // 由于答案是一大堆数字的异或和,根据贡献法的思想,
    // 我们可以讨论每个数字在这一大堆数字中出现了多少次,对答案的贡献是多少。
    // 对于 nums1[i],由于要与 nums2[i] 每个元素异或一次,因此nums1[i]出现了len(nums2[i])次
    // 由于一个元素异或他自己=0,因此如果m是偶数,nums1[i]对答案贡献是0,否则就是nums1[i]
    public int xorAllNums(int[] nums1, int[] nums2) {
        int res = 0;
        if(nums1.length % 2 != 0)
            for(int n : nums2) res ^= n;
        if(nums2.length % 2 != 0)
            for(int n : nums1) res ^= n;
        return res;
    }
}

2420. 找到所有好下标

难度中等33收藏分享切换为英文接收动态反馈

给你一个大小为 n 下标从 0 开始的整数数组 nums 和一个正整数 k

对于 k <= i < n - k 之间的一个下标 i ,如果它满足以下条件,我们就称它为一个 下标:

  • 下标 i 之前k 个元素是 非递增的
  • 下标 i 之后k 个元素是 非递减的

升序 返回所有好下标。

示例 1:

输入:nums = [2,1,1,1,3,4,1], k = 2
输出:[2,3]
解释:数组中有两个好下标:
- 下标 2 。子数组 [2,1] 是非递增的,子数组 [1,3] 是非递减的。
- 下标 3 。子数组 [1,1] 是非递增的,子数组 [3,4] 是非递减的。
注意,下标 4 不是好下标,因为 [4,1] 不是非递减的。

示例 2:

输入:nums = [2,1,1,2], k = 2
输出:[]
解释:数组中没有好下标。

提示:

  • n == nums.length
  • 3 <= n <= 105
  • 1 <= nums[i] <= 106
  • 1 <= k <= n / 2

前缀和 + 后缀和预处理

class Solution {
    public List<Integer> goodIndices(int[] nums, int k) {
        int n = nums.length;
        int[] suf = new int[n+1]; // suf[i]记录i位置后缀有多少非递减的
        int[] pre = new int[n+1]; // pre[i]记录i位置前缀有多少非递增的
        pre[0] = 1;
        for(int i = 1; i < n; i++)
            pre[i] = nums[i] <= nums[i-1] ? pre[i-1] + 1 : 1;
        suf[n-1] = 1;
        for(int i = n-2; i >= 0; i--)
            suf[i] = nums[i] <= nums[i+1] ? suf[i+1] + 1 : 1;
        List<Integer> res = new ArrayList<>();
        for(int i = k; i < n-k; i++){
            // 枚举i位置,注意不包括i
            if(pre[i-1] >= k && suf[i+1] >= k) res.add(i);
        }
        return res;
    }
}

🔺2397. 被列覆盖的最多行数

难度中等35

给你一个下标从 0 开始的 m x n 二进制矩阵 mat 和一个整数 cols ,表示你需要选出的列数。

如果一行中,所有的 1 都被你选中的列所覆盖,那么我们称这一行 被覆盖 了。

请你返回在选择 cols 列的情况下,被覆盖 的行数 最大 为多少。

示例 1:

img

输入:mat = [[0,0,0],[1,0,1],[0,1,1],[0,0,1]], cols = 2
输出:3
解释:
如上图所示,覆盖 3 行的一种可行办法是选择第 0 和第 2 列。
可以看出,不存在大于 3 行被覆盖的方案,所以我们返回 3 。

示例 2:

输入:mat = [[1],[0]], cols = 1
输出:2
解释:
选择唯一的一列,两行都被覆盖了,原因是整个矩阵都被覆盖了。
所以我们返回 2 。

提示:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 12
  • mat[i][j] 要么是 0 要么是 1
  • 1 <= cols <= n
class Solution {
    public int maximumRows(int[][] matrix, int numSelect) {
        int m = matrix.length, n = matrix[0].length;
        int state = 1 << n; //state = 2^n-1
        int[] mask = new int[m];

        //把每一行看成一个数字
        //预处理一个行的掩码mask,记录行1出现的位置
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(matrix[i][j] == 1){
                    mask[i] ^= (1 << j);
                }
            }
        }
        int res = 0;
        // 一开始还想dfs枚举出所有大小为k的列组合,实际上只用遍历1<<n,然后查看bit数是否等于k就行了
        for(int i = 0; i < state; i++){//遍历{空集-{0,1,2,..,n-1}所有的集合}
            if(Integer.bitCount(i) == numSelect){
                //只有选中cols列时才判断覆盖了多少行
                int count = 0;
                for(int x : mask){
                    // 逐行进行判断,统计个数
                    // i=101 ; ~i=010 ; x = 101 ; (~i)&x = 000;
                    if(((~i)&x) == 0){
                        count++;
                    }
                }
                res = Math.max(res, count);
            }
        }
        return res;
    }
}

2401. 最长优雅子数组

难度中等40

给你一个由 整数组成的数组 nums

如果 nums 的子数组中位于 不同 位置的每对元素按位 **与(AND)**运算的结果等于 0 ,则称该子数组为 优雅 子数组。

返回 最长 的优雅子数组的长度。

子数组 是数组中的一个 连续 部分。

**注意:**长度为 1 的子数组始终视作优雅子数组。

示例 1:

输入:nums = [1,3,8,48,10]
输出:3
解释:最长的优雅子数组是 [3,8,48] 。子数组满足题目条件:
- 3 AND 8 = 0
- 3 AND 48 = 0
- 8 AND 48 = 0
可以证明不存在更长的优雅子数组,所以返回 3 。

示例 2:

输入:nums = [3,1,5,11,13]
输出:1
解释:最长的优雅子数组长度为 1 ,任何长度为 1 的子数组都满足题目条件。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109

核心思想:双指针 + 位运算

class Solution {
    public int longestNiceSubarray(int[] nums) {
        int n = nums.length;
        int left = 0, right = 0;
        int res = 0;
        int cnt = 0;
        while(right < n){
            while((cnt & nums[right]) != 0){
                cnt ^= nums[left];
                left++;
            }
            cnt ^= nums[right];
            right++;
            res = Math.max(res, (right-left));
        }
        return res;
    }
}

🔺2381. 字母移位 II

难度中等16

给你一个小写英文字母组成的字符串 s 和一个二维整数数组 shifts ,其中 shifts[i] = [starti, endi, directioni] 。对于每个 i ,将 s 中从下标 starti 到下标 endi (两者都包含)所有字符都进行移位运算,如果 directioni = 1 将字符向后移位,如果 directioni = 0 将字符向前移位。

将一个字符 向后 移位的意思是将这个字符用字母表中 下一个 字母替换(字母表视为环绕的,所以 'z' 变成 'a')。类似的,将一个字符 向前 移位的意思是将这个字符用字母表中 前一个 字母替换(字母表是环绕的,所以 'a' 变成 'z' )。

请你返回对 s 进行所有移位操作以后得到的最终字符串。

示例 1:

输入:s = "abc", shifts = [[0,1,0],[1,2,1],[0,2,1]]
输出:"ace"
解释:首先,将下标从 0 到 1 的字母向前移位,得到 s = "zac" 。
然后,将下标从 1 到 2 的字母向后移位,得到 s = "zbd" 。
最后,将下标从 0 到 2 的字符向后移位,得到 s = "ace" 。

示例 2:

输入:s = "dztz", shifts = [[0,0,0],[1,1,1]]
输出:"catz"
解释:首先,将下标从 0 到 0 的字母向前移位,得到 s = "cztz" 。
最后,将下标从 1 到 1 的字符向后移位,得到 s = "catz" 。

提示:

  • 1 <= s.length, shifts.length <= 5 * 104
  • shifts[i].length == 3
  • 0 <= starti <= endi < s.length
  • 0 <= directioni <= 1
  • s 只包含小写英文字母。

题解:差分数组

那么现在有一个任务:对数组a区间[left,right]每个元素加一个常数c。这时可以利用原数组就是差分数组的前缀和这个特性,来解决这个问题。

对于b数组,只需要执行b[left] += c, b[right+1] −= c

如何得到更新后的数组元素值? 只需要累加即可:第i位值sum :sum += b[i]

class Solution {
    // 使用差分数组计算每个位置总共左移或者右移的位数
    // 与现有的加和后取模得到最终的字符
    public String shiftingLetters(String s, int[][] shifts) {
        int n = s.length();
        int[] diff = new int[n+1];
        for(int i = 0; i < shifts.length; i++){
            int from = shifts[i][0], to = shifts[i][1], d = shifts[i][2];
            int add = d == 1 ? 1 : -1;
            diff[from] += add;
            diff[to+1] -= add;
        }
        int sum = 0;
        char[] c = new char[n];
        for(int i = 0; i < n; i++){
            sum += diff[i];
            c[i] = (char)(((((s.charAt(i) - 'a' + sum) % 26) + 26) % 26) + 'a'); 
        }
        return new String(c);
    }   
}

🔺2516. 每种字符至少取 K 个

难度中等25

给你一个由字符 'a''b''c' 组成的字符串 s 和一个非负整数 k 。每分钟,你可以选择取走 s 最左侧 还是 最右侧 的那个字符。

你必须取走每种字符 至少 k 个,返回需要的 最少 分钟数;如果无法取到,则返回 -1

示例 1:

输入:s = "aabaaaacaabc", k = 2
输出:8
解释:
从 s 的左侧取三个字符,现在共取到两个字符 'a' 、一个字符 'b' 。
从 s 的右侧取五个字符,现在共取到四个字符 'a' 、两个字符 'b' 和两个字符 'c' 。
共需要 3 + 5 = 8 分钟。
可以证明需要的最少分钟数是 8 。

示例 2:

输入:s = "a", k = 1
输出:-1
解释:无法取到一个字符 'b' 或者 'c',所以返回 -1 。

提示:

  • 1 <= s.length <= 105
  • s 仅由字母 'a''b''c' 组成
  • 0 <= k <= s.length
class Solution {
    // aabaaaacaabc k = 2
    // 前缀      后缀           答案
    //           baaaacaabc     10
    // a         baaaacaabc     11
    // aa        baaaacaabc     12
    // aab       caabc          8  // 找到了一个更短的前缀+后缀
    // aaba      caabc          9
    // aabaa     caabc          10
    // ...       ...            ...
    //  随着i的变大,j也会单调变大,因此可以从小到大枚举j,一边维护i的最大值
    public int takeCharacters(String s, int k) {
        int n = s.length();
        if(n < 3 * k) return -1;
        int[] arrs = new int[3];
        char[] cs = s.toCharArray();
        // 判断s中abc各有多少个
        for(char c : cs)
            arrs[c-'a']++;
        // abc减去k后,还剩多少个(即不需要取走的字符数)
        int leavea = arrs[0] - k;
        int leaveb = arrs[1] - k;
        int leavec = arrs[2] - k;
        // 如果剩余的都为0,说明s整个都要取走 return n
        if(leavea == 0 && leaveb == 0 && leavec == 0) return n;
        // 如果有一个剩余<0,说明该字符不足k个
        if(leavea < 0 || leaveb < 0 || leavec < 0) return -1;
        // 滑动窗口 为s中的一段,满足arrs[i] > leave i,求得滑动窗口最大值,len - windows 为所求最小值
        // right,n为后缀;0-left为前缀,从小到大枚举后缀的同时维护前缀
        int left = 0, right = 0, max = 0;
        arrs = new int[3];
        while(right < n){
            arrs[cs[right] - 'a']++;
            // 当[left,right]时,再[0,left-1]和[right+1,n]前后缀中取不到abc任意k个,就需要缩短窗口值
            while(arrs[0] > leavea || arrs[1] > leaveb || arrs[2] > leavec){
                arrs[cs[left++] - 'a']--;
            }
            max = Math.max(max, right - left + 1);
            right++;
        }
        return n - max;
    }
}

2439. 最小化数组中的最大值

难度中等40

给你一个下标从 0 开始的数组 nums ,它含有 n 个非负整数。

每一步操作中,你需要:

  • 选择一个满足 1 <= i < n 的整数 i ,且 nums[i] > 0
  • nums[i] 减 1 。
  • nums[i - 1] 加 1 。

你可以对数组执行 任意 次上述操作,请你返回可以得到的 nums 数组中 最大值 最小 为多少。

示例 1:

输入:nums = [3,7,1,6]
输出:5
解释:
一串最优操作是:
1. 选择 i = 1 ,nums 变为 [4,6,1,6] 。
2. 选择 i = 3 ,nums 变为 [4,6,2,5] 。
3. 选择 i = 1 ,nums 变为 [5,5,2,5] 。
nums 中最大值为 5 。无法得到比 5 更小的最大值。
所以我们返回 5 。

示例 2:

输入:nums = [10,1]
输出:10
解释:
最优解是不改动 nums ,10 是最大值,所以返回 10 。

提示:

  • n == nums.length
  • 2 <= n <= 105
  • 0 <= nums[i] <= 109

二分答案:

class Solution {
    public int minimizeArrayValue(int[] nums) {
        int n = nums.length;
        int max = 0;
        for(int i = 0; i < n; i++) max = Math.max(nums[i], max);
        int left = 0, right = max;
        while(left < right){
            int mid = (left + right) / 2;
            if(!check(nums, mid)) left = mid + 1;
            else right = mid;
        }
        return right;
    }
    // nums数组中能不能最大值top
    public boolean check(int[] nums, int top){
        int n = nums.length;
        int right = n-1;
        double diff = 0;
        while(right > 0){ // 从后往前模拟
            // 如果当前数字 + 后一位额外添加的数字 > top,则需要nums[right] + diff - top补给前一位
            if(nums[right] + diff > top) {
                diff = (double)(nums[right] + diff - top);
            }else{
                diff = 0;
            }
            right--;
        }
        // 最后负担都再nums[0]上,看nums[0]能否满足要求(能不能最大值top)
        return nums[0] + diff <= top;
    }
}

🔺2517. 礼盒的最大甜蜜度

难度中等31

给你一个正整数数组 price ,其中 price[i] 表示第 i 类糖果的价格,另给你一个正整数 k

商店组合 k不同 糖果打包成礼盒出售。礼盒的 甜蜜度 是礼盒中任意两种糖果 价格 绝对差的最小值。

返回礼盒的 最大 甜蜜度*。*

示例 1:

输入:price = [13,5,1,8,21,2], k = 3
输出:8
解释:选出价格分别为 [13,5,21] 的三类糖果。
礼盒的甜蜜度为 min(|13 - 5|, |13 - 21|, |5 - 21|) = min(8, 8, 16) = 8 。
可以证明能够取得的最大甜蜜度就是 8 。

示例 2:

输入:price = [1,3,1], k = 2
输出:2
解释:选出价格分别为 [1,3] 的两类糖果。 
礼盒的甜蜜度为 min(|1 - 3|) = min(2) = 2 。
可以证明能够取得的最大甜蜜度就是 2 。

示例 3:

输入:price = [7,7,7,7], k = 2
输出:0
解释:从现有的糖果中任选两类糖果,甜蜜度都会是 0 。

提示:

  • 1 <= price.length <= 105
  • 1 <= price[i] <= 109
  • 2 <= k <= price.length
class Solution {
    // 「能力检测二分」
    // 由于随着甜蜜度的增大,能选择的糖果数量变小,有单调性,所以可以用二分答案来做。
    // 疑问:那个check里面的大于等于,如果你所有的元素都是大于那个d,
    //                      而没有等于的话,他取min的时候不也是不符合的嘛?
    // 解答:二分结束的位置等号一定是成立的。
    //          因为从「能选至少 k 个」到「选不到 k 个」之间,一定会经过「能选恰好k个」。
    public int maximumTastiness(int[] price, int k) {
        Arrays.sort(price);
        int n = price.length;
        int left = 0, right = price[n-1] - price[0];
        while(left < right){ // (left, right]
            // 这里左开右闭模型(当mid满足条件仍然是要mid的)
            // 因此mid在取值时要向上取整即(left + right + 1)/2
            int mid = (left + right + 1) >> 1;
            if(check(price, k, mid)) left = mid;
            else right = mid - 1;
        }
        return left;
    }
    
    // 贪心: 一旦与上一个选中糖果的差值大于等于gap, 就取这个糖果
    public boolean check(int[] price, int k, int gap){
        int cnt = 1; // 最小的糖果一定会取
        int pre = price[0];
        for(int i = 1; i < price.length; i++){
            // 如果两个下标的甜蜜读
            if(price[i] - pre >= gap){
                cnt++;
                pre = price[i];
            }
            if(cnt >= k)// 取到了k个,提前结束
                return true;
        }
        return cnt >= k;
    }
}

🔺2444. 统计定界子数组的数目

难度困难74

给你一个整数数组 nums 和两个整数 minK 以及 maxK

nums 的定界子数组是满足下述条件的一个子数组:

  • 子数组中的 最小值 等于 minK
  • 子数组中的 最大值 等于 maxK

返回定界子数组的数目。

子数组是数组中的一个连续部分。

示例 1:

输入:nums = [1,3,5,2,7,5], minK = 1, maxK = 5
输出:2
解释:定界子数组是 [1,3,5] 和 [1,3,5,2] 。

示例 2:

输入:nums = [1,1,1,1], minK = 1, maxK = 1
输出:10
解释:nums 的每个子数组都是一个定界子数组。共有 10 个子数组。

提示:

  • 2 <= nums.length <= 105
  • 1 <= nums[i], minK, maxK <= 106
class Solution {
    // 把在[minK,maxK]之外的数字当成分割点,只需要考虑在两个相邻分割点之间的子数组
    // ==> 双指针 枚举右端点,看左端点能落在哪些范围内
    // 1. mini,最小值minK出现的位置 ; maxi,最大值mink出现的位置
    // 2. i0, 上一个不在[minK,maxK]区间范围内的边界
    // 取值 (Math.min(mini, maxi) - i0)
    public long countSubarrays(int[] nums, int minK, int maxK) {
        long res = 0l;
        int n = nums.length, mini = -1, maxi = -1, i0 = -1;
        for(int i = 0; i < n; i++){
            int x = nums[i];
            if(x == minK) mini = i;
            if(x == maxK) maxi = i;
            if(x < minK || x > maxK) i0 = i; // 子数组不能包含 nums[i0]
            res += Math.max(Math.min(mini, maxi) - i0, 0);
        }
        return res;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript由浅到深(下篇)

目录 七、TypeScript泛型编程 泛型实现类型参数化: 泛型接口和泛型类的使用: 泛型约束: 映射类型: TypeScript条件类型&#xff08;Conditional Types&#xff09;: 在条件类型中推断&#xff08;inter&#xff09;: 分发条件类型&#xff08;Distributive Conditional …

【Java基础】day15

day15 一、为什么需要使用多线程&#xff1f; 1、资源利用率提升&#xff0c;程序处理效率提高 2、软件运行效率提升 3、使用线程可以把占据时间长的程序中的任务放到后台去处理 4、充分利用 CPU 资源&#xff0c;多核 CPU 的情况下会更高效 二、Spring Boot 的启动流程&…

搭建个人网站没有公网IP地址可以吗?

搭建网站不一定需要公网IP地址&#xff0c;甚至都不需要云服务器或虚拟主机。可以先在本地个人电脑中搭建一个网站&#xff1b;然后网站需要为公网上的其他访客提供访问&#xff1b;所以&#xff0c;需要内网穿透&#xff0c;映射公网域名进行访问。但是完全没必要&#xff0c;…

一文了解Gralde

&#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公司实习&#x1f…

python黑马程序员(单例模式工厂模式)笔记

一、单例模式 1、设计模式就是一种编程套路 使用特定的套路得到特定的效果 2、什么时单例设计模式 单例模式就是对一个类&#xff0c;只获取其唯一的类实例对象&#xff0c;持续复用它 节省内存 节省创建对象的开销 非单例模式效果&#xff1a; # 演示单例模式的效果 # 非…

chatgpt相关关键字

听了一堂chatgpt的课程&#xff0c;真假参半&#xff0c;但积累了一些关键词。不知道这些关键字会在什么时候起到作用&#xff0c;先记录下来作为灵感积累 1 自然进化的过程&#xff0c;是人选择工具&#xff0c;也是工具选择人 2 Copliot-自动编程&#xff0c;感觉适用于独立新…

安卓 Windows 通过ts链接获取m3u8视频地址进行视频下载

目录 环境&#xff1a; 解决思路及过程&#xff1a; .TS——> .m3u8 1.利用安卓视频缓存机制合成视频 1.1 找到鲨鱼浏览器目录 1.2 进入Android/data/com.zhijianzhuoyue.sharkbrowser/cashe 缓存目录 1.3 显示隐藏文件 1.4 进入可以看到两个随机生成的视频文件夹&…

傅盛“追风”GPT,猎户星空春天来了?

GPT的横空出世&#xff0c;让冷清已久的商用服务机器人市场&#xff0c;又有了“新故事”。 从技术底层逻辑而言&#xff0c;服务机器人受到这类新技术的影响会更为明显。因为抛开硬件&#xff0c;服务机器人的内核其实就是AI&#xff0c;GPT大模型的出现显然成了现阶段该产业进…

coreldraw2023安装教程及新功能讲解

coreldraw是一款非常好用的设计软件&#xff0c;功能非常强大&#xff0c;它可应用于商标设计、标志制作、模型绘制、插图描画、排版及分色输出等领域&#xff0c;因此受到了不少设计师的青睐&#xff0c; CorelDRAW2023新功能有哪些&#xff1f;CorelDRAW2023最新版本更新怎么…

SpringBoot-核心技术篇

技术掌握导图 六个大标题↓ 配置文件web开发数据访问单元测试指标指控原理解析 配置文件 1.文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML是 “YAML Aint Markup Language”&#xff08;YAML不是一种标记语言&#xff09;的递归缩写。在…

JumpServer部署与应用实践

JumpServer部署与介绍 文章目录JumpServer部署与介绍前言堡垒机功能特点主要主件一、在线安装二、环境访问三、堡垒机的应用&#xff08;重点&#xff09;3.1用户与用户组的创建3.2资产管理3.3账号管理3.4权限管理四、应用实践前言 Jumpserver 是一款使用 Python, Django 开发…

Su+ELK实现网络监测(1)——Suricata安装与配置

Suricata安装配置文档一、环境准备1. 基础环境安装2. 安装基础组件二、Luajit部署1. LuaJIT的安装2. 需要更新动态库三、suricata部署1. 安装相关依赖2. 下载、编译并安装suricata3. 执行4. 安装其他组件5. 修改配置文件6. 启动测试7. 安装suricata-update8. 更新规则集9. 启动…

Java多线程:线程组

线程组 可以把线程归属到某一个线程组中&#xff0c;线程组中可以有线程对象&#xff0c;也可以有线程组&#xff0c;组中还可以有线程&#xff0c;这样的组织结构有点类似于树的形式&#xff0c;如图所示&#xff1a; 线程组的作用是&#xff1a;可以批量管理线程或线程组对象…

从4k到42k,软件测试工程师的涨薪史,给我看哭了

清明节一过&#xff0c;盲猜大家已经无心上班&#xff0c;在数着日子准备过五一&#xff0c;但一想到银行卡里的余额……瞬间心情就不美丽了。 最近&#xff0c;2023年高校毕业生就业调查显示&#xff0c;本科毕业月平均起薪为5825元。调查一出&#xff0c;便有很多同学表示自己…

命令行打jar包

命令行打jar包前言jar --helpcvfmMANIFEST.MF压入class文件前言 每一个java开发者运行第一个java项目时都知道项目通常被打成jar包&#xff0c;这些jar包是由IDE工具打的&#xff0c;知其然不知其所以然。 jar --help 用help命令打印jar支持的所有参数&#xff0c;不做过多解…

【GPT开发】人人都能用ChatGPT4.0做Avatar虚拟人直播

0 前言 最近朋友圈以及身边很多朋友都在研究GPT开发&#xff0c;做了各种各样的小工具小Demo&#xff0c;AI工具用起来是真的香&#xff01;在他们的影响下&#xff0c;我也继续捣鼓GPT Demo&#xff0c;希望更多的开发者加入一起多多交流。 上一篇结合即时通 IM SDK捣鼓了一个…

2023mathorcup B题

已完成完整B题C题的模型代码&#xff0c;具体看文末 摘要 城市轨道交通系统作为城市出行的重要组成部分&#xff0c;对于缓解城市交通压力、提高出行效率具有重要意义。优化列车时刻表是提高运营效率、降低企业运营成本、提高乘客满意度的关键。本研究针对城市轨道交通列车时…

SAR ADC系列24:冗余设计

目录 冗余&#xff08;Redundancy&#xff09; 比较器出错&#xff1a;原因 比较器出错&#xff1a;后果 引入冗余&#xff1a;纠错 冗余&#xff1a;容错量 冗余&#xff1a;非二进制CDAC --sub二进制 冗余&#xff1a;提速 另一种冗余设计方法&#xff1a; 下面的关…

tab栏切换

效果&#xff1a; 当鼠标通过上边tab栏时&#xff0c;对应的元素变亮并切换到相应的菜单 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge…

英语学渣如何成功逆袭?聊聊我获得海外工作的真实经历

今天我想和大家分享一下作为一个英语学渣&#xff0c;我是如何成功找到一份海外工作的。希望能够给和我有相似经历的小伙伴们一些启示。以下是兴哥一个女粉丝成功逆袭的经历&#xff0c;大家可以一起旁观一下她的自白。 首先&#xff0c;让我简单介绍一下我的情况。我现在在芬…