• LeetCode846.一手顺子


     LeetCode-846.一手顺子

      题目链接(微信打开):https://mp.weixin.qq.com/s/RWzrpg1Q4Dke7UNR4T34PA

    一、题目描述

      Alice有一手牌:整数数组hand,需要将它分组,问是否可以将其分成每组牌数都是groupSize张,且每组牌数的数字需要连续。可以的话输出:true,否则:false。

    示例 1:

    输入:hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
    输出:true
    解释:Alice 手中的牌可以被重新排列为 [1,2,3],[2,3,4],[6,7,8]。

    示例 2:

    输入:hand = [1,2,3,4,5], groupSize = 4
    输出:false
    解释:Alice 手中的牌无法被重新排列成几个大小为 4 的组。

    注意:

    • 1 <= hand.length <= 104

    • 0 <= hand[i] <= 109

    • 1 <= groupSize <= hand.length

    二、题目分析

      先说说自己能想到的和不能想到的一些问题吧~~~

    (I)能想到的

    这个hand数组能不能分成每组为:groupSize个数,首要条件需要满足:

    1、hand的个数能被groupSize 除尽,即:

    hand.length mod groupSize == 0

    2、需要对整个hand数组,进行从小到大的排序

    3、要统计每个数字的出现次数

      用另一个数组cnt,cnt[hand[i]] 表示hand[i] 这个数的出现次数。比如,未对数组hand排序前,hand[2],hand[7],hand[11],hand[18]都是数字3,那么cnt[3]=4。

    4、当某个hand[i]能分成一对顺子后,这个hand[i]要被剔除,然后出现次数要递减1。

      拿上面的示例2来说,没成顺子前应得到次数数组:

    cnt[1]= 1
    cnt[2]= 2
    cnt[3]= 2
    cnt[4]= 1
    cnt[6]= 1
    cnt[7]= 1
    cnt[8]= 1

     当把1,2,3成一对顺子后,次数数组变成:

    cnt[1]= 0    #成顺子,次数减去1
    cnt[2]= 1    #成顺子,次数减去1
    cnt[3]= 1    #成顺子,次数减去1
    cnt[4]= 1
    cnt[6]= 1
    cnt[7]= 1
    cnt[8]= 1

    5、最后就是成顺子的一些组合,数字有重叠的是情况1~3,数字不重叠的是情况4

    (II)不能想到的

      上面都是些零碎的分析,如何窜代码,成了我一道坎。

      主要体现在:

    (1)当从hand数组拿走一对groupSize牌数后,次数数组相应的递减1,之后如何保证hand数组,最终剩下的牌能不能刚好都成顺子;

    (2)这个位置移动怎么移动,例如上面图的情况1,需要分成每组groupSize=2的顺子,当从中拿走1、2成顺子后,下一个顺子位置是从3、4,还是从1、2去拿,如果从3,4去做顺子,怎么折返回去拿1,2。然后就被自己搞混了。

     

    三、最终解析

      后来我看了题解,发现没想象中复杂!以下摘自题解:

    假设尚未分组的牌中,最小的数字是 x,则如果存在符合要求的分组方式,x 一定是某个组中的最小数字(否则 x 不属于任何一个组,不符合每张牌都必须被分到某个组),该组的数字范围是 [x,x+groupSize−1]。
    再将 x 到 x+groupSize−1 的 groupSize 张牌分成一个组之后,继续使用贪心的策略对剩下的牌分组,直到所有的牌分组结束或者无法完成分组。
    如果在分组过程中发现从最小数字开始的连续 groupSize 个数字中有不存在的数字,则无法完成分组

      对第一个疑问,其实是我顾虑太多了。首先,hand数组能被groupSize除得尽,如果都能成顺子配对的话,是不会存在多余的数成不了顺子的。

      第二个疑问:怎么移动下一对顺子的位置。排完序依次顺着来,直到次数数组递减到0,我们再移动位置就好了。

      刚前面提到情况1的图,如果1、2成顺子,下一个顺子位置应从1、2去组顺子,而不是直接拿3,4去组。 因为cnt[1]和cnt[2]还没成0。

      假设n表示hand数组的长度,伪代码如下:

    #1、声明一个次数数组
    cnt[1~n] = 0
    
    #2、输入hand[1~n]的同时,统计每个整数出现的次数,注意,有可能某些cnt[x]会为0
    cnt[hand[i]] ++     #i的取值为:1~n
    
    #3、对hand[1~n]从小到大排序
    sort(hand, hand+n)
    
    #4、判断是否能被n除尽,不能则表示分不了每组为groupSize的顺子,返回False
    if (n mod groupSize != 0)
        return False
    
    #5、遍历hand,是否能恰好分组:每组都为顺子
    ##表示下一次移动的位置
    for (i=1; i <= n; i++)
        #刚好次数被递减到0,或者cnt本来就为0,跳到下一个需要比较的位置
        if (cnt[hand[i]] == 0)
            continue
    
        ##表示顺子的取数,是相对hand[i]去取的
        for j in range(hand[i], hand[i]+groupSize-1)
            #如果组不了顺子,返回False
            if (cnt[j] == 0)
                return False
            #能组成顺子,次数减去1
            cnt[j]--

    #全部数字,确认过都能组成顺子
    return True

         最后,感叹下,python的代码确实写下来比其他代码要短很多~~~

      还有,时间复杂度和空间复杂度,大家请参考微信的分析。。。

  • 相关阅读:
    最小生成树Prim算法
    哈夫曼树与哈夫曼编码
    二叉树的非递归遍历
    浅谈C++中指针和引用的区别
    poj2406 Power Strings
    (收藏)KMP算法的前缀next数组最通俗的解释
    HDU 1556 Color the ball
    Floyd算法
    最短路Dijkstra和Flyod
    编程中无穷大常量的设定技巧
  • 原文地址:https://www.cnblogs.com/windysai/p/16062553.html
Copyright © 2020-2023  润新知