题目

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/hand-of-straights/

1.描述

Alice手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌数都是 groupSize ,并且由 groupSize 张连续的牌组成。
给你一个整数数组 hand, 其中 hand[i] 是第 i 张牌的值,和一个整数 groupSize 。如果她可能按照题给条件重新排列这些牌,返回 true 。否则,返回 false

2.示例

  • 示例 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 的组。

解题思路

此题可以使用排序+哈希表解决。
我们首先按照从小到大的顺序对手牌进行排序,然后从左往右一次遍历数组选出每个大小为groupSize的牌组,并使用哈希表将每张已使用过的牌进行标记。一旦无法满足牌组条件,即下一张可用牌牌值不是当前牌牌值加一,即返回false;如果选出了所有牌组,返回true

算法

1.计算手牌数m,如果m不是groupSize的整数倍,返回假;如果groupSize1,返回true
2.对手牌进行排序,初始化标记已使用手牌的哈希表hash
3.从第一张开始,依次选出每个牌组的牌:

  • 如果当前牌没有被使用过,选中当前牌作为当前牌组的第一张牌,并标记为cur
  • 从下一个位置开始,依次选出牌组的每一张牌,一张牌能作为牌组的下一张牌的条件是该牌没被使用过,并且牌值不等于当前牌,如果从左往右第一张可用牌的大小就超过了cur+1,则返回false
  • 如果满足牌组条件,更新已选牌cur为当前牌,将该牌标记为已使用,并更新下一张牌的可选初始位置为当前牌的下一位置。
  • 如果选牌过程中达到了手牌最末位置的下一位置,说明无法完成选牌,返回false
4.遍历完手牌,返回true

代码

class Solution {
public:
    bool isNStraightHand(vector<int>& hand, int groupSize) {
        int m = hand.size();//手牌数
        if(m % groupSize != 0) return false;//如果手牌数不是组牌数的整数倍,返回假
        if(groupSize == 1) return true;//如果组牌数是1,返回真
        sort(hand.begin(), hand.end());//对手牌排序
        vector<int> hash(m, 0);//哈希表,存储哪些手牌被用过了,键为下标,值为是否被用过
        for(int i = 0; i < m; ++i){//顺序遍历排序后的手牌
            if(hash[i]>0) continue;//如果手牌被用过了,跳出本次循环
            int cur = hand[i];//如果手牌没被用过,选中作为当前牌组的第一张牌
            int j;//标记当前正在选当前牌组的第几张牌
            int k = i + 1;//标记正在验证的手牌,依次往后遍历,验证该手牌是否可以作为当前牌组的下一张牌
            for(j = 1; j < groupSize; ++j){//依次选出当前牌组的各张牌
                while(k<m && (hash[k]>0 || hand[k]==cur)){
                    //在满足还未遍历到手牌最后一个位置的下一个位置的条件下,
                    //如果该手牌被用过了,或者该手牌的值等于上一张已选中手牌的值
                    //则遍历下一张手牌,否则跳出该循环
                    k++;
                } 
                //跳出循环后
                //如果此时已经选到了手牌最后一个位置的下一个位置
                //则停止当前牌组的选择工作,跳出循环
                if(k==m) break;
                //如果满足以上条件的手牌无法与已选手牌构成顺子,返回假
                if(hand[k]!=++cur) return false;
                //否则更新已选牌为当前牌
                cur = hand[k];
                //并且将其标记为已使用
                ++hash[k];
                //更新下一次选牌的开始位置为当前牌的下一个位置
                ++k;
                //继续选牌组的下一张牌
            }
            //判断循环的跳出方式,如果未选完当前牌组就跳出了,返回假
            if(j!=groupSize) return false;
        }
        //选完了所有牌组,返回真
        return true;
    }
};

复杂度分析

时间复杂度: O(mlogm)m为手牌数,排序的时间消耗为O(mlogm),选牌的时间消耗为O(m+((m/groupSize)*groupSize)),综合时间复杂度为O(mlogm)
空间复杂度: O(m)。哈希表的空间消耗。

更多知识内容分享:
力扣个人主页https://leetcode-cn.com/profile/articles/
CSDN个人主页https://blog.csdn.net/qq_39255924
牛客个人主页https://blog.nowcoder.net/newcoderthewarrior