【每日算法Day 103】老题新做,几乎不会有人想到的解法,它来了
前两天 Day 99 的时候,做过一道顺子的题目,当时有一个网友的妙解有点没看懂,今天我来给大家详细讲解一下。
题目链接
LeetCode 846. 一手顺子[1]
往期回顾:
【每日算法Day 99】你们可能不知道只用20万赢到578万是什么概念[2]
题目描述
卢本伟有一手(hand)由整数数组给定的牌。
现在她想把牌重新排列成组,使得每个组的大小都是 W
,且由 W
张连续的牌组成。
如果她可以完成分组就返回 true
,否则返回 false
。
说明:
1 <= hand.length <= 10000
0 <= hand[i] <= 10^9
1 <= W <= hand.length
示例1
输入:
hand = [1,2,3,6,2,3,4,7,8], W = 3
输出:
true
解释:
卢本伟的手牌可以被重新排列为 [1,2,3],[2,3,4],[6,7,8]。
示例2
输入:
hand = [1,2,3,4,5], W = 4
输出:
false
解释:
卢本伟的手牌无法被重新排列成几个大小为 4 的组。
题解
这题的妙解来自于题解区网友 zhanzq
,当时没怎么看懂,现在我来给大家讲解一下。
网友题解地址[3]
我们用一个例子来讲解:
假设 W = 3
,给定的手牌正好是三个顺子:[1,2,3], [2,3,4], [6,7,8]
。
那么我们统计出每张牌的数量,并且从小到大排序,记为 count
,这里就是 [1,2,2,1,0,1,1,1,0]
,并且在数字不连续处和末尾补 0
(作用后面会详细说)。
- 然后从小到大遍历每一张牌,首先
1
只有一张,那么如果它和后面牌能构成顺子,那么2, 3
至少要有一张才行,于是total
数组后面两个位置都加上1
。 - 然后遍历到
2
,因为2
的数量是大于该位置处的total
值的,所以2
的数量足够满足前面的牌顺子要求。此外2
还会多出一张,那么后面两个位置至少要有一张牌才行,于是total
后面两个位置再加上1
。 - 然后遍历
3, 4
,发现数量正好都等于total
,那说明它俩正好和前面的牌构成顺子,一点都不会多余。 - 然后遍历到
0
了,这就说明和前面的牌断开了。如果这时候total
不为0
,就说明中间缺失了一些牌,前面存在顺子没法补足结尾。而如果最开始没有填充0
的话,就没有办法判断这里的牌是否和前面连续的,你就有可能把6
这张牌直接接到4
后面组成顺子了。 - 然后遍历
6, 7, 8
同理,在对应位置处更新total
就行了。 - 最后遍历
0
,发现total
也是0
,那就说明整副牌可以构成顺子,完美!
时间复杂度是 ,这题数据不强也可以过的。
有没有办法优化呢?其实更新 total
这一步可以优化掉 这个复杂度,直接 更新 total
。
- 首先遍历
1
,因为1
只有一张,那么如果它和后面牌能构成顺子,那么2, 3
至少要有一张才行。但是这里我们不对这几张牌的total
加上一,而是在这个顺子结尾的下一张牌处的deltas
减去1
。 - 然后遍历
2
,那么这时候没有total
了,怎么计算应该扣除多少前面顺子需要的2
呢?其实只需要用前一张牌的牌数加上当前的deltas
值就行了。为什么呢?前面一张牌有多少张,你当前这张就得至少有那么多去构成顺子,但是如果前面一张牌是某些顺子的结尾,你还得扣掉一些,而扣掉的数值正好就是当前的deltas
,这在前面顺子的开头处已经记录过了。 - 后面操作类似,就不详细阐述了。
这种方法精髓就在于,不需要直接更新所有的 total
值,只需要在顺子结尾下一个元素处更新一下 deltas
就行了,每次的 total
可以通过上一张牌的 count
和当前的 deltas
推算出来。
这样总的时间复杂度就降到了 ,近似 。
不得不说,这个方法还是非常妙的,反正我是一下子想不到的,看了代码都想了很久才想通。
代码
暴力更新(c++)
class Solution {public:bool valid(vector<int> &count, int W) {int n = count.size();vector<int> total(n, 0);for (int i = 0; i < n; ++i) {if (count[i] > total[i]) {int delta = count[i] - total[i];for (int j = i; j < i+W && j < n; ++j) total[j] += delta;} else if (count[i] < total[i]) {return false;}}return true;}bool isNStraightHand(vector<int>& hand, int W) {int n = hand.size();if (W == 1) return true;if (n%W) return false;sort(hand.begin(), hand.end());vector<int> count;int i = 0, j = 0;while (i < n) {while (j < n && hand[i] == hand[j]) j++;count.push_back(j-i);if (j >= n) break;else if (hand[j] != hand[j-1]+1) count.push_back(0);i = j;}count.push_back(0);return valid(count, W);}
};
优化(c++)
class Solution {public:bool valid(vector<int> &count, int W) {int n = count.size(), pre = 0;vector<int> deltas(n, 0);for (int i = 0; i < n; ++i) {pre += deltas[i];if (pre < count[i]) {int delta = count[i] - pre;pre = count[i];if (i + W < n) deltas[i+W] -= delta;} else if (pre > count[i]) {return false;}}return true;}bool isNStraightHand(vector<int>& hand, int W) {int n = hand.size();if (W == 1) return true;if (n%W) return false;sort(hand.begin(), hand.end());vector<int> count;int i = 0, j = 0;while (i < n) {while (j < n && hand[i] == hand[j]) j++;count.push_back(j-i);if (j >= n) break;else if (hand[j] != hand[j-1]+1) count.push_back(0);i = j;}count.push_back(0);return valid(count, W);}
};
参考资料
[1]
LeetCode 846. 一手顺子: https://leetcode-cn.com/problems/hand-of-straights/
[2]
【每日算法Day 99】你们可能不知道只用20万赢到578万是什么概念: https://godweiyang.com/2020/04/13/leetcode-846/
[3]
网友题解地址: https://leetcode-cn.com/problems/hand-of-straights/solution/onlognsuan-fa-by-zhanzq/
【每日算法Day 103】老题新做,几乎不会有人想到的解法,它来了相关推荐
- 旧题新做:从idy的视角看数据结构
"今天你不写总结--!!!" 额-- 还是讲我的吧.这些考试都是idy出的题. 20170121:DFS序. ST表.线段树练习 这是第一次考数据结构. Problem 1. se ...
- 老题新理解-在话winform之间的窗体传值
也许当你看到标题的时候,你会想,这窗体间传值方法就这么多,无非就是那几种: 1.静态变量(这个最简单) 非时性的传递: 1.窗体的属性 2.构造函数 时时性的传递: 1.委托 2.静态变量(这个最简单 ...
- 经典算法题每日演练——第十九题 双端队列
经典算法题每日演练--第十九题 双端队列 原文:经典算法题每日演练--第十九题 双端队列 话说大学的时候老师说妹子比工作重要~,工作可以再换,妹子这个...所以...这两个月也就一直忙着Fall in ...
- php算法在线刷题,c,算法_每日一道算法:leetcode 刷题碰到的问题。,c,算法 - phpStudy...
每日一道算法:leetcode 刷题碰到的问题. 这是题目: Given an unsorted array nums, reorder it such that nums[0] < nums[ ...
- 经典算法题每日演练——第二十二题 奇偶排序
原文:经典算法题每日演练--第二十二题 奇偶排序 这个专题因为各种原因好久没有继续下去了,MM吧...你懂的,嘿嘿,不过还得继续写下去,好长时间不写,有些东西有点生疏了, 这篇就从简单一点的一个&qu ...
- endpointimpl怎么填参数_App拉新:以老拉新活动怎么做?
虽然利用老用户来拉新用户这种活动方式已经比较老套了,但是这种方式依然可以为App带来很多的流量和用户,并且有概率实现用户裂变式的增长. 以老拉新这种拉新获客模式,主要是源于用户的分享行为,有些App会 ...
- 怎么做app图标_App拉新:以老拉新活动怎么做?
虽然利用老用户来拉新用户这种活动方式已经比较老套了,但是这种方式依然可以为App带来很多的流量和用户,并且有概率实现用户裂变式的增长. 以老拉新这种拉新获客模式,主要是源于用户的分享行为,有些App会 ...
- 每日算法刷题Day7-比较字符串大小,去掉多余的空格,单词替换
⭐每日算法题解系列文章旨在精选重点与易错的算法题,总结常见的算法思路与可能出现的错误,与笔者另一系列文章有所区别,并不是以知识点的形式提升算法能力,而是以实战习题的形式理解算法,使用算法.
- 每日算法刷题Day10-字符串最大跨距、最长公共字符串后缀
CSDN话题挑战赛第2期 参赛话题:算法题解 ⭐每日算法题解系列文章旨在精选重点与易错的算法题,总结常见的算法思路与可能出现的错误,与笔者另一系列文章有所区别,并不是以知识点的形式提升算法能力,而是以 ...
- 每日算法刷题Day5-平方矩阵II和III、蛇形矩阵图解
⭐每日算法题解系列文章旨在精选重点与易错的算法题,总结常见的算法思路与可能出现的错误,与笔者另一系列文章有所区别,并不是以知识点的形式提升算法能力,而是以实战习题的形式理解算法,使用算法.
最新文章
- SQL中where与having的区别
- UStore-自定义JDF文件格式输出
- 【风险管理】风控一二三
- 一文探讨 RPC 框架中的服务线程隔离
- Xposed框架实战
- Install GIT in Ubuntu
- jQuery构建路由
- 谷歌fuchsiaos和华为鸿蒙,华为鸿蒙最大的对手现身!谷歌正式推送Fuchsia OS,或替代安卓...
- python barrier_Python线程障碍对象Barrier原理详解
- c#发送邮件,可发送多个附件
- linux之systemctl命令
- java的实例变量_JAVA语言中的实例变量
- Android游戏辅助开发流程,安卓辅助脚本开发游戏化编
- 怎样清理xp系统垃圾
- 被带走的机密文件WP
- c语言中ch的作用,C语言中IN(ch,OP)是什么意思
- VFB组件:Picture控件(画板)
- 视频网站大幅提价无异于“逆水行舟”
- 【DS实践 | Coursera】Assignment 2 | Applied Plotting, Charting Data Representation in Python
- CatBoost 模型中标称型特征转换成数字型特征