2517.礼盒的最大甜蜜度

给你一个正整数数组 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 。

提示:

  • 2 <= k <= price.length <= 105
  • 1 <= price[i] <= 109
class Solution{
    public int maximumTastiness(int[] price, int k) {
        Arrays.sort(price); // 对价格数组进行排序
        int l = 0, r = price[price.length - 1] - price[0]; // 初始化左右边界
        while (l < r) {
            int mid = (l + r + 1) >> 1; // 取中间值,(l + r + 1) >> 1 相当于 (l + r + 1) / 2
            if (check(price, k, mid)) {
                l = mid; // 如果check通过,则mid是可行的,将左边界移至mid
            } else {
                r = mid - 1; // 否则,将右边界移至mid - 1
            }
        }
        return l; // 返回找到的最大可能的最小差值
    }
    private boolean check(int[] price, int k, int x) {
        int cnt = 0, pre = -x; // 初始化计数器cnt和前一个选择的价格pre
        for (int cur : price) {
            if (cur - pre >= x) {
                pre = cur; // 更新前一个选择的价格为当前的cur
                ++cnt; // 满足条件,计数器加1
            }
        }
        return cnt >= k; // 返回是否能够找到至少k个这样的元素
    }
}

  • 排序: 首先对价格数组 price 进行升序排序。排序后,数组中相邻的元素差值最小,而两端的元素差值最大。
  • 二分查找: 我们使用二分查找来找到最大可能的最小差值。l 是最小的可能值(0),r 是最大的可能值(最大价格与最小价格之差)。
  • 中间值: 在每次循环中,我们计算当前的中间值 mid,并检查是否可以用这个 mid 作为两个价格之间的最小差值。
  • 更新边界: 如果 mid 是可行的(即 check 方法返回 true),我们将 l 移动到 mid,否则将 r 移动到 mid - 1
  • 返回结果: 最后,l 将是我们需要的最大可能的最小差值。
  1. 初始化: 初始化计数器 cntpre(前一个选择的价格)为 -x。这里 -x 作为 pre 的初始值确保第一个价格总是能够被选择。
  2. 遍历价格数组: 对每个价格 cur,检查当前价格与上一个选择的价格 pre 之间的差值是否大于或等于 x
  3. 更新: 如果差值大于或等于 x,则更新 pre 为当前价格 cur,并将计数器 cnt 加1。
  4. 返回结果: 最后判断是否找到了至少 k 个满足条件的元素,如果找到了返回 true,否则返回 false

总结

这段代码使用二分查找来高效地找到价格数组中能够满足条件的最大可能的最小差值。通过对价格数组进行排序和合理地设置边界条件,确保了算法的正确性和效率。

pre 变量初始化为 -x 是为了确保在第一次迭代时,价格数组中的第一个元素总是能够被选择。x 代表我们在当前二分查找中测试的最小允许差值。-x 的具体作用如下:

1. 保证第一个元素的选择

  • pre 被初始化为 -x,意味着在遍历价格数组时,第一个元素 curpre 之间的差值总是 cur - (-x) = cur + x。因为 cur 是一个正数,cur + x 一定大于等于 x,所以 if (cur - pre >= x) 这个条件在第一次检查时总是成立。这保证了第一个元素会被选择并且更新 pre 为当前的 cur

2. -x 的含义

  • 这里的 -x 实际上是一个策略性的初始值,它并不代表具体的含义,而是为了确保逻辑上 cur - pre >= x 在第一次判断时总是为 true。这避免了额外的边界处理,也简化了代码。

示例解释:

假设 x = 5,价格数组为 [3, 8, 14, 20],并且我们想要选择 k = 3 个元素。

  • 初始化pre = -5
  • 第一次迭代
    • cur = 3
    • cur - pre = 3 - (-5) = 8(大于等于 5
    • 条件成立,选择 3,更新 pre = 3cnt = 1
  • 第二次迭代
    • cur = 8
    • cur - pre = 8 - 3 = 5(等于 5
    • 条件成立,选择 8,更新 pre = 8cnt = 2
  • 第三次迭代
    • cur = 14
    • cur - pre = 14 - 8 = 6(大于 5
    • 条件成立,选择 14,更新 pre = 14cnt = 3
  • 完成:此时我们已经选择了 3 个元素,因此返回 true

在上述示例中,-x 确保了第一次选择价格时不会被错误地跳过,从而保证了算法的正确性。如果没有使用 -x,而是将 pre 初始化为 0,那么在第一次选择时,cur - pre 可能不会满足条件,从而导致少选择一个元素,逻辑上不正确。

总之,pre = -x 是一种简化代码的技巧,用来确保第一个元素在任何情况下都能被正确选择。

int mid = (l + r + 1) >> 1; 这一行代码的作用是计算二分查找中的中间值 (mid),并且使用了位移操作符 >> 来实现整除。我们来逐步解析这段代码。

二分查找的中间值计算

在二分查找中,我们需要不断地计算当前范围的中间值,以决定下一步要检查哪一半。通常,中间值的计算方式如下:

 

java

复制代码

int mid = (l + r) / 2;

其中,l 是当前区间的左边界,r 是右边界。mid 就是 lr 的中间位置。

为什么使用 (l + r + 1) >> 1;

  • 避免向下取整偏差: 如果使用 (l + r) / 2 来计算 mid,当 lr 之间只有两个数时,mid 会偏向较小的那个数。例如,当 l = 4r = 5 时,(l + r) / 2 会得到 4。而 (l + r + 1) >> 1 会得到 5,即它偏向右边一点点。

  • >> 1 的作用: >> 1 是右移位操作符,相当于将一个整数除以 2。因此,(l + r + 1) >> 1 实际上等价于 (l + r + 1) / 2,只不过使用位移操作在某些情况下可能更快(尽管现代编译器已经会优化这种简单除法操作)。

具体解析 (l + r + 1) >> 1

  1. l + r + 1: 在计算中加 1 是为了在偶数和奇数的情况下,向上取整。例如,如果 l = 4r = 5l + r + 1 = 10。当除以 2 时得到 5,这使得 mid 更加偏向右边,避免始终选择中间偏左的元素。

  2. >> 1: 右移一位,即等于除以 2,结果是得到 mid 的值。

例子

假设 l = 4r = 5:

  • mid = (l + r + 1) >> 1 = (4 + 5 + 1) >> 1 = 10 >> 1 = 5mid 被设置为 5

假设 l = 2r = 3:

  • mid = (l + r + 1) >> 1 = (2 + 3 + 1) >> 1 = 6 >> 1 = 3mid 被设置为 3

这保证了当 lr 接近时,mid 总是更偏向右边,这对某些二分查找问题有利,特别是当 lr 的取值范围较小时。

prev 这里的设定要求是p取第一个值为真

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值