【力扣时间】【846】【中等】一手顺子

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、总结

常规的哈希计数,不及格的贪心难度。
总之,今天虽是中等题,但全然没有中等的排名。

解散!祝大家周四愉快。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值