LeetCode第20场双周赛(Biweekly Contest 20)解题报告

哦吼吼,AK的双周赛,总体来说还是简单的,主要思路要跟得上,先做的第四题,才回来做第三题,第三题卡了很一会儿,还是有点可惜(其实是得了便宜还卖乖,哈哈哈

第一题:排序。

第二题:模拟。

第三题:双指针。

第四题:组合数学。

详细题解如下。


1. 根据数字二进制下 1 的数目排序(Sort Integers By The Number of 1 Bits)

           AC代码(C++)

2. 每隔 n 个顾客打折(Apply Discount Every N Orders)

           AC代码(C++)

3.包含所有三种字符的子字符串数目(Number Of Substrings Containing All Three Characters)

           AC代码(C++)

4.有效的快递序列数目(Count All Valid Pickup And Delivery Options)

           AC代码(C++)


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;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值