3551. Minimum Swaps to Sort by Digit Sum
Problem Description
You are given an array nums
containing distinct positive integers. Your task is to sort this array based on a specific rule and determine the minimum number of swaps needed to achieve this sorted order.
The sorting rule works as follows:
- Calculate the sum of digits for each number (e.g., for 123, the digit sum is 1 + 2 + 3 = 6)
- Sort the numbers in increasing order based on their digit sums
- If two numbers have the same digit sum, place the smaller number first
A swap means exchanging the values at two different positions in the array. You need to find the minimum number of such swaps required to transform the original array into the sorted arrangement.
For example, if nums = [312, 123, 456]
:
- Digit sum of 312 = 3 + 1 + 2 = 6
- Digit sum of 123 = 1 + 2 + 3 = 6
- Digit sum of 456 = 4 + 5 + 6 = 15
Since 312 and 123 have the same digit sum (6), we place 123 before 312 (smaller value first). The sorted order would be [123, 312, 456]
.
The solution uses a cycle detection approach to find the minimum swaps. It first creates the target sorted arrangement, then maps each element to its target position. By identifying cycles in the permutation (where elements need to rotate positions), it calculates that each cycle of length k
requires k-1
swaps. The total minimum swaps equals n - (number of cycles)
, where n
is the array length.
Intuition
The key insight is recognizing that finding the minimum number of swaps to sort an array is fundamentally a problem about permutation cycles.
Think of it this way: we have the current array and we know where each element should end up in the sorted array. Each element wants to move to its target position. When we trace these movements, we discover cycles - chains of elements where element A needs to go to B's position, B needs to go to C's position, and eventually some element needs to go to A's position, forming a closed loop.
For example, if element at index 0 needs to go to index 2, element at index 2 needs to go to index 4, and element at index 4 needs to go back to index 0, we have a cycle of length 3. To arrange these 3 elements correctly, we need exactly 2 swaps (one less than the cycle length).
Why k-1
swaps for a cycle of length k
? Consider a simple cycle: A β B β C β A. We can fix this with 2 swaps:
- First swap A and B: now B is in the right place
- Then swap A (now in B's old position) and C: now all three are correct
This pattern holds for any cycle length. Each swap fixes one element's position permanently, and the last element automatically falls into place when we've done k-1
swaps.
The beautiful realization is that the total minimum swaps equals n - (number of cycles)
. Each cycle of length k
contributes k
elements but needs k-1
swaps. If we have c
cycles covering all n
elements, we need (kβ-1) + (kβ-1) + ... + (kc-1) = n - c
swaps total.
The solution first determines the target sorted order by computing digit sums, then maps each element to where it should go. By marking visited elements while traversing cycles, we count the total number of cycles and derive our answer.
Learn more about Sorting patterns.
Solution Approach
The implementation follows these steps to find the minimum number of swaps:
Step 1: Calculate digit sums
def f(x: int) -> int:
s = 0
while x:
s += x % 10
x //= 10
return s
This helper function computes the sum of digits for any number by repeatedly extracting the last digit using modulo 10 and integer division.
Step 2: Create the sorted target arrangement
arr = sorted((f(x), x) for x in nums)
We create tuples of (digit_sum, value)
for each number and sort them. Python's sort naturally handles our requirements: it sorts by digit sum first, and when digit sums are equal, it sorts by the actual value (smaller first).
Step 3: Build position mapping
d = {a[1]: i for i, a in enumerate(arr)}
We create a dictionary that maps each value to its target index in the sorted array. For each value in the original array, we can now look up where it should be positioned.
Step 4: Count cycles using graph traversal
ans = n
vis = [False] * n
for i in range(n):
if not vis[i]:
ans -= 1
j = i
while not vis[j]:
vis[j] = True
j = d[nums[j]]
- We initialize
ans = n
(total elements) and will subtract the number of cycles found - The
vis
array tracks which positions we've already processed - For each unvisited position
i
, we start tracing a cycle:- Mark the current position as visited
- Look up where the current element should go:
d[nums[j]]
- Continue following this chain until we return to a visited position (completing the cycle)
- Each time we start a new cycle (when
vis[i]
is False), we decrementans
by 1
The final answer is n - (number of cycles)
, which gives us the minimum swaps needed.
Time Complexity: O(n log n)
for sorting, plus O(n)
for cycle detection
Space Complexity: O(n)
for the mapping dictionary and visited array
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 trace through the algorithm with nums = [312, 123, 456]
:
Step 1: Calculate digit sums
- 312: digit sum = 3 + 1 + 2 = 6
- 123: digit sum = 1 + 2 + 3 = 6
- 456: digit sum = 4 + 5 + 6 = 15
Step 2: Create sorted target arrangement
Creating tuples: [(6, 312), (6, 123), (15, 456)]
After sorting: [(6, 123), (6, 312), (15, 456)]
Target array: [123, 312, 456]
Step 3: Build position mapping
d = {123: 0, 312: 1, 456: 2}
This tells us:
- 123 should be at index 0
- 312 should be at index 1
- 456 should be at index 2
Step 4: Count cycles
Initial state: nums = [312, 123, 456]
, vis = [False, False, False]
, ans = 3
Cycle detection process:
Starting at index 0:
- Current element: 312
- Where should 312 go?
d[312] = 1
- Mark index 0 as visited:
vis = [True, False, False]
- Move to index 1
At index 1:
- Current element: 123
- Where should 123 go?
d[123] = 0
- Mark index 1 as visited:
vis = [True, True, False]
- Move to index 0
- Index 0 is already visited β cycle complete!
- This cycle has elements at positions 0 and 1
- Decrement ans:
ans = 3 - 1 = 2
Starting at index 2:
- Current element: 456
- Where should 456 go?
d[456] = 2
- Mark index 2 as visited:
vis = [True, True, True]
- Move to index 2
- Index 2 is already visited β cycle complete!
- This is a self-loop (element already in correct position)
- Decrement ans:
ans = 2 - 1 = 1
Result: We found 2 cycles, so minimum swaps = 3 - 2 = 1
Verification: Indeed, we only need 1 swap (swap positions 0 and 1) to transform [312, 123, 456]
into [123, 312, 456]
.
Solution Implementation
1class Solution:
2 def minSwaps(self, nums: List[int]) -> int:
3 def calculate_digit_sum(number: int) -> int:
4 """Calculate the sum of digits of a given number."""
5 digit_sum = 0
6 while number:
7 digit_sum += number % 10
8 number //= 10
9 return digit_sum
10
11 n = len(nums)
12
13 # Create array of tuples (digit_sum, original_value) and sort by digit sum
14 sorted_pairs = sorted((calculate_digit_sum(num), num) for num in nums)
15
16 # Create a mapping from original value to its target position after sorting
17 value_to_target_position = {pair[1]: index for index, pair in enumerate(sorted_pairs)}
18
19 # Initialize answer as total elements (we'll subtract number of cycles)
20 min_swaps_needed = n
21 visited = [False] * n
22
23 # Find all cycles in the permutation
24 for i in range(n):
25 if not visited[i]:
26 # Found a new cycle, subtract 1 from answer (cycle of length k needs k-1 swaps)
27 min_swaps_needed -= 1
28 current_position = i
29
30 # Traverse the entire cycle and mark all positions as visited
31 while not visited[current_position]:
32 visited[current_position] = True
33 # Move to the target position of the current element
34 current_position = value_to_target_position[nums[current_position]]
35
36 return min_swaps_needed
37
1class Solution {
2 public int minSwaps(int[] nums) {
3 int arrayLength = nums.length;
4
5 // Create a 2D array to store [digitSum, originalValue] pairs
6 int[][] digitSumValuePairs = new int[arrayLength][2];
7 for (int i = 0; i < arrayLength; i++) {
8 digitSumValuePairs[i][0] = f(nums[i]); // Calculate digit sum
9 digitSumValuePairs[i][1] = nums[i]; // Store original value
10 }
11
12 // Sort array by digit sum first, then by value if digit sums are equal
13 Arrays.sort(digitSumValuePairs, (a, b) -> {
14 if (a[0] != b[0]) {
15 return Integer.compare(a[0], b[0]); // Compare by digit sum
16 }
17 return Integer.compare(a[1], b[1]); // Compare by value if digit sums are equal
18 });
19
20 // Create a map from sorted values to their target positions
21 Map<Integer, Integer> valueToTargetPosition = new HashMap<>();
22 for (int i = 0; i < arrayLength; i++) {
23 valueToTargetPosition.put(digitSumValuePairs[i][1], i);
24 }
25
26 // Track visited positions to identify cycles
27 boolean[] visited = new boolean[arrayLength];
28
29 // Count cycles in the permutation graph
30 // Each cycle of length k requires (k-1) swaps
31 int numberOfCycles = 0;
32 for (int i = 0; i < arrayLength; i++) {
33 if (!visited[i]) {
34 numberOfCycles++;
35
36 // Traverse the current cycle
37 int currentPosition = i;
38 while (!visited[currentPosition]) {
39 visited[currentPosition] = true;
40 // Move to where the current element should go
41 currentPosition = valueToTargetPosition.get(nums[currentPosition]);
42 }
43 }
44 }
45
46 // Total swaps = total elements - number of cycles
47 return arrayLength - numberOfCycles;
48 }
49
50 /**
51 * Calculates the sum of digits of a given number
52 * @param x the input number
53 * @return sum of all digits in x
54 */
55 private int f(int x) {
56 int digitSum = 0;
57 while (x != 0) {
58 digitSum += x % 10; // Add the last digit
59 x /= 10; // Remove the last digit
60 }
61 return digitSum;
62 }
63}
64
1class Solution {
2public:
3 // Calculate the sum of digits of a number
4 int f(int x) {
5 int digitSum = 0;
6 while (x > 0) {
7 digitSum += x % 10; // Add the last digit
8 x /= 10; // Remove the last digit
9 }
10 return digitSum;
11 }
12
13 int minSwaps(vector<int>& nums) {
14 int n = nums.size();
15
16 // Create pairs of (digit sum, original value) for sorting
17 vector<pair<int, int>> digitSumPairs(n);
18 for (int i = 0; i < n; ++i) {
19 digitSumPairs[i] = {f(nums[i]), nums[i]};
20 }
21
22 // Sort by digit sum (and by value if digit sums are equal)
23 sort(digitSumPairs.begin(), digitSumPairs.end());
24
25 // Map each value to its target position after sorting
26 unordered_map<int, int> valueToTargetIndex;
27 for (int i = 0; i < n; ++i) {
28 valueToTargetIndex[digitSumPairs[i].second] = i;
29 }
30
31 // Track visited elements to find cycles
32 vector<bool> visited(n, false);
33
34 // Count cycles in the permutation
35 // Minimum swaps = n - number of cycles
36 int cycleCount = 0;
37
38 for (int i = 0; i < n; ++i) {
39 if (!visited[i]) {
40 cycleCount++;
41
42 // Traverse the cycle starting from index i
43 int currentIndex = i;
44 while (!visited[currentIndex]) {
45 visited[currentIndex] = true;
46 // Move to where the current element should go
47 currentIndex = valueToTargetIndex[nums[currentIndex]];
48 }
49 }
50 }
51
52 // Minimum swaps needed = total elements - number of cycles
53 return n - cycleCount;
54 }
55};
56
1/**
2 * Calculates the sum of digits of a number
3 * @param x - The input number
4 * @returns The sum of all digits in the number
5 */
6function f(x: number): number {
7 let digitSum = 0;
8
9 // Extract each digit and add to sum
10 while (x !== 0) {
11 digitSum += x % 10; // Get the last digit
12 x = Math.floor(x / 10); // Remove the last digit
13 }
14
15 return digitSum;
16}
17
18/**
19 * Finds the minimum number of swaps needed to sort the array
20 * based on digit sum (primary) and value (secondary)
21 * @param nums - The input array of numbers
22 * @returns The minimum number of swaps required
23 */
24function minSwaps(nums: number[]): number {
25 const n = nums.length;
26
27 // Create array of [digitSum, originalValue] pairs
28 const digitSumValuePairs: [number, number][] = new Array(n);
29 for (let i = 0; i < n; i++) {
30 digitSumValuePairs[i] = [f(nums[i]), nums[i]];
31 }
32
33 // Sort by digit sum first, then by original value if digit sums are equal
34 digitSumValuePairs.sort((a, b) => {
35 if (a[0] !== b[0]) {
36 return a[0] - b[0]; // Sort by digit sum
37 }
38 return a[1] - b[1]; // Sort by value if digit sums are equal
39 });
40
41 // Map each value to its target position in sorted array
42 const valueToTargetPosition = new Map<number, number>();
43 for (let i = 0; i < n; i++) {
44 valueToTargetPosition.set(digitSumValuePairs[i][1], i);
45 }
46
47 // Track visited positions to find cycles
48 const visited: boolean[] = new Array(n).fill(false);
49
50 // Count cycles: n elements - number of cycles = minimum swaps
51 let cycleCount = 0;
52
53 for (let i = 0; i < n; i++) {
54 if (!visited[i]) {
55 cycleCount++;
56 let currentPosition = i;
57
58 // Traverse the cycle
59 while (!visited[currentPosition]) {
60 visited[currentPosition] = true;
61 // Move to where the current element should go
62 currentPosition = valueToTargetPosition.get(nums[currentPosition])!;
63 }
64 }
65 }
66
67 // Minimum swaps = total elements - number of cycles
68 return n - cycleCount;
69}
70
Time and Space Complexity
Time Complexity: O(n log n + n * d)
where n
is the length of the input array and d
is the maximum number of digits in any element.
- The function
f(x)
calculates the sum of digits, which takesO(d)
time whered
is the number of digits inx
. - Creating the
arr
list with digit sums requiresO(n * d)
time as we callf(x)
for each of then
elements. - Sorting the
arr
takesO(n log n)
time. - Building the dictionary
d
takesO(n)
time. - The cycle detection loop visits each element exactly once, taking
O(n)
time. - Overall:
O(n * d + n log n + n + n)
=O(n log n + n * d)
Since the number of digits d
in an integer is typically O(log m)
where m
is the maximum value, and assuming m
is bounded by a constant or polynomial in n
, the time complexity simplifies to O(n log n)
.
Space Complexity: O(n)
- The
arr
list storesn
tuples:O(n)
space. - The dictionary
d
storesn
key-value pairs:O(n)
space. - The
vis
boolean array has sizen
:O(n)
space. - The sorting algorithm typically uses
O(log n)
toO(n)
auxiliary space depending on implementation. - Overall:
O(n + n + n)
=O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Digit Sum Calculation for Edge Cases
A common mistake is not handling the digit sum calculation properly for single-digit numbers or zero values. While the current solution handles positive integers correctly, developers often write buggy implementations:
Incorrect Implementation:
def calculate_digit_sum(number: int) -> int:
digit_sum = 0
while number > 0: # Bug: doesn't handle number = 0 properly
digit_sum += number % 10
number = number / 10 # Bug: using float division instead of integer division
return digit_sum
Correct Implementation:
def calculate_digit_sum(number: int) -> int:
# Handle zero explicitly if needed
if number == 0:
return 0
digit_sum = 0
while number:
digit_sum += number % 10
number //= 10 # Use integer division
return digit_sum
2. Misunderstanding the Sorting Order
Developers often make mistakes in the sorting logic, especially when handling ties in digit sums:
Incorrect Approach:
# Wrong: Sorting by value first, then digit sum
sorted_pairs = sorted((num, calculate_digit_sum(num)) for num in nums)
Correct Approach:
# Correct: Sort by (digit_sum, value) - Python naturally sorts tuples element by element
sorted_pairs = sorted((calculate_digit_sum(num), num) for num in nums)
3. Building the Wrong Position Mapping
A critical error is mapping positions incorrectly, which leads to wrong cycle detection:
Incorrect Mapping:
# Wrong: Maps index to value instead of value to target index
value_to_target_position = {i: pair[1] for i, pair in enumerate(sorted_pairs)}
Correct Mapping:
# Correct: Maps each value to its target position in sorted array
value_to_target_position = {pair[1]: i for i, pair in enumerate(sorted_pairs)}
4. Cycle Detection Logic Errors
The cycle traversal can go wrong if we don't properly track visited positions or follow the wrong chain:
Common Mistake:
for i in range(n):
if not visited[i]:
min_swaps_needed -= 1
# Wrong: Not properly traversing the cycle
visited[i] = True
j = value_to_target_position[nums[i]]
visited[j] = True # Only marks two positions, missing the rest of the cycle
Correct Implementation:
for i in range(n):
if not visited[i]:
min_swaps_needed -= 1
current_position = i
# Traverse the entire cycle
while not visited[current_position]:
visited[current_position] = True
current_position = value_to_target_position[nums[current_position]]
5. Assuming Array Indices Instead of Values
Since the problem states the array contains "distinct positive integers" (not necessarily 0 to n-1), a common mistake is treating array values as if they were indices:
Incorrect Assumption:
# Wrong: Assumes nums contains values 0 to n-1
for i in range(n):
if not visited[i]:
j = i
while not visited[j]:
visited[j] = True
j = nums[j] # Bug: Using value as index directly
Correct Approach:
# Correct: Use the mapping to find target positions
for i in range(n):
if not visited[i]:
j = i
while not visited[j]:
visited[j] = True
j = value_to_target_position[nums[j]] # Look up where nums[j] should go
Prevention Tips:
- Always test with edge cases like single-digit numbers and numbers with trailing zeros
- Verify the sorting order with a small example before implementing cycle detection
- Draw out the permutation cycles on paper to understand the mapping relationships
- Remember that the visited array tracks indices, not values
- Test with arrays containing large numbers (not just 0 to n-1) to catch index/value confusion
Which of the following is a min heap?
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!