class Solution:
    def isNStraightHand(self, hand: List[int], W: int) -> bool:
        """
        0. edge case  len = m *k 
        1. cnt freq 
        2. iterate sorted num,   num,num+1, .. num+W-1 freq -= cnt from the most front number 
        3. if num == 0 but val before num  != 0: return False 
        4. return True   
        tc O(MlgM+N+ M*W) M: number of uniqe val  N: len of hand
        sc O(M)  
        """
        if W == 1: return True
        if len(hand) %W != 0:
            return False 
        c = collections.Counter(hand)
        for x in sorted(c):
            if c[x] > 0 :
                for j in range(W)[::-1]:
                    c[x+j] -= c[x]
                    if c[x+j] < 0:
                        return False
        return True 

Follow up: (1) print all the straight (2) if W is huge, should we cut off card one by one?

PQ solution

# tc O(MlgM + N )
class Solution:
    def isNStraightHand(self, hand: List[int], W: int) -> bool:
        c = collections.Counter(hand)
        q = collections.deque()
        last_checked_val, opened_cnt = -1,0
        for x in sorted(c):
            # smallest number's cnt > next val    next val is not consecutive and previous value still have cnt 
            if opened_cnt > c[x] or            opened_cnt >0 and x > last_checked_val +1 :return False
            q.append(c[x]-opened_cnt)
            last_checked_val= x # mark last visited val and last visited cnt  
            opened_cnt  = c[x]  
            if len(q) == W:
                opened_cnt -= q.popleft()
        return opened_cnt == 0 

optimization O(N) ?????????? [refer] (https://leetcode.com/problems/hand-of-straights/discuss/137794/Python-true-O(N)-solution)

from collections import Counter
class Solution:
    def isNStraightHand(self, hand: List[int], W: int) -> bool:
        if W == 0:
            return True 
        if len(hand) % W != 0:
            return False
        
        c = Counter(hand)
        while c :
            card,cnt  = c.popitem()
            c[card] = cnt
            # get smallest car for the grough where card is in 
            min_card = card
            while min_card in c:
                min_card -= 1
            min_card += 1
            
            
            cur_card = min_card
            opened = 0 
            open_start = {}
            while cur_card in c:
                cnt = c.pop(cur_card)
                if cnt < opened :
                    return False 
                open_start[cur_card] = cnt - opened 
                closed = open_start.get(cur_card-W+1,0)
                opened += open_start[cur_card] - closed 
                cur_card += 1
                
            if opened > 0:
                return False
        return True