3011. Find if Array Can Be Sorted
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 is101
(has 2 set bits)3
in binary is011
(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.
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:
- Group consecutive elements with the same bit count
- Find the minimum and maximum value in each group
- 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 groupsi
: Starting index of the current groupmi
andmx
: Minimum and maximum values in the current group
The algorithm works as follows:
-
Initialize tracking variables: Set
pre_mx = 0
to track the maximum value seen in previous groups, and start withi = 0
. -
Process each group: For each position
i
, we:- Calculate the bit count of
nums[i]
usingbit_count()
- Initialize
mi
andmx
withnums[i]
- Use pointer
j
to find all consecutive elements with the same bit count
- Calculate the bit count of
-
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
-
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 returnFalse
. -
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
- Update
-
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 EvaluatorExample 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), updatemi = 8
,mx = 16
nums[3] = 4
: bit count = 1 (same), updatemi = 4
,mx = 16
nums[4] = 2
: bit count = 1 (same), updatemi = 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 groupi
,j
,n
: loop counters and array lengthcnt
: stores the bit count of the current groupmi
,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.
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!