0 <= hand[i] <= 10⁹
额……谁家的牌有这么大的面额?
1、瞅瞅题
题内也说了,此题与另一个题是相同的。
虽然题意应该都很明确,但可以说一个比一个简洁。
开干!
2、审题
直白点说,此题就是要把数组分成若干个连续的子数组。如果原数组所有数字都可以被分为子数组,就返回true;反之false。
辣么有什么需要注意的吗?
大概……就只有初始数组是无序的了。
这句话好像之前说过?
3、思路
由于题目是要求拆成连续的顺子,而初始数组又是无序的,所以很好想到排序是必要的工作。
我们可以通过排序得到连续的卡,就很容易判断卡是否能组成顺子。
哦,但是之前,还有一个必要的点,数组的数量一定是
groupSize
的整数倍。这是一个很好的快速判错点。
之后呢?
由于每张卡都可能存在多次,所以我想到通过哈希表来存储所有的卡及其出现的次数。
所以再次吐槽下卡面为什么辣么大。
之后,我们只需要遍历排好序的数组,有小到大判断能否有连续的groupSize
张牌来组成顺子。
这里大可以使用贪心的思想,一旦发现连续的groupSize
张就直接让他们出队。毕竟题目考察的是所有卡片都组成,而不是最多可以组成多少,于是这里是不允许有卡调单。
4、撸代码
class Solution {
public boolean isNStraightHand(int[] hand, int groupSize) {
if (groupSize == 1) {
return true;
}
//首先,判断数量是否是groupSize的整数倍
if (hand.length % groupSize != 0) {
return false;
}
Map<Integer, Integer> cardMap = new HashMap<>();
//记录每个卡片出现的次数
for (int card : hand) {
cardMap.put(card, cardMap.getOrDefault(card, 0) + 1);
}
//排序所有卡片(相同面额仅出现一次)
List<Integer> cardList = cardMap.keySet().stream().sorted().collect(Collectors.toList());
int i = 0;
while (i <= cardList.size() - groupSize) {
//当前卡
int curCard = cardList.get(i);
//取一张当前卡
if (this.takeCard(cardMap, curCard)) {
//当前卡还有库存时
for (int j = 1; j < groupSize; j++) {
//依次检查当前卡后续连续的卡是否还有库存
if (!this.takeCard(cardMap, curCard + j)) {
return false;
}
}
} else {
//当前卡没有库存时,才移动卡
i++;
}
}
return cardMap.isEmpty();
}
private boolean takeCard(Map<Integer, Integer> cardMap, Integer card) {
int count = cardMap.getOrDefault(card, 0);
//如果一张不剩,返回false
if (count == 0) {
return false;
}
//如果仅剩一张,则取完后删除,否则让数量-1
if (count == 1) {
cardMap.remove(card);
} else {
cardMap.put(card, count - 1);
}
return true;
}
}
5、解读
整体如此。
最先的是几个特例情况:
if (groupSize == 1) {
return true;
}
此判断是因为题目给到1 <= groupSize <= hand.length
,而groupSize=1
时,即单张卡就能组成顺子,便也可以不用进行任何判断了。
虽然不知道case中有没有给出这个情况。
//首先,判断数量是否是groupSize的整数倍
if (hand.length % groupSize != 0) {
return false;
}
然后,便是提到过的卡牌数量的快速判错了。
回到主要逻辑。
首先,我遍历了初始数组,在将所有卡以及其出现次数存入一个Map中。
Map<Integer, Integer> cardMap = new HashMap<>();
//记录每个卡片出现的次数
for (int card : hand) {
cardMap.put(card, cardMap.getOrDefault(card, 0) + 1);
}
然后对Map的keySet进行排序,得到的就是排好序的所有出现过的卡。
//排序所有卡片(相同面额仅出现一次)
List<Integer> cardList = cardMap.keySet().stream().sorted().collect(Collectors.toList());
之后,就是遍历每张卡了,在该卡还有剩余时,判断之后连续groupSize
张卡是否都有数量。
while (i <= cardList.size() - groupSize) {
//当前卡
int curCard = cardList.get(i);
//取一张当前卡
if (this.takeCard(cardMap, curCard)) {
//当前卡还有库存时
for (int j = 1; j < groupSize; j++) {
//依次检查当前卡后续连续的卡是否还有库存
if (!this.takeCard(cardMap, curCard + j)) {
return false;
}
}
} else {
//当前卡没有库存时,才移动卡
i++;
}
}
判断是否还有数量的逻辑,我单独写成了一个方法,逻辑很简单,大家结合注释自己查看:
private boolean takeCard(Map<Integer, Integer> cardMap, Integer card) {
int count = cardMap.getOrDefault(card, 0);
//如果一张不剩,返回false
if (count == 0) {
return false;
}
//如果仅剩一张,则取完后删除,否则让数量-1
if (count == 1) {
cardMap.remove(card);
} else {
cardMap.put(card, count - 1);
}
return true;
}
最后,由于我没有遍历完所有的数组,而是在剩余数量为groupSize-1
时就跳出了,毕竟剩余的卡不足以组成长度为groupSize
的顺子。
而此时只要判断cardMap
是否还有卡,就可以知道是否能够把所有卡组成顺子了。
6、提交
7、咀嚼
首先,我们进行一次遍历,时间复杂度为O(N)。
之后,就是对卡片的排序,由于每张卡可能会出现多次,所以实际参与排序的数量会小于N,但由于数量无法控制,故还是认为排序时的时间复杂度为O(NlogN)。
之后的遍历,看似嵌套了一层,但实际上只是遍历了原来的数组中收起来的空间,实际上的时间复杂度是O(N)。
于是整体的时间复杂度为O(NlogN)。
空间复杂度为O(N)。
8、康康他人
由于官解和大牛们的思路基本都是这一套,所以不多赘述。
来都来了,就康我的吧!
9、总结
常规的哈希计数,不及格的贪心难度。
总之,今天虽是中等题,但全然没有中等的排名。
解散!祝大家周四愉快。