Facebook Pixel

1296. Divide Array in Sets of K Consecutive Numbers

Problem Description

You are given an array of integers nums and a positive integer k. Your task is to determine if it's possible to divide the entire array into groups where each group contains exactly k consecutive numbers.

For example, if you have nums = [1,2,3,3,4,4,5,6] and k = 4, you can divide it into two groups: [1,2,3,4] and [3,4,5,6], where each group has 4 consecutive numbers. Therefore, the answer would be true.

The key requirements are:

  • Each group must have exactly k numbers
  • The numbers in each group must be consecutive (like 1,2,3 or 5,6,7)
  • Every number in the array must be used exactly once
  • The order of numbers in the original array doesn't matter - you can rearrange them to form the groups

Return true if such a division is possible, otherwise return false.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about this problem step by step. If we need to form groups of k consecutive numbers, we should start from the smallest number available and try to build a group from there.

Why start from the smallest? Because if we have a smallest number x in our array, it can only be the starting point of a consecutive sequence. It cannot be in the middle or end of any sequence since there's no smaller number to come before it.

Once we identify that x must start a sequence, we know the sequence must be [x, x+1, x+2, ..., x+k-1]. We need to check if all these numbers exist in sufficient quantities.

The key insight is that we can use a greedy approach: always form sequences starting from the smallest available number. This works because:

  1. Every number must belong to exactly one group
  2. The smallest unused number can only start a new group
  3. Once we commit to starting a group with number x, the rest of the group is determined: [x, x+1, ..., x+k-1]

To implement this, we need to:

  • Count how many times each number appears (using a hash table/counter)
  • Process numbers in sorted order
  • When we encounter a number with count > 0, we try to form a complete group starting from it
  • For each group formed, we decrement the count of all k consecutive numbers

If at any point we can't complete a group (because a required consecutive number has count 0), we know it's impossible to divide the array as required.

Learn more about Greedy and Sorting patterns.

Solution Approach

Let's walk through the implementation step by step:

Step 1: Check Divisibility First, we check if len(nums) % k != 0. If the total number of elements isn't divisible by k, it's impossible to divide them into groups of size k, so we return false immediately.

Step 2: Count Frequencies We use a hash table (Counter in Python) to count the occurrences of each number in the array. This is stored in cnt = Counter(nums). For example, if nums = [1,2,3,3,4,4,5,6], then cnt = {1:1, 2:1, 3:2, 4:2, 5:1, 6:1}.

Step 3: Sort and Process We sort the array nums to process numbers in ascending order. This ensures we always start forming groups from the smallest available number.

Step 4: Form Consecutive Groups We iterate through the sorted array:

  • For each number x, we check if cnt[x] > 0 (meaning this number is still available to use)
  • If available, we try to form a group starting from x: [x, x+1, x+2, ..., x+k-1]
  • For each number y in the range from x to x + k - 1:
    • If cnt[y] == 0, it means we need number y to complete the group but it's not available, so we return false
    • Otherwise, we decrement cnt[y] by 1 to mark that we've used one occurrence of this number

Step 5: Return Result If we successfully process all numbers without returning false, it means we can divide the array into groups of k consecutive numbers, so we return true.

Example Walkthrough: For nums = [1,2,3,3,4,4,5,6] and k = 4:

  1. Length check: 8 % 4 = 0 โœ“
  2. Count: {1:1, 2:1, 3:2, 4:2, 5:1, 6:1}
  3. Process sorted array: [1,2,3,3,4,4,5,6]
  4. Start with 1 (cnt[1] = 1 > 0):
    • Form group [1,2,3,4]
    • Update counts: {1:0, 2:0, 3:1, 4:1, 5:1, 6:1}
  5. Continue with 3 (cnt[3] = 1 > 0):
    • Form group [3,4,5,6]
    • Update counts: {1:0, 2:0, 3:0, 4:0, 5:0, 6:0}
  6. All numbers used, return true

The time complexity is O(n log n) due to sorting, and space complexity is O(n) for the hash table.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with nums = [3,2,1,2,3,4,3,4,5,9,10,11] and k = 3.

Step 1: Initial Setup

  • Array length: 12, and 12 % 3 = 0 โœ“ (divisible by k, so we can proceed)
  • Count frequencies: {1:1, 2:2, 3:3, 4:2, 5:1, 9:1, 10:1, 11:1}
  • Sort the array: [1,2,2,3,3,3,4,4,5,9,10,11]

Step 2: Form First Group

  • Start with smallest number 1 (cnt[1] = 1 > 0)
  • Need to form group: [1, 2, 3]
  • Check and decrement:
    • cnt[1] = 1 โ†’ 0 โœ“
    • cnt[2] = 2 โ†’ 1 โœ“
    • cnt[3] = 3 โ†’ 2 โœ“
  • First group formed successfully

Step 3: Form Second Group

  • Continue processing, next available is 2 (cnt[2] = 1 > 0)
  • Need to form group: [2, 3, 4]
  • Check and decrement:
    • cnt[2] = 1 โ†’ 0 โœ“
    • cnt[3] = 2 โ†’ 1 โœ“
    • cnt[4] = 2 โ†’ 1 โœ“
  • Second group formed successfully

Step 4: Form Third Group

  • Next available is 3 (cnt[3] = 1 > 0)
  • Need to form group: [3, 4, 5]
  • Check and decrement:
    • cnt[3] = 1 โ†’ 0 โœ“
    • cnt[4] = 1 โ†’ 0 โœ“
    • cnt[5] = 1 โ†’ 0 โœ“
  • Third group formed successfully

Step 5: Form Fourth Group

  • Next available is 9 (cnt[9] = 1 > 0)
  • Need to form group: [9, 10, 11]
  • Check and decrement:
    • cnt[9] = 1 โ†’ 0 โœ“
    • cnt[10] = 1 โ†’ 0 โœ“
    • cnt[11] = 1 โ†’ 0 โœ“
  • Fourth group formed successfully

Result: All numbers used, all counts are 0. Return true.

The four groups formed are: [1,2,3], [2,3,4], [3,4,5], [9,10,11]

Note how the greedy approach works: we always start from the smallest available number, which forces us to use specific consecutive numbers. This ensures we don't miss any valid grouping opportunity.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def isPossibleDivide(self, nums: List[int], k: int) -> bool:
6        # Check if total number of elements is divisible by k
7        # If not, we cannot form groups of size k
8        if len(nums) % k != 0:
9            return False
10      
11        # Count frequency of each number in the array
12        frequency_map = Counter(nums)
13      
14        # Process numbers in ascending order
15        for num in sorted(nums):
16            # Skip if current number has already been used in previous groups
17            if frequency_map[num] > 0:
18                # Try to form a consecutive group starting from current number
19                for consecutive_num in range(num, num + k):
20                    # Check if we have enough of the consecutive number
21                    if frequency_map[consecutive_num] == 0:
22                        return False
23                    # Use one occurrence of this number for the current group
24                    frequency_map[consecutive_num] -= 1
25      
26        # All numbers have been successfully grouped
27        return True
28
1class Solution {
2    public boolean isPossibleDivide(int[] nums, int k) {
3        // Check if total number of elements is divisible by k
4        // If not, we cannot form groups of size k
5        if (nums.length % k != 0) {
6            return false;
7        }
8      
9        // Sort the array to process consecutive elements
10        Arrays.sort(nums);
11      
12        // Create a frequency map to count occurrences of each number
13        Map<Integer, Integer> frequencyMap = new HashMap<>();
14        for (int num : nums) {
15            frequencyMap.merge(num, 1, Integer::sum);
16        }
17      
18        // Iterate through sorted array to form consecutive groups
19        for (int num : nums) {
20            // Check if current number is still available (not used in previous groups)
21            if (frequencyMap.getOrDefault(num, 0) > 0) {
22                // Try to form a consecutive group starting from current number
23                // Group will be: [num, num+1, num+2, ..., num+k-1]
24                for (int consecutive = num; consecutive < num + k; consecutive++) {
25                    // Decrement the count for this consecutive number
26                    // merge() returns the new value after applying the function
27                    if (frequencyMap.merge(consecutive, -1, Integer::sum) < 0) {
28                        // If count becomes negative, the required number doesn't exist
29                        return false;
30                    }
31                }
32            }
33        }
34      
35        // All groups formed successfully
36        return true;
37    }
38}
39
1class Solution {
2public:
3    bool isPossibleDivide(vector<int>& nums, int k) {
4        // Check if total number of elements is divisible by k
5        // If not, we cannot form groups of size k
6        if (nums.size() % k != 0) {
7            return false;
8        }
9      
10        // Sort the array to process consecutive elements
11        sort(nums.begin(), nums.end());
12      
13        // Count frequency of each number
14        unordered_map<int, int> frequencyMap;
15        for (int num : nums) {
16            frequencyMap[num]++;
17        }
18      
19        // Iterate through sorted array to form consecutive groups
20        for (int num : nums) {
21            // Skip if current number has already been used in previous groups
22            if (frequencyMap.find(num) != frequencyMap.end()) {
23                // Try to form a group starting from current number
24                for (int consecutive = num; consecutive < num + k; consecutive++) {
25                    // Check if the consecutive number exists
26                    if (frequencyMap.find(consecutive) == frequencyMap.end()) {
27                        return false;  // Cannot form a valid group
28                    }
29                  
30                    // Decrement the count of the consecutive number
31                    frequencyMap[consecutive]--;
32                  
33                    // Remove from map if count becomes zero
34                    if (frequencyMap[consecutive] == 0) {
35                        frequencyMap.erase(consecutive);
36                    }
37                }
38            }
39        }
40      
41        // All numbers have been successfully grouped
42        return true;
43    }
44};
45
1/**
2 * Determines if an array can be divided into groups of k consecutive numbers
3 * @param nums - Array of integers to be divided
4 * @param k - Size of each group
5 * @returns true if the array can be divided into groups of k consecutive numbers, false otherwise
6 */
7function isPossibleDivide(nums: number[], k: number): boolean {
8    // Check if total number of elements is divisible by k
9    if (nums.length % k !== 0) {
10        return false;
11    }
12  
13    // Create a frequency map to count occurrences of each number
14    const frequencyMap = new Map<number, number>();
15    for (const num of nums) {
16        frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
17    }
18  
19    // Sort the array in ascending order
20    nums.sort((a, b) => a - b);
21  
22    // Try to form consecutive groups starting from the smallest available number
23    for (const startNum of nums) {
24        // Skip if this number has already been used in previous groups
25        if (frequencyMap.get(startNum)! > 0) {
26            // Try to form a group of k consecutive numbers starting from startNum
27            for (let currentNum = startNum; currentNum < startNum + k; currentNum++) {
28                // Check if the current number is available
29                if ((frequencyMap.get(currentNum) || 0) === 0) {
30                    return false;
31                }
32                // Decrement the count for the current number
33                frequencyMap.set(currentNum, frequencyMap.get(currentNum)! - 1);
34            }
35        }
36    }
37  
38    // All numbers have been successfully grouped
39    return true;
40}
41

Time and Space Complexity

Time Complexity: O(n ร— log n + n ร— k)

The time complexity consists of two main parts:

  • sorted(nums) takes O(n ร— log n) time where n is the length of the array
  • The nested loop iterates through each element in sorted order (n iterations), and for each element where cnt[x] > 0, it performs k operations to check and decrement consecutive elements, resulting in O(n ร— k) in the worst case

Since the reference answer states O(n ร— log n), this suggests that k is treated as a constant or small value compared to n. When k is much smaller than n, the sorting dominates and the complexity simplifies to O(n ร— log n).

Space Complexity: O(n)

The space complexity comes from:

  • The Counter object cnt which stores at most n key-value pairs (one for each unique element in nums), requiring O(n) space
  • The sorted list created by sorted(nums) also requires O(n) space

Therefore, the overall space complexity is O(n), which matches the reference answer.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Inefficient Repeated Sorting

The current implementation sorts the entire array but then checks frequency_map[num] > 0 for each element. This means we're iterating through many numbers that have already been consumed in previous group formations, leading to unnecessary iterations.

Problem Example: If nums = [1,1,1,2,2,2,3,3,3] and k = 3, after forming the first group [1,2,3], we still iterate through the remaining two 1's, two 2's, and two 3's in the sorted array, checking their frequencies each time.

Solution: Instead of sorting the original array, sort only the unique keys from the frequency map and process each unique number only once:

def isPossibleDivide(self, nums: List[int], k: int) -> bool:
    if len(nums) % k != 0:
        return False
  
    frequency_map = Counter(nums)
  
    # Sort only unique numbers instead of the entire array
    for num in sorted(frequency_map.keys()):
        count = frequency_map[num]
        if count > 0:
            # Form 'count' groups starting from this number
            for consecutive_num in range(num, num + k):
                if frequency_map[consecutive_num] < count:
                    return False
                frequency_map[consecutive_num] -= count
  
    return True

Pitfall 2: Not Handling Multiple Groups Starting from Same Number

When a number appears multiple times and serves as the starting point for multiple groups, the code processes each occurrence separately in the sorted array. This can be confusing and inefficient.

Problem Example: If nums = [1,1,2,2,3,3] and k = 3, the number 1 appears twice and both occurrences need to start separate groups [1,2,3].

Better Approach: Process all groups starting from the same number together by using the frequency count:

def isPossibleDivide(self, nums: List[int], k: int) -> bool:
    if len(nums) % k != 0:
        return False
  
    frequency_map = Counter(nums)
  
    while frequency_map:
        # Find the minimum number available
        min_num = min(frequency_map.keys())
        min_count = frequency_map[min_num]
      
        # Form min_count groups starting from min_num
        for i in range(k):
            current_num = min_num + i
            if current_num not in frequency_map or frequency_map[current_num] < min_count:
                return False
            frequency_map[current_num] -= min_count
            if frequency_map[current_num] == 0:
                del frequency_map[current_num]
  
    return True

This approach processes all groups starting from the smallest available number at once, making the logic clearer and potentially more efficient for datasets with many duplicate values.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More