1356. Sort Integers by The Number of 1 Bits
Problem Description
You are given an integer array arr
. Your task is to sort this array based on two criteria:
- Primary criterion: Sort the integers in ascending order by the number of
1
s in their binary representation - Secondary criterion: If two or more integers have the same number of
1
s in their binary representation, sort them in ascending order by their numerical value
Return the sorted array.
For example, if we have numbers like 2
(binary: 10
, one 1
), 3
(binary: 11
, two 1
s), and 4
(binary: 100
, one 1
), we would first group them by the count of 1
s:
- Numbers with one
1
:2
and4
- Numbers with two
1
s:3
Within each group, we sort by value. So the final order would be [2, 4, 3]
because:
2
comes first (one1
, smaller value)4
comes second (one1
, larger value than2
)3
comes last (two1
s)
The solution uses Python's sorted()
function with a custom key that returns a tuple (x.bit_count(), x)
. The bit_count()
method counts the number of 1
s in the binary representation, and by including x
itself as the second element of the tuple, Python automatically handles the secondary sorting by numerical value when the bit counts are equal.
Intuition
When we need to sort elements based on multiple criteria, we can leverage Python's built-in sorting capabilities which naturally handle tuple comparisons. The key insight is that Python compares tuples element by element from left to right - it first compares the first elements, and only if they're equal does it move on to compare the second elements.
This problem asks us to sort by two criteria: the count of 1
s in binary representation (primary) and the numerical value itself (secondary). We can represent each number as a tuple where:
- The first element is the bit count (number of
1
s) - The second element is the number itself
For example, the number 5
(binary: 101
) would be represented as (2, 5)
because it has two 1
s. The number 8
(binary: 1000
) would be (1, 8)
with one 1
.
When Python sorts these tuples, it naturally achieves what we want:
(1, 8)
comes before(2, 5)
because1 < 2
(fewer bits)(2, 3)
comes before(2, 5)
because when the first elements are equal (2 == 2
), it compares the second elements (3 < 5
)
The elegant part of this solution is using x.bit_count()
which directly gives us the count of 1
s in the binary representation, eliminating the need to manually convert to binary and count. By creating the key as (x.bit_count(), x)
, we let Python's natural tuple sorting behavior handle all the complexity for us in a single line of code.
Learn more about Sorting patterns.
Solution Approach
The solution uses Python's custom sorting capability to solve this problem efficiently. Here's how the implementation works:
Algorithm: Custom Sorting with Lambda Function
The core of the solution is the single line:
return sorted(arr, key=lambda x: (x.bit_count(), x))
Let's break down each component:
-
sorted()
function: Python's built-in sorting function that creates a new sorted list from the input array. It accepts akey
parameter that determines how elements are compared. -
Lambda function as key:
lambda x: (x.bit_count(), x)
- This creates an anonymous function that takes each element
x
from the array - Returns a tuple with two values for sorting comparison
- This creates an anonymous function that takes each element
-
The sorting tuple
(x.bit_count(), x)
:- First element:
x.bit_count()
- counts the number of1
s in the binary representation ofx
- Second element:
x
- the original number itself
- First element:
How the sorting works:
When Python sorts using this key function, for each number in the array:
- It applies the lambda function to get a tuple
- Sorts all tuples lexicographically (comparing first elements, then second if first are equal)
- Returns the original numbers in the sorted order
Example walkthrough:
For array [0, 1, 2, 3, 4, 5, 6, 7, 8]
:
0
β(0, 0)
(binary:0
, zero1
s)1
β(1, 1)
(binary:1
, one1
)2
β(1, 2)
(binary:10
, one1
)3
β(2, 3)
(binary:11
, two1
s)4
β(1, 4)
(binary:100
, one1
)- And so on...
After sorting these tuples, we get the order: [0, 1, 2, 4, 8, 3, 5, 6, 7]
Time Complexity: O(n log n)
due to the sorting operation
Space Complexity: O(n)
for the sorted array creation
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 a small example with the array [7, 2, 3, 9, 4]
.
Step 1: Convert each number to its sorting key (bit_count, value)
For each number, we calculate the bit count and create a tuple:
7
β binary:111
β has 3 ones β key:(3, 7)
2
β binary:10
β has 1 one β key:(1, 2)
3
β binary:11
β has 2 ones β key:(2, 3)
9
β binary:1001
β has 2 ones β key:(2, 9)
4
β binary:100
β has 1 one β key:(1, 4)
Step 2: Sort by these tuple keys
Now we sort these tuples lexicographically:
(1, 2)
- 1 bit set, value 2(1, 4)
- 1 bit set, value 4(2, 3)
- 2 bits set, value 3(2, 9)
- 2 bits set, value 9(3, 7)
- 3 bits set, value 7
Notice how:
- Numbers with 1 bit come first:
2
and4
(sorted by value: 2 < 4) - Numbers with 2 bits come next:
3
and9
(sorted by value: 3 < 9) - Numbers with 3 bits come last: only
7
Step 3: Extract the sorted numbers
The final sorted array is: [2, 4, 3, 9, 7]
This demonstrates both sorting criteria:
- Primary: Numbers are grouped by bit count (1-bit group, then 2-bit group, then 3-bit group)
- Secondary: Within each group, numbers are in ascending order (2 before 4, and 3 before 9)
Solution Implementation
1class Solution:
2 def sortByBits(self, arr: List[int]) -> List[int]:
3 """
4 Sort an array by the number of 1 bits in ascending order.
5 If two numbers have the same number of 1 bits, sort them by value in ascending order.
6
7 Args:
8 arr: List of integers to be sorted
9
10 Returns:
11 List of integers sorted by bit count, then by value
12 """
13 # Sort the array using a custom key function
14 # Primary key: count of 1 bits in binary representation
15 # Secondary key: the number itself (for tie-breaking)
16 return sorted(arr, key=lambda num: (num.bit_count(), num))
17
1class Solution {
2 public int[] sortByBits(int[] arr) {
3 int arrayLength = arr.length;
4
5 // Encode each number: combine original value with bit count
6 // Formula: encodedValue = originalValue + (bitCount * 100000)
7 // This allows sorting by bit count first, then by value
8 for (int i = 0; i < arrayLength; ++i) {
9 int originalValue = arr[i];
10 int bitCount = Integer.bitCount(originalValue);
11 arr[i] = originalValue + (bitCount * 100000);
12 }
13
14 // Sort the encoded array
15 // Numbers with fewer bits will have smaller encoded values
16 // Among numbers with same bit count, smaller numbers come first
17 Arrays.sort(arr);
18
19 // Decode each number back to its original value
20 // Extract original value using modulo operation
21 for (int i = 0; i < arrayLength; ++i) {
22 arr[i] = arr[i] % 100000;
23 }
24
25 return arr;
26 }
27}
28
1class Solution {
2public:
3 vector<int> sortByBits(vector<int>& arr) {
4 // Encode each number by adding (bit count * 100000) to create a composite value
5 // This allows sorting by bit count first, then by the original value
6 for (int& value : arr) {
7 int bitCount = __builtin_popcount(value); // Count number of 1-bits
8 value += bitCount * 100000; // Encode: new_value = original + (bit_count * 100000)
9 }
10
11 // Sort the encoded values
12 // Values with fewer bits will have smaller encoded values
13 // Among values with same bit count, smaller original values come first
14 sort(arr.begin(), arr.end());
15
16 // Decode each number back to its original value using modulo operation
17 for (int& value : arr) {
18 value %= 100000; // Extract original value by removing the bit count encoding
19 }
20
21 return arr;
22 }
23};
24
1/**
2 * Sorts an array of integers by the number of 1s in their binary representation.
3 * If two numbers have the same number of 1s, they are sorted in ascending order.
4 * @param arr - The input array of integers to sort
5 * @returns A new sorted array based on bit count criteria
6 */
7function sortByBits(arr: number[]): number[] {
8 /**
9 * Counts the number of 1s in the binary representation of a number
10 * using Brian Kernighan's algorithm.
11 * @param num - The number to count 1s for
12 * @returns The count of 1s in the binary representation
13 */
14 const countOnes = (num: number): number => {
15 let bitCount = 0;
16
17 // Keep removing the rightmost set bit until number becomes 0
18 while (num) {
19 // Clear the rightmost set bit
20 num &= num - 1;
21 bitCount++;
22 }
23
24 return bitCount;
25 };
26
27 // Sort array with custom comparator:
28 // 1. Primary sort by number of 1s in binary representation
29 // 2. Secondary sort by actual value if bit counts are equal
30 return arr.sort((a, b) => {
31 const bitCountDifference = countOnes(a) - countOnes(b);
32
33 // If bit counts are equal, sort by actual values
34 // Otherwise, sort by bit count difference
35 return bitCountDifference || a - b;
36 });
37}
38
Time and Space Complexity
Time Complexity: O(n log n)
The time complexity is dominated by the sorted()
function, which uses Timsort algorithm in Python. The sorting operation has a time complexity of O(n log n)
where n
is the length of the input array arr
.
For each element during sorting, we need to:
- Calculate
x.bit_count()
: This operation takesO(log x)
time in the worst case, wherex
is the value of the element. However, since we're dealing with fixed-size integers (typically 32-bit or 64-bit), this can be consideredO(1)
. - Create the tuple
(x.bit_count(), x)
for comparison:O(1)
Since the bit counting operation is O(1)
for fixed-size integers and is performed for each comparison during sorting, the overall time complexity remains O(n log n)
.
Space Complexity: O(n)
The space complexity consists of:
- The
sorted()
function creates a new list containing alln
elements:O(n)
- The sorting algorithm (Timsort) uses additional space for its operations:
O(n)
in the worst case - The lambda function and temporary tuples created during comparisons use
O(1)
space
Therefore, the total space complexity is O(n)
where n
is the length of the array arr
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Manual Bit Counting Instead of Built-in Methods
A common pitfall is implementing custom bit counting logic when Python provides efficient built-in methods. Developers might write:
# Inefficient manual approach
def count_bits(n):
count = 0
while n:
count += n & 1
n >>= 1
return count
return sorted(arr, key=lambda x: (count_bits(x), x))
Why it's problematic:
- More code to maintain and debug
- Potentially slower than optimized built-in methods
- Higher chance of bugs (e.g., not handling negative numbers correctly)
Solution: Use bit_count()
for Python 3.10+ or bin(x).count('1')
for earlier versions:
# Python 3.10+
return sorted(arr, key=lambda x: (x.bit_count(), x))
# Python < 3.10
return sorted(arr, key=lambda x: (bin(x).count('1'), x))
2. Forgetting to Handle Negative Numbers
The problem statement doesn't explicitly mention negative numbers, but if they're present in the input, the behavior might be unexpected:
# Negative numbers have different binary representations -1 # In two's complement: all bits are 1 -2 # Binary representation depends on system architecture
Solution: Either validate input or handle negative numbers explicitly:
def sortByBits(self, arr: List[int]) -> List[int]:
# Option 1: Filter out negatives if not allowed
if any(x < 0 for x in arr):
raise ValueError("Negative numbers not supported")
# Option 2: Handle negatives by using absolute value for bit counting
return sorted(arr, key=lambda x: (bin(abs(x)).count('1'), x))
3. Modifying the Original Array Instead of Returning a New One
Some developers might use arr.sort()
instead of sorted(arr)
:
# This modifies the original array arr.sort(key=lambda x: (x.bit_count(), x)) return arr # Original array is modified
Why it matters:
- The problem asks to "return the sorted array" which typically implies creating a new array
- Modifying the input can cause unexpected side effects if the caller expects the original array to remain unchanged
Solution: Use sorted()
to create a new sorted list:
return sorted(arr, key=lambda x: (x.bit_count(), x))
4. Not Considering Edge Cases
Failing to handle edge cases like empty arrays or single-element arrays:
# Potential issues with edge cases
if not arr: # Empty array
return []
if len(arr) == 1: # Single element
return arr
Solution: The current implementation with sorted()
naturally handles these cases, but it's good practice to document expected behavior:
def sortByBits(self, arr: List[int]) -> List[int]:
"""
Sort array by bit count then value.
Handles empty arrays and single elements correctly.
"""
# sorted() handles empty and single-element arrays correctly
return sorted(arr, key=lambda x: (x.bit_count(), x))
5. Performance Optimization Misconception
Trying to optimize by caching bit counts when it's unnecessary:
# Over-engineered solution
bit_cache = {}
for num in arr:
bit_cache[num] = num.bit_count()
return sorted(arr, key=lambda x: (bit_cache[x], x))
Why it's unnecessary:
- For typical array sizes, the overhead of creating and accessing the cache might not provide benefits
bit_count()
is already highly optimized- Adds complexity without significant performance gain
Solution: Keep it simple unless profiling shows a real bottleneck:
return sorted(arr, key=lambda x: (x.bit_count(), x))
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
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!