一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
每日刷题 2021.04.13
- leetcode原题链接:leetcode-cn.com/problems/2v…
- 难度:中等
- 方法:二分+排序
题目
- 小扣在秋日市集选择了一家早餐摊位,一维整型数组
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) - 有意思的评论区