哦吼吼,AK的双周赛,总体来说还是简单的,主要思路要跟得上,先做的第四题,才回来做第三题,第三题卡了很一会儿,还是有点可惜(其实是得了便宜还卖乖,哈哈哈
第一题:排序。
第二题:模拟。
第三题:双指针。
第四题:组合数学。
详细题解如下。
1. 根据数字二进制下 1 的数目排序(Sort Integers By The Number of 1 Bits)
2. 每隔 n 个顾客打折(Apply Discount Every N Orders)
3.包含所有三种字符的子字符串数目(Number Of Substrings Containing All Three Characters)
4.有效的快递序列数目(Count All Valid Pickup And Delivery Options)
LeetCode第20场双周赛地址:
https://leetcode-cn.com/contest/biweekly-contest-20/
1. 根据数字二进制下 1 的数目排序(Sort Integers By The Number of 1 Bits)
题目链接
https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/
题意
给你一个整数数组 arr 。请你将数组中的元素按照其二进制表示中数字 1 的数目升序排序。
如果存在多个数字二进制中 1 的数目相同,则必须将它们按照数值大小升序排列。
请你返回排序后的数组。
示例 1:
输入:arr = [0,1,2,3,4,5,6,7,8] 输出:[0,1,2,4,8,3,5,6,7] 解释:[0] 是唯一一个有 0 个 1 的数。 [1,2,4,8] 都有 1 个 1 。 [3,5,6] 有 2 个 1 。 [7] 有 3 个 1 。 按照 1 的个数排序得到的结果数组为 [0,1,2,4,8,3,5,6,7]
提示:
1 <= arr.length <= 500
0 <= arr[i] <= 10^4
解题思路
题目意思很简单,就是进行排序,排序依据有两个
1、按照元素对应的二进制数中 1 的个数进行升序排序。
2、如果二进制数中 1 的个数相同,就按照原本元素的大小进行升序排序。
因此,我们先要计算出,每一个元素对应的二进制中 1 的个数。然后重写 排序函数 sort 的 cmp即可。
AC代码(C++)
unordered_map<int, int> mp; // 记录每个元素的 1 个数
bool cmp(const int& a, const int& b) {
if(mp[a] != mp[b]) return mp[a] < mp[b]; // 先按照 mp(1 的个数进行排序)的大小排序
else return a < b;
}
class Solution {
public:
vector<int> sortByBits(vector<int>& arr) {
int n = arr.size();
for(int i = 0;i < n; ++i) {
if(mp[arr[i]] != 0) continue;
int cnt = 0;
// 求 1 的个数,可以是int 正数有 31位
for(int j = 0;j < 31; ++j) {
if((arr[i] >> j) & 1) cnt++;
}
// 也可以写成下面的形式,每次右移一位
// int tep = arr[i];
// while(tep) {
// if(tep & 1 == 1) ++cnt;
// tep >>= 1;
// }
// 因为mp的初始大小是 0,而 1 出现的次数也可以是 0,所以我们把出现次数都 + 1,进行排序,顺序也不会变动
mp[arr[i]] = cnt + 1;
}
sort(arr.begin(), arr.end(), cmp);
return arr;
}
};
2. 每隔 n 个顾客打折(Apply Discount Every N Orders)
题目链接
https://leetcode-cn.com/problems/apply-discount-every-n-orders/
题意
超市里正在举行打折活动,每隔 n 个顾客会得到 discount 的折扣。
超市里有一些商品,第 i 种商品为 products[i] 且每件单品的价格为 prices[i] 。
结账系统会统计顾客的数目,每隔 n 个顾客结账时,该顾客的账单都会打折,折扣为 discount (也就是如果原本账单为 x ,那么实际金额会变成 x - (discount * x) / 100 ),然后系统会重新开始计数。
顾客会购买一些商品, product[i] 是顾客购买的第 i 种商品, amount[i] 是对应的购买该种商品的数目。
请你实现 Cashier 类:
- Cashier(int n, int discount, int[] products, int[] prices) 初始化实例对象,参数分别为打折频率 n ,折扣大小 discount ,超市里的商品列表 products 和它们的价格 prices 。
- double getBill(int[] product, int[] amount) 返回账单的实际金额(如果有打折,请返回打折后的结果)。返回结果与标准答案误差在 10^-5 以内都视为正确结果。
示例 1:
输入 ["Cashier","getBill","getBill","getBill","getBill","getBill","getBill","getBill"] [[3,50,[1,2,3,4,5,6,7],[100,200,300,400,300,200,100]],[[1,2],[1,2]],[[3,7],[10,10]],[[1,2,3,4,5,6,7],[1,1,1,1,1,1,1]],[[4],[10]],[[7,3],[10,10]],[[7,5,3,1,6,4,2],[10,10,10,9,9,9,7]],[[2,3,5],[5,3,2]]] 输出 [null,500.0,4000.0,800.0,4000.0,4000.0,7350.0,2500.0] 解释 Cashier cashier = new Cashier(3,50,[1,2,3,4,5,6,7],[100,200,300,400,300,200,100]); cashier.getBill([1,2],[1,2]); // 返回 500.0, 账单金额为 = 1 * 100 + 2 * 200 = 500. cashier.getBill([3,7],[10,10]); // 返回 4000.0 cashier.getBill([1,2,3,4,5,6,7],[1,1,1,1,1,1,1]); // 返回 800.0 ,账单原本为 1600.0 ,但由于该顾客是第三位顾客,他将得到 50% 的折扣,所以实际金额为 1600 - 1600 * (50 / 100) = 800 。 cashier.getBill([4],[10]); // 返回 4000.0 cashier.getBill([7,3],[10,10]); // 返回 4000.0 cashier.getBill([7,5,3,1,6,4,2],[10,10,10,9,9,9,7]); // 返回 7350.0 ,账单原本为 14700.0 ,但由于系统计数再次达到三,该顾客将得到 50% 的折扣,实际金额为 7350.0 。 cashier.getBill([2,3,5],[5,3,2]); // 返回 2500.0
提示:
1 <= n <= 10^4
0 <= discount <= 100
1 <= products.length <= 200
1 <= products[i] <= 200
products 列表中 不会 有重复的元素。
prices.length == products.length
1 <= prices[i] <= 1000
1 <= product.length <= products.length
product[i] 在 products 出现过。
amount.length == product.length
1 <= amount[i] <= 1000
最多有 1000 次对 getBill 函数的调用。
返回结果与标准答案误差在 10^-5 以内都视为正确结果。
解题思路
根据题目意思进行模拟即可。
即,对应每一个客户,计算出购买的物品总价格 = 各个物品 * 数量 * 单价 之和。
然后用一个变量统计这是第几个客户,当 = n 的时候,就对这个客户的总价格进行打折计算再输出。
AC代码(C++)
class Cashier {
public:
int cnt = 0; // 第几个客户
unordered_map<int, int> mp; // 记录每个物品对应的单价
int N, Dis;
Cashier(int n, int discount, vector<int>& products, vector<int>& prices) {
cnt = 0;
N = n;
mp.clear();
Dis = discount;
for(int i = 0;i < products.size(); ++i){
mp[products[i]] = prices[i];
}
}
double getBill(vector<int> product, vector<int> amount) {
++cnt; // 来了一个新客户 + 1
double sum = 0; // 计算总价
for(int i = 0;i < product.size(); ++i) {
sum += mp[product[i]] * amount[i];
}
if(cnt == N) { // 当 = N 时,表示这个客户打折
cnt = 0;
sum = sum - (1.0 * Dis * sum) /1.0 / 100; // 按要求计算即可
}
return sum;
}
};
/**
* Your Cashier object will be instantiated and called as such:
* Cashier* obj = new Cashier(n, discount, products, prices);
* double param_1 = obj->getBill(product,amount);
*/
3.包含所有三种字符的子字符串数目(Number Of Substrings Containing All Three Characters)
题目链接
https://leetcode-cn.com/problems/number-of-substrings-containing-all-three-characters/
题意
给你一个字符串
s
,它只包含三种字符 a, b 和 c 。请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。
示例 1:
输入:s = "abcabc" 输出:10 解释:包含 a,b 和 c 各至少一次的子字符串为 "abc", "abca", "abcab", "abcabc", "bca", "bcab", "bcabc", "cab", "cabc" 和 "abc" (相同字符串算多次)。
示例 2:
输入:s = "aaacb" 输出:3 解释:包含 a,b 和 c 各至少一次的子字符串为 "aaacb", "aacb" 和 "acb" 。
示例 3:
输入:s = "abc" 输出:1
提示:
3 <= s.length <= 5 x 10^4
s
只包含字符 a,b 和 c 。
解题分析
我们使用双指针。[left, right] 表示在这个下标范围内的字符串,满足要求的最小下标范围,我们 left 从 0 开始,right 从 2 开始 (因为至少要长度为 3,才会至少包含 a,b,c 各一个)。
这个时候有两种情况
1)在这个下标范围内,已经满足了要求(至少包含 a,b,c 各一个),那么 right 即使再往后移动,也一定是满足的,因此从 left 开始的字符串,满足要求的个数就是,从 right 到字符串尾的个数(也就是 n - right ),那么此时从 left 开始的字符串,满足要求的数量是 n - right。
同时,我们从 left 开始满足了,那就要继续走 下一个,即 left + 1,那么此时 right 也是从上面的那个位置继续开始(不需要从头判断),因为 [left, right] 满足要求,那么把 left + 1 后,right还是从这个位置开始。
如果新的 [left, right] 还满足,继续走 1)
2)在这个下标范围不满足,那么是移动 right,将下标范围增大 right + 1 (直到 right 不能再往后移动了),这样子才有可能满足要求
因此这个双指针的方法,相当于是两个指针分别遍历一次数组,因此时间复杂度是 O(N),不会超时。
AC代码(C++)
class Solution {
public:
int numberOfSubstrings(string s) {
int cnt[3]; // 统计下标范围内的a,b,c个数
memset(cnt, 0, sizeof(cnt));
int n = s.size();
int ans = 0;
for(int i = 0;i < 3; ++i) ++cnt[s[i] - 'a'];
int l = 0, r = 2; // 一开始的范围 [0, 2]
while(r < n - 1) {
while(cnt[0] > 0 && cnt[1] > 0 && cnt[2] > 0) { // 满足要求,那就是移动 left,同时,数量 += n - right
ans += n - r;
--cnt[s[l] - 'a'];
++l;
}
// 不满足要求,那就要增大下标范围,所以是 right + 1
++r;
++cnt[s[r] - 'a'];
}
// 此时 r 已经是最后了,那么判断左边界的 l 还可不可以移动
while(cnt[0] > 0 && cnt[1] > 0 && cnt[2] > 0) {
ans += n - r;
--cnt[s[l] - 'a'];
++l;
}
return ans;
}
};
4.有效的快递序列数目(Count All Valid Pickup And Delivery Options)
题目链接
https://leetcode-cn.com/problems/count-all-valid-pickup-and-delivery-options/
题意
给你 n 笔订单,每笔订单都需要快递服务。
请你统计所有有效的 收件/配送 序列的数目,确保第 i 个物品的配送服务 delivery(i) 总是在其收件服务 pickup(i) 之后。
由于答案可能很大,请返回答案对 10^9 + 7 取余的结果。
示例 1:
输入:n = 1 输出:1 解释:只有一种序列 (P1, D1),物品 1 的配送服务(D1)在物品 1 的收件服务(P1)后。
示例 2:
输入:n = 2 输出:6 解释:所有可能的序列包括: (P1,P2,D1,D2),(P1,P2,D2,D1),(P1,D1,P2,D2),(P2,P1,D1,D2),(P2,P1,D2,D1) 和 (P2,D2,P1,D1)。 (P1,D2,P2,D1) 是一个无效的序列,因为物品 2 的收件服务(P2)不应在物品 2 的配送服务(D2)之后。
示例 3:
输入:n = 3 输出:90
提示:
- 1 <= n <= 500
解题分析
一道组合数学的问题,根据要求,是 Pi 一定在 Di 的前面 ,具体分析如下:
AC代码(C++)
class Solution {
public:
const long long MOD = 1e9 + 7;
int countOrders(int n) {
long long res = 1;
for(int i = 2;i <= n; ++i) {
long long tep = res;
res = i * (2 * i - 1) % MOD;
res = tep * res % MOD;
}
return res;
}
};