又是一周掉分之旅,我发现,LeetCode周赛的数据好水,所以有的时候,实在没思路,先暴力解决试试(即使分析出时间复杂度会超时),比如第二题和第三题都可以暴力通过,GG思密达。

这周主要使用了数据结构中的:优先队列。即当我们动态改变一个数组的值,同时每一次都想知道其中的最大值(或者最小值),那么可以考虑使用 优先队列。其存取的时间复杂度是 O(logN),N 是优先队列中的个数。

第一题:暴力枚举 或者 二分查找。

第二题:前缀和(乘积)。

第三题:贪心。

第四题:思维。

详细题解如下。


1.统计有序矩阵中的负数(Count Negative Numbers In A Sorted Matrix)

AC代码(C++)

2. 最后 K 个数的乘积(Product of The Last K Numbers)

AC代码(C++)

3.最多可以参加的会议数目(Maximum Number of Events that Can Be Attended)

AC代码(C++)

4.多次求和构造目标数组(Maximum Students Taking Exam)

AC代码(C++)


LeetCode第176场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-176


1.统计有序矩阵中的负数(Count Negative Numbers In A Sorted Matrix)

题目链接

https://leetcode-cn.com/problems/count-negative-numbers-in-a-sorted-matrix/

题意

给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。

请你统计并返回 grid 中 负数 的数目。

示例 1:

输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵中共有 8 个负数。

示例 2:

输入:grid = [[3,2],[1,0]]
输出:0

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100
  • -100 <= grid[i][j] <= 100

解题思路

根据数据范围,我们可以直接暴力枚举二维数组中的每一个数,时间复杂度为 O(N*M)。

优化的做法:二分查找,从数组的右上角(这样子,该值的左边都比它大,下边都比它小),也就有了二分查找。从而我们可以找到每一列中,从第几行开始,就都是负数,这样子的时间复杂度为 O(M + N),最多遍历一次所有列,同时一次所有行。

AC代码(C++)

class Solution {
public:int countNegatives(vector<vector<int>>& grid) {int cnt = 0;int n = grid.size(), m = grid[0].size();for (int i = 0; i < n; ++i) {for (int j = 0;j < m; ++j) {if(grid[i][j] < 0) ++cnt;}}return cnt;}
};

2. 最后 K 个数的乘积(Product of The Last K Numbers)

题目链接

https://leetcode-cn.com/problems/product-of-the-last-k-numbers/

题意

请你实现一个「数字乘积类」ProductOfNumbers,要求支持下述两种方法:

1. add(int num)

  • 将数字 num 添加到当前数字列表的最后面。

2. getProduct(int k)

  • 返回当前数字列表中,最后 k 个数字的乘积。
  • 你可以假设当前列表中始终 至少 包含 k 个数字。
  • 题目数据保证:任何时候,任一连续数字序列的乘积都在 32-bit 整数范围内,不会溢出。

示例 1:

输入:
["ProductOfNumbers","add","add","add","add","add","getProduct","getProduct","getProduct","add","getProduct"]
[[],[3],[0],[2],[5],[4],[2],[3],[4],[8],[2]]输出:
[null,null,null,null,null,null,20,40,0,null,32]解释:
ProductOfNumbers productOfNumbers = new ProductOfNumbers();
productOfNumbers.add(3);        // [3]
productOfNumbers.add(0);        // [3,0]
productOfNumbers.add(2);        // [3,0,2]
productOfNumbers.add(5);        // [3,0,2,5]
productOfNumbers.add(4);        // [3,0,2,5,4]
productOfNumbers.getProduct(2); // 返回 20 。最后 2 个数字的乘积是 5 * 4 = 20
productOfNumbers.getProduct(3); // 返回 40 。最后 3 个数字的乘积是 2 * 5 * 4 = 40
productOfNumbers.getProduct(4); // 返回  0 。最后 4 个数字的乘积是 0 * 2 * 5 * 4 = 0
productOfNumbers.add(8);        // [3,0,2,5,4,8]
productOfNumbers.getProduct(2); // 返回 32 。最后 2 个数字的乘积是 4 * 8 = 32 

提示:

  • add 和 getProduct 两种操作加起来总共不会超过 40000 次。
  • 0 <= num <= 100
  • 1 <= k <= 40000

解题思路

从数据范围分析,如果直接每一次暴力求 k 项乘积,应该是会超时的(但实际上,用C++暴力,可以过,不知道为啥子。。。)除非在暴力的时候,进行分析优化(根据分析,乘积的结果应该是 int 大小,那也就是说,这个数最大为 2^31 - 1,由根据num 的范围,0 和 1的多少,其实不影响,k 的乘积(因为乘上 0 就是 0 ,乘上 1 还是本身 ),所以如果从 2 开始,k 个数相乘中,最多32个,就会超要求,所以其实,只要我们统计了 0 的个数,统计连续 1 的个数存在一起,那么对于连续 k 个相乘,最多也就是 64个左右 (也就是非0非1的数之间,插入非0或非1 ))。

而这道题,其实利用了前缀和(乘积)的性质,我们如果记录了 mutil[ i ] 为 前面 i 个数字的乘积,那么对于后 k 个数的结果就是 mutil[ n ] / mutil[ n - k ],也就是我们可以在 O(1) 时间内,求出后 k 个数乘积结果。

但是这里有一个问题,add 数字的 num 可能为 0,我们知道对于出现 0了,会使得乘积为 0。

那我们这里,当遇到 0 时,我们认为当前乘积为 1 (这样子,对于后面的乘积,不受影响(因为前面时 0 了,所以后面的和前面没关系了,重新算)),同时记录最后一个 0 出现的位置

当我们计算 后 k 个数的时候,我们判断,后 k 个数的位置,有没有出现 0 的位置,如果出现,那么直接返回 0。否则还是用 mutil[ n ] / mutil[ n - k ]。

举个例子

[2 3 0 4 2 3]
乘积结果 -> [2 3 1 4 8 24]
当我们要求后三个数的时候,那就是 24 / 1 = 24 (因为要除原本 0 位置的乘积,所以当我们输入 0 ,记录的是 1 )
当我们求后 4 个数的时候,由于 后 4 个数包括了 0 ,那就是返回 0

AC代码(C++)

class ProductOfNumbers {
public:vector<int> mutil;int len;int isZero;ProductOfNumbers() {// 初始化,用len记录乘积数组中的个数,同时由于前缀和的性质,我们一般多一位在前面(因为相除的时候,下标要 - 1,所以为了不越界,多一个值)mutil.clear();mutil.push_back(1);len = 1;isZero = 0;  // 记录最后一个 0 出现的位置}void add(int num) {if(num == 0) {isZero = len;mutil.push_back(1);}elsemutil.push_back(mutil[len - 1] * num);  // 如果不是 0,当前值 = 上一个值 * num++len;}int getProduct(int k) {if(isZero >= len - k) return 0;  // 判断出,后 k 个数的下标比 0 出现的下标小,也就是后 k 个数包括 0.return mutil[len - 1] / mutil[len - k - 1];}
};/*** Your ProductOfNumbers object will be instantiated and called as such:* ProductOfNumbers* obj = new ProductOfNumbers();* obj->add(num);* int param_2 = obj->getProduct(k);*/

3.最多可以参加的会议数目(Maximum Number of Events that Can Be Attended)

题目链接

https://leetcode-cn.com/problems/maximum-number-of-events-that-can-be-attended/

题意

给你一个数组 events,其中 events[i] = [startDayi, endDayi] ,表示会议 i 开始于 startDayi ,结束于 endDayi 。

你可以在满足 startDayi <= d <= endDayi 中的任意一天 d 参加会议 i 。注意,一天只能参加一个会议。

请你返回你可以参加的 最大 会议数目。

示例 1:

输入:events = [[1,2],[2,3],[3,4]]
输出:3
解释:你可以参加所有的三个会议。
安排会议的一种方案如上图。
第 1 天参加第一个会议。
第 2 天参加第二个会议。
第 3 天参加第三个会议。

示例 2:

输入:events= [[1,2],[2,3],[3,4],[1,2]]
输出:4

提示:

  • 1 <= events.length <= 10^5
  • events[i].length == 2
  • 1 <= events[i][0] <= events[i][1] <= 10^5

解题分析

拿到题目,想到就是利用贪心的做法

首先,先对会议进行排序 : 按照开始时间进行升序排序,如果开始时间一样,那么就按照结束时间排序。

那么我们对天数进行贪心,对于某一天,我们随便找一个,正在开会同时还没有参与的会议,并且为了使得会议时间尽量延长,我们先参与 快要结束的会议(已经结束的会议就不考虑了),然后再考虑下一天。

我们要记录,已经开会(也就是枚举的时间,大于了会议的开始时间)的会议,为了方便我们找到要更快结束的,即找到已经开始的会议中,结束时间最小的。那么这个会议记录是动态的(可能有新开的要进入,已经结束的,要去掉),在一个动态的记录中,找最小值,所以可以考虑使用 : 优先队列

  • 按照会议开始日期对 events 排序,使用默认排序算法就可以
  • 再使用一个优先队列,根据日期,将今天开始的会议全都加入到优先队列中(也就是优先队列中的,是已经开始会议的结束时间)
  • 优先队列的排序以会议的结束日期为标准,保证越接近结束日期的越靠前(按结束时间从小到大排序)
  • 每一天如果优先队列里还有会议,就去参加一个(同时把这个会议去掉)
  • 新的一天,先把优先队列里已经过期的会议清除掉(也就是结束时间 < 新的时间,那就是会议已经结束了)

AC代码(C++)

class Solution {
public:int maxEvents(vector<vector<int>>& events) {priority_queue<int, vector<int>, greater<int> > pq;  // 优先队列,升序排序sort(events.begin(), events.end());  // 对events的开始时间进行排序int n = events.size();int day = 0;int ans = 0;int len = 0;  用于判断events的是不是已经结束了while (len < n || !pq.empty()) {++day;  // 新的一天while(!pq.empty() && pq.top() < day)  // 先把已经结束的去掉,因为结束时间小的在队头{pq.pop();}while(len < n && events[len][0] == day) {  // 同时把新的一天,要开始的新会议加入队列中pq.push(events[len][1]);++len;}if(!pq.empty()) {  // 如果队列不为空,说明对于这一天而言,有这些会议都在开,那么就找快要结束的一个会议参加pq.pop();++ans;}}return ans;}
};

4.多次求和构造目标数组(Maximum Students Taking Exam)

题目链接

https://leetcode-cn.com/problems/maximum-students-taking-exam/

题意

给你一个整数数组 target 。一开始,你有一个数组 A ,它的所有元素均为 1 ,你可以执行以下操作:

  • 令 x 为你数组里所有元素的和
  • 选择满足 0 <= i < target.size 的任意下标 i ,并让 A 数组里下标为 i 处的值为 x 。
  • 你可以重复该过程任意次

如果能从 A 开始构造出目标数组 target ,请你返回 True ,否则返回 False 。

示例 1:

输入:target = [9,3,5]
输出:true
解释:从 [1, 1, 1] 开始
[1, 1, 1], 和为 3 ,选择下标 1
[1, 3, 1], 和为 5, 选择下标 2
[1, 3, 5], 和为 9, 选择下标 0
[9, 3, 5] 完成

示例 2:

输入:target = [1,1,1,2]
输出:false
解释:不可能从 [1,1,1,1] 出发构造目标数组。

示例 3:

输入:target = [8,5]
输出:true

提示:

  • N == target.length
  • 1 <= target.length <= 5 * 10^4
  • 1 <= target[i] <= 10^9

解题分析

数学思维题,首先先要分析题目,我们如果要正的推,即从 (1, 1, 1...,1)推到相应的 target,很难,因为有很多的可能性。那么就要考虑,逆着推

比如,对于示例 1 和 示例 3

[9,3,5] -> [1, 3, 5] -> [1, 3, 1] -> [1, 1, 1]
[8,5] -> [3, 5] -> [3, 2] -> [1, 2] -> [1, 1]

也就是,我们要得到数组中的最大值(记作mx),利用 最大值 - 其他值之和,替换掉最大值,这样子才能一步步接近 1。记数组的长度为 n。

那么有几个问题要解决

1)因为最大值要替换,所以我们要动态得到数组中的最大值,那就考虑,优先队列 (要求从大到小排序)

2)有了最大值,我们怎么知道其他值之和?我们可以先求出,数组的所有和(记作 sum),那么 其他值之和 = 所有和 - 最大值。同时,当我们更新最大值替换成 最大值 - 其他值之和 (记作 temp),那么此时的数组和更新为 sum = sum - mx + temp。(因为最大值被替换成 temp了)

3)什么时候返回false,什么时候返回true?想要为 true,那么只有当所有数组中的元素为 1,也就是 sum == n(n 是数组长度)。如果不满足,那就一直做上面和1)和2)。那什么时候为false,也就是当我们计算出要把 mx 替换成 temp 时,计算出来的 temp <= 0 (因为数组中的数一定是 >= 1,说明比 1 小的数,说明就不对了),因此,当我们计算出 temp < 1,那么也就是替换后的数,不应该出现在数组中,也就是无法回到 全是 1 的数组,那就返回 false。

AC代码(C++)

#define LL long longclass Solution {
public:bool isPossible(vector<int>& target) {int n = target.size();priority_queue<LL, vector<LL>, less<LL> > pq;  // 利用优先队列找到最大值 O(logN) (降序排序)LL sum = 0;LL mx;for(auto t : target){sum += t;pq.push(t);  // 把所有数,放到队列中}while(sum > n) {mx = pq.top();  // 取出最大数pq.pop();LL temp = sum - mx;  // 其他数的和if(mx <= temp) return false;  // 如果其他数和 >= 最大值,那么最大值 - 其他数和 <= 0temp = mx - temp;  // 要替换的数,也就是 最大值 - 其他数之和sum = sum - mx + temp;   // 更新数组所有数之和pq.push(temp);  // 对于队列而言,我们把最大数去掉了,要替换成temp,那就是加入temp}return true;}
};

LeetCode第176场周赛(Weekly Contest 176)解题报告相关推荐

  1. 记LeetCode第143次周赛(Weekly Contest 143)

    上午打完LeetCode第143次周赛,发现很多不常用的知识点都比较生涩了,最后一个半小时也只ac了前两题.这一次的题目相对以往还是比较简单吧,但奈何就是迟迟没有在代码上有较满意的实现.学习果然是不进 ...

  2. LeetCode第187场周赛(Weekly Contest 187)解题报告

    差点又要掉分了,还好最后几分钟的时候,绝杀 AK.干巴爹!!! 第一题:思路 + 模拟暴力. 第二题:线性扫描. 第三题:双指针(滑动窗口) + 优先队列. 第四题:暴力每一行最小 k 个 + 优先队 ...

  3. LeetCode第 227 场周赛题解

    LeetCode第 227 场周赛题解 检查数组是否经排序和轮转得到 原题链接 https://leetcode-cn.com/problems/check-if-array-is-sorted-an ...

  4. LeetCode 第 194 场周赛

    LeetCode 第 194 场周赛 数组异或操作 思路和代码 保证文件名唯一 思路及代码 避免洪水泛滥 思路及代码 找到最小生成树里的关键边和伪关键边 思路及代码 这次周赛比以往难很多. 数组异或操 ...

  5. Acwing第72场周赛+Leetcode第314场周赛

    Acwing第72场周赛 第一题:AcWing 4624. 最小值 分析:向下取整可以用到math.h头文件中的floor()函数,最后输出时套用两个min()函数求三个数的最小值即可. 代码: #i ...

  6. Leetcode第 310 场周赛 补打

    Leetcode 第310场周赛 自己赛后打了一下,记录了一下时间,大概15min A 3题,第四题是写不出来,然后学习了一天线段树(真的强). 思路: 1.排序后统计偶数的数目 2.遍历扫一遍,用直 ...

  7. Leetcode第321场周赛补题

    Leetcode第321场周赛补题 第一题:6245. 找出中枢整数 - 力扣(LeetCode) 分析:由于数组中是差值为1的等差数列,所以可以直接用等差数列求和公式的朴素法更加简便的解决这题,,其 ...

  8. Leetcode 第133场周赛解题报告

    今天参加了leetcode的周赛,算法比赛,要求速度比较快.有思路就立马启动,不会纠结是否有更好的方法或代码可读性.只要在算法复杂度数量级内,基本上是怎么实现快速就怎么来了. 比赛时先看的第二题,一看 ...

  9. [算法]LeetCode第194场周赛202006021

    第194场周赛 20200621 1486. 数组异或操作 题目描述1 给你两个整数,n 和 start . 数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 ...

最新文章

  1. 两个苹果手机怎么传通讯录_苹果手机通讯录丢失怎么恢复?货真价实的通讯录恢复技巧...
  2. CVPR2021深度框架训练:不是所有数据增强都可以提升最终精度
  3. 线性表元素的区间删除
  4. android 7.0 更新apk,Android更新apk兼容7.0和8.0
  5. React-router总结
  6. HashMap和ArrayList初始大小和扩容后的大小
  7. 配置 Syslog 守护程序
  8. linux中代码挂上n,Linux系统常用命令nl详解(示例代码)
  9. python爬虫微博图片,pyhton爬虫爬取微博某个用户所有微博配图
  10. 基于jquery.fixedheadertable 表格插件左侧固定 对齐
  11. 用oledb导出数据到excel
  12. 分享一款在线less转css的神器
  13. Android初学第9天
  14. Nginx的configure各项中文说明
  15. ofd阅读器qt_OFD编辑器实例
  16. pytorch中的nn.Unfold()函数和fold(函数详解
  17. 企业上云要几步?中拓互联奉送企业上云全攻略
  18. UVA, 516 Prime Land
  19. android屏幕 录制检测,Android 录制屏幕的实现方法
  20. 微信小程序之获取百度语音合成

热门文章

  1. python 字符串 双引号中包含双引号
  2. 酒吧运营你要知道的:制订方案
  3. 前端转安卓开发!作为一名程序员我不忘初心,进阶学习资料!
  4. android 虚拟返回键功能_Android返回键功能的实现方法
  5. Java实现文件监控器FileMonitor
  6. 服务器的信号来源来自哪里,人类曾多次发现地外信号,到底来自哪里?
  7. Axure自定义元件
  8. nginx + php 出现file not found问题解决方案
  9. VMware虚拟机开机黑屏问题
  10. 英语最常使用对话排行前30