Facebook Pixel

3011. Find if Array Can Be Sorted

MediumBit ManipulationArraySorting
Leetcode Link

Problem Description

You have an array of positive integers where each element can be represented in binary form. The problem asks whether you can sort this array in ascending order using a specific type of swap operation.

The key constraint is that you can only swap two adjacent elements if they have the same number of set bits (1s in their binary representation). For example:

  • 5 in binary is 101 (has 2 set bits)
  • 3 in binary is 011 (has 2 set bits)
  • Since both have 2 set bits, you could swap them if they were adjacent

You can perform this swap operation as many times as needed, including zero times.

The goal is to determine if it's possible to sort the entire array in ascending order using only these restricted swaps. Return true if the array can be sorted this way, or false if it's impossible.

For example, if you have [8, 4, 2, 30, 15]:

  • 8 = 1000 (1 set bit)
  • 4 = 100 (1 set bit)
  • 2 = 10 (1 set bit)
  • 30 = 11110 (4 set bits)
  • 15 = 1111 (4 set bits)

Elements with the same bit count can be rearranged among themselves through adjacent swaps, but elements with different bit counts cannot pass each other. This creates "blocks" of elements that must maintain their relative order to other blocks.

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

Intuition

Since we can only swap adjacent elements with the same number of set bits, elements with different bit counts cannot "pass through" each other. This creates natural boundaries in the array - imagine the array being divided into blocks where each block contains elements with the same bit count.

Within each block, we can rearrange elements freely through adjacent swaps (like bubble sort). This means each block can be internally sorted. However, the blocks themselves must remain in their original relative positions.

For the array to be sortable, we need the following condition: when we sort each block internally, the resulting array should be globally sorted. This happens if and only if the maximum value in each block is less than or equal to the minimum value in the next block.

Think of it this way: if block A comes before block B, and block A's maximum value is greater than block B's minimum value, then these two values are "out of order" globally. Since they have different bit counts, they can never swap positions, making it impossible to sort the array.

The key insight is that we don't need to actually perform the swaps. We just need to:

  1. Group consecutive elements with the same bit count
  2. Find the minimum and maximum value in each group
  3. Check if the maximum of each group is less than or equal to the minimum of the next group

If this condition holds for all consecutive groups, the array can be sorted. Otherwise, it cannot.

Learn more about Sorting patterns.

Solution Approach

The solution uses a two-pointer technique to process the array in groups of elements with the same bit count.

We maintain several key variables:

  • pre_mx: Tracks the maximum value from all previously processed groups
  • i: Starting index of the current group
  • mi and mx: Minimum and maximum values in the current group

The algorithm works as follows:

  1. Initialize tracking variables: Set pre_mx = 0 to track the maximum value seen in previous groups, and start with i = 0.

  2. Process each group: For each position i, we:

    • Calculate the bit count of nums[i] using bit_count()
    • Initialize mi and mx with nums[i]
    • Use pointer j to find all consecutive elements with the same bit count
  3. Expand the current group: Starting from j = i + 1, we continue adding elements to the current group while they have the same bit count:

    while j < n and nums[j].bit_count() == cnt:
        mi = min(mi, nums[j])
        mx = max(mx, nums[j])
        j += 1
  4. Check the sorting condition: After identifying a complete group, we verify if pre_mx > mi. If true, it means the maximum value from previous groups is greater than the minimum value of the current group. This violates the sorting requirement, so we return False.

  5. Update for next iteration:

    • Update pre_mx = mx to track the new maximum value seen so far
    • Move i = j to start processing the next group
  6. Return result: If we process all groups without finding any violations, return True.

The time complexity is O(n) as we visit each element exactly once. The space complexity is O(1) since we only use a constant amount of extra variables.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the array [3, 16, 8, 4, 2] to see if it can be sorted.

First, let's identify the bit counts:

  • 3 = 11 (2 set bits)
  • 16 = 10000 (1 set bit)
  • 8 = 1000 (1 set bit)
  • 4 = 100 (1 set bit)
  • 2 = 10 (1 set bit)

Step 1: Process first group (i=0)

  • Start with nums[0] = 3, bit count = 2
  • Initialize: mi = 3, mx = 3
  • Check nums[1] = 16: bit count = 1 (different from 2)
  • Group ends at index 0
  • Group contains: [3] with min=3, max=3
  • Check condition: pre_mx (0) > mi (3)? No, continue
  • Update: pre_mx = 3, i = 1

Step 2: Process second group (i=1)

  • Start with nums[1] = 16, bit count = 1
  • Initialize: mi = 16, mx = 16
  • Check remaining elements:
    • nums[2] = 8: bit count = 1 (same), update mi = 8, mx = 16
    • nums[3] = 4: bit count = 1 (same), update mi = 4, mx = 16
    • nums[4] = 2: bit count = 1 (same), update mi = 2, mx = 16
  • Group contains: [16, 8, 4, 2] with min=2, max=16
  • Check condition: pre_mx (3) > mi (2)? Yes!
  • Return False

The array cannot be sorted because the element 3 (from the first group with 2 set bits) needs to come after 2 (from the second group with 1 set bit) in the sorted order. Since they have different bit counts, they cannot swap positions through adjacent swaps.

To visualize why this fails: if we could sort within groups, we'd have:

  • Group 1: [3] (already sorted)
  • Group 2: [2, 4, 8, 16] (sorted within group)
  • Result: [3, 2, 4, 8, 16] - not globally sorted!

For comparison, the array [8, 4, 2, 30, 15] would return True because:

  • Group 1 (1 bit): [8, 4, 2] β†’ sorted becomes [2, 4, 8], max=8
  • Group 2 (4 bits): [30, 15] β†’ sorted becomes [15, 30], min=15
  • Since 8 < 15, the condition is satisfied and the final result [2, 4, 8, 15, 30] is globally sorted.

Solution Implementation

1class Solution:
2    def canSortArray(self, nums: List[int]) -> bool:
3        """
4        Check if array can be sorted by swapping adjacent elements with same bit count.
5      
6        The algorithm groups consecutive elements with the same number of set bits,
7        then verifies that each group's minimum is greater than or equal to the
8        previous group's maximum.
9        """
10        previous_max = 0  # Track maximum value from previous group
11        index = 0
12        array_length = len(nums)
13      
14        # Process array in groups of consecutive elements with same bit count
15        while index < array_length:
16            # Get bit count of current element
17            current_bit_count = nums[index].bit_count()
18          
19            # Find the range of consecutive elements with same bit count
20            next_index = index + 1
21            group_min = nums[index]  # Track minimum in current group
22            group_max = nums[index]  # Track maximum in current group
23          
24            # Extend the group while next elements have same bit count
25            while next_index < array_length and nums[next_index].bit_count() == current_bit_count:
26                group_min = min(group_min, nums[next_index])
27                group_max = max(group_max, nums[next_index])
28                next_index += 1
29          
30            # Check if current group violates sorting order
31            # (previous group's max should not exceed current group's min)
32            if previous_max > group_min:
33                return False
34          
35            # Update previous max for next iteration
36            previous_max = group_max
37          
38            # Move to next group
39            index = next_index
40      
41        return True
42
1class Solution {
2    public boolean canSortArray(int[] nums) {
3        // Track the maximum value from the previous group
4        int previousMaximum = 0;
5        int currentIndex = 0;
6        int arrayLength = nums.length;
7      
8        // Process the array in groups where each group has the same bit count
9        while (currentIndex < arrayLength) {
10            // Get the bit count of the current element
11            int currentBitCount = Integer.bitCount(nums[currentIndex]);
12          
13            // Find the range of elements with the same bit count
14            int nextIndex = currentIndex + 1;
15            int currentGroupMinimum = nums[currentIndex];
16            int currentGroupMaximum = nums[currentIndex];
17          
18            // Extend the group while elements have the same bit count
19            while (nextIndex < arrayLength && Integer.bitCount(nums[nextIndex]) == currentBitCount) {
20                currentGroupMinimum = Math.min(currentGroupMinimum, nums[nextIndex]);
21                currentGroupMaximum = Math.max(currentGroupMaximum, nums[nextIndex]);
22                nextIndex++;
23            }
24          
25            // Check if the current group can be placed after the previous group
26            // The minimum of current group must be >= maximum of previous group
27            if (previousMaximum > currentGroupMinimum) {
28                return false;
29            }
30          
31            // Update the previous maximum for the next iteration
32            previousMaximum = currentGroupMaximum;
33          
34            // Move to the next group
35            currentIndex = nextIndex;
36        }
37      
38        return true;
39    }
40}
41
1class Solution {
2public:
3    bool canSortArray(vector<int>& nums) {
4        int previousGroupMax = 0;  // Maximum value from all previous groups
5        int currentIndex = 0;
6        int arraySize = nums.size();
7      
8        // Process array in groups where each group has same bit count
9        while (currentIndex < arraySize) {
10            // Get the bit count of current element (number of 1s in binary)
11            int currentBitCount = __builtin_popcount(nums[currentIndex]);
12          
13            // Find the range of consecutive elements with same bit count
14            int nextIndex = currentIndex + 1;
15            int currentGroupMin = nums[currentIndex];  // Minimum in current group
16            int currentGroupMax = nums[currentIndex];  // Maximum in current group
17          
18            // Extend the group while next elements have same bit count
19            while (nextIndex < arraySize && 
20                   __builtin_popcount(nums[nextIndex]) == currentBitCount) {
21                currentGroupMin = min(currentGroupMin, nums[nextIndex]);
22                currentGroupMax = max(currentGroupMax, nums[nextIndex]);
23                nextIndex++;
24            }
25          
26            // Check if current group can be placed after previous groups
27            // (all elements in previous groups must be <= all elements in current group)
28            if (previousGroupMax > currentGroupMin) {
29                return false;  // Cannot sort the array
30            }
31          
32            // Update maximum for next iteration
33            previousGroupMax = currentGroupMax;
34            currentIndex = nextIndex;
35        }
36      
37        return true;  // Array can be sorted
38    }
39};
40
1/**
2 * Determines if an array can be sorted by only swapping adjacent elements
3 * that have the same number of set bits in their binary representation
4 * @param nums - The input array of numbers to check
5 * @returns true if the array can be sorted, false otherwise
6 */
7function canSortArray(nums: number[]): boolean {
8    let previousMaxValue = 0;
9    const arrayLength = nums.length;
10  
11    // Process groups of consecutive elements with the same bit count
12    for (let i = 0; i < arrayLength; ) {
13        // Get the bit count of the current element
14        const currentBitCount = bitCount(nums[i]);
15      
16        // Find the range of consecutive elements with the same bit count
17        let j = i + 1;
18        let [minInGroup, maxInGroup] = [nums[i], nums[i]];
19      
20        // Extend the group while elements have the same bit count
21        while (j < arrayLength && bitCount(nums[j]) === currentBitCount) {
22            minInGroup = Math.min(minInGroup, nums[j]);
23            maxInGroup = Math.max(maxInGroup, nums[j]);
24            j++;
25        }
26      
27        // Check if this group violates the sorting constraint
28        // The minimum of current group must be >= maximum of previous group
29        if (previousMaxValue > minInGroup) {
30            return false;
31        }
32      
33        // Update the maximum value seen so far
34        previousMaxValue = maxInGroup;
35      
36        // Move to the next group
37        i = j;
38    }
39  
40    return true;
41}
42
43/**
44 * Counts the number of set bits (1s) in the binary representation of a number
45 * Uses Brian Kernighan's algorithm with bit manipulation optimizations
46 * @param i - The number to count bits for
47 * @returns The count of set bits
48 */
49function bitCount(i: number): number {
50    // Step 1: Count bits in groups of 2
51    i = i - ((i >>> 1) & 0x55555555);
52  
53    // Step 2: Count bits in groups of 4
54    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
55  
56    // Step 3: Count bits in groups of 8
57    i = (i + (i >>> 4)) & 0x0f0f0f0f;
58  
59    // Step 4: Count bits in groups of 16
60    i = i + (i >>> 8);
61  
62    // Step 5: Count bits in groups of 32
63    i = i + (i >>> 16);
64  
65    // Step 6: Mask to get the final count (max 32 bits = 0x3f)
66    return i & 0x3f;
67}
68

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums.

The algorithm uses a two-pointer approach with variables i and j. The outer while loop starts with i = 0 and continues while i < n. The inner while loop moves j forward to find consecutive elements with the same bit count. The key observation is that each element in the array is visited exactly once by the pointer j. When j reaches the end of a group with the same bit count, i jumps directly to j, skipping all elements that were already processed. Therefore, despite having nested loops, each element is processed only once, resulting in a linear time complexity of O(n).

Space Complexity: O(1).

The algorithm only uses a constant amount of extra space regardless of the input size. The variables used are:

  • pre_mx: stores the maximum value of the previous group
  • i, j, n: loop counters and array length
  • cnt: stores the bit count of the current group
  • mi, mx: stores the minimum and maximum values of the current group

All these variables occupy constant space that doesn't scale with the input size, hence the space complexity is O(1).

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

Common Pitfalls

1. Incorrectly Assuming Non-Adjacent Elements Can Be Compared

A common mistake is thinking that if two elements have the same bit count, they can be swapped regardless of their positions. This leads to incorrect solutions that try to sort all elements with the same bit count together, ignoring the adjacency requirement.

Incorrect approach:

# WRONG: Groups all elements by bit count, ignoring position
def canSortArray(nums):
    bit_groups = {}
    for num in nums:
        bits = num.bit_count()
        if bits not in bit_groups:
            bit_groups[bits] = []
        bit_groups[bits].append(num)
  
    # This doesn't consider that elements must be adjacent to swap

Why it fails: Elements with the same bit count that are separated by elements with different bit counts cannot be swapped. For example, in [3, 16, 8, 4, 2], the values 3 and 2 both have 2 set bits, but they cannot be swapped because they're separated by elements with 1 set bit.

2. Not Maintaining Proper Group Boundaries

Another pitfall is failing to correctly identify where one group ends and another begins, especially when updating the loop index.

Incorrect approach:

# WRONG: Increments index by 1 instead of jumping to next group
def canSortArray(nums):
    prev_max = 0
    i = 0
    while i < len(nums):
        # ... process group ...
        i += 1  # WRONG: Should be i = j

Solution: Always update the index to skip past the entire processed group:

while i < len(nums):
    # Find group boundary at j
    j = i + 1
    while j < len(nums) and nums[j].bit_count() == nums[i].bit_count():
        j += 1
    # ... check conditions ...
    i = j  # CORRECT: Jump to start of next group

3. Forgetting to Track Both Min and Max Within Groups

Some solutions only track the maximum value of each group but forget that we need both the minimum and maximum to properly validate the sorting condition.

Incorrect approach:

# WRONG: Only tracks maximum, missing minimum check
def canSortArray(nums):
    prev_max = 0
    for num in nums:
        if num < prev_max and different_bit_count:
            return False
        prev_max = max(prev_max, num)

Solution: Always track both minimum and maximum for each group:

# Track both min and max for current group
group_min = nums[i]
group_max = nums[i]
while j < len(nums) and nums[j].bit_count() == current_bit_count:
    group_min = min(group_min, nums[j])
    group_max = max(group_max, nums[j])
    j += 1

4. Edge Case: Single Element Groups

Failing to properly handle groups with only one element can lead to bugs in the min/max initialization or boundary checking.

Solution: Initialize both group_min and group_max with the first element of the group before entering the expansion loop. This ensures single-element groups are handled correctly without special casing.

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

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More