[ 二分 + 排序 ]LCP 18. 早餐组合

206 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

每日刷题 2021.04.13

题目

  • 小扣在秋日市集选择了一家早餐摊位,一维整型数组 staple 中记录了每种主食的价格,一维整型数组 drinks 中记录了每种饮料的价格。小扣的计划选择一份主食和一款饮料,且花费不超过x元。请返回小扣共有多少种购买方案。
  • 注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1

示例

  • 示例1
输入:staple = [10,20,5], drinks = [5,5,2], x = 15
输出:6

解释:小扣有 6 种购买方案,所选主食与所选饮料在数组中对应的下标分别是:
第 1 种方案:staple[0] + drinks[0] = 10 + 5 = 15;
第 2 种方案:staple[0] + drinks[1] = 10 + 5 = 15;
第 3 种方案:staple[0] + drinks[2] = 10 + 2 = 12;
第 4 种方案:staple[2] + drinks[0] = 5 + 5 = 10;
第 5 种方案:staple[2] + drinks[1] = 5 + 5 = 10;
第 6 种方案:staple[2] + drinks[2] = 5 + 2 = 7
  • 示例2
输入:staple = [2,1,1], drinks = [8,9,5,1], x = 9
输出:8

解释:小扣有 8 种购买方案,所选主食与所选饮料在数组中对应的下标分别是:
第 1 种方案:staple[0] + drinks[2] = 2 + 5 = 7;
第 2 种方案:staple[0] + drinks[3] = 2 + 1 = 3;
第 3 种方案:staple[1] + drinks[0] = 1 + 8 = 9;
第 4 种方案:staple[1] + drinks[2] = 1 + 5 = 6;
第 5 种方案:staple[1] + drinks[3] = 1 + 1 = 2;
第 6 种方案:staple[2] + drinks[0] = 1 + 8 = 9;
第 7 种方案:staple[2] + drinks[2] = 1 + 5 = 6;
第 8 种方案:staple[2] + drinks[3] = 1 + 1 = 2

提示

  • 1 <= staple.length <= 10^5
  • 1 <= drinks.length <= 10^5
  • 1 <= staple[i],drinks[i] <= 10^5
  • 1 <= x <= 2*10^5

解题思路

  • 读题意:选择一份主食和一款饮料,且花费不超过x元,请问有多少种方案?
  • 那么最直观的方法:就是每次选择一个主食,然后去匹配每一个饮料,如果总和小于等于x,那么就计作一种方案。时间复杂度o(n * m),其中n表示staple数组的长度,m表示drink数组的长度。
    • 因为对于每一个主食你都需要去匹配合适的饮料,那么主食有n个,一共就需要匹配n * m
  • 查看题目所给的数据范围:10 ^ 5 * 10 ^ 5就会超时,因此需要考虑优化写法。

优化写法

  • 之前做过的类似的题目:两数之和采购方案两数之和IV
  • 针对每一种主食,可以匹配的不超过x的饮料,不需要将所有的饮料都循环遍历一遍与之匹配。可以优化:找到当前的主食可以匹配的最贵的饮料🥤,那么比当前的饮料便宜的饮料都可以匹配这个主食。
  • 那么当前这个主食可以匹配的方案数 = 当前主食可以匹配到的第一个最贵的饮料
  • 整体的总的方案数 = n个不同主食的方案数之和

实现方法

  • 那么如何找到:当前主食可以匹配到的第一个最贵的饮料
  • 可以将饮料数组drink排序后,非递减的顺序排序
  • 针对有序的数组,就可以使用二分查找,此时就可以将时间复杂度从o(n * m),降为o(n * logm)
  • 二分查找的关键:想好自己想要的值,是在l(左指针)还是在r(右指针)
    • 因为我想要找“当前主食可以匹配到的第一个最贵的饮料”,因此右指针r就是想要找的

AC代码

var breakfastNumber = function(staple, drinks, x) {
  const mod = 1e9 + 7;
  // [5,10,20] [2,5,5]
  const len = staple.length;
  staple.sort((a, b) => a - b);
  drinks.sort((a, b) => a - b);
  // console.log(staple,drinks)
  let res = 0;
  for(let i = 0;i < len; i++) {
    // 二分查找另一个数组中最大满足的即:加和小于x
    if(staple[i] < x){
      const cha = x - staple[i];
      // 二分
      let l = -1,r = drinks.length;
      while(l + 1 != r) {
        // console.log('l',l,'r',r)
        const mid = parseInt((l + r) / 2);
        // console.log('mid',mid)
        if(drinks[mid] <= cha){
          l = mid;
        }else {
          r = mid;
        }
      }
      // 找到的时候,l就是结果
      // console.log('res', res,'l',l)
      res = res + (l + 1) % mod;
    }
  }
  return res % mod;
};

总结

  • 思路上的转变,因为题目中只是要求我们找出有多少种方案,那么我们可以找到第一个不合适的,那么前面就都是合适的,直接计算区间长度就可以得到合适的方案数。此时再分析,如何找到第一个不合适的,那么因为数组是有序的,因此可以使用二分,时间复杂度o(logn)
  • 有意思的评论区 image.png