2578. Split With Minimum Sum


Problem Description

Given a positive integer num, the task is to find two non-negative integers num1 and num2 such that when combined as a concatenated string, they form a permutation of the original number num. The meaning of a permutation here is that the frequency of each digit in num1 plus num2 should match the frequency of each digit in num. Additionally, num1 and num2 can start with zeros, and the goal is to minimize the sum of num1 and num2.

Intuition

The key to solving this problem is understanding what permutations and minimum sums involve. To achieve the minimum sum, smaller digits should contribute more to the less significant places (rightmost) of the numbers, and we should distribute the digits between num1 and num2 as evenly as possible, especially the smallest non-zero digit to avoid a larger sum.

One approach could be using a counting method. Counting each digit's occurrences in num can help us ensure that we distribute the digits correctly between num1 and num2. By starting with the smallest digits and alternating between num1 and num2, we can spread out the digits evenly and keep the sums low.

Here's how we can think through the problem:

  • Count the occurrences of each digit (0-9) in num.
  • Starting from the smallest digit, assign digits to num1 and num2 alternately.
  • Ensure that the least significant digit of num (the rightmost) goes to the least significant place in num1 and num2 to keep their sums to a minimum.
  • Loop through the digits until all counted digits are assigned.
  • Finally, compute the total by summing up num1 and num2.

This approach leads us to distribute the digits in a way that balances the sum. The counting method is handy since it doesn't require us to modify num and is efficient in terms of time complexity.

Learn more about Greedy, Math and Sorting patterns.

Solution Approach

To implement the solution, we follow a count and greedy algorithm. Below are the steps that elaborate on how this approach is applied in the solution using the given Python code as a reference:

  1. Initialize a Counter: A counter (from Python's collections standard library) is used to track the frequency of each digit in the input number num. This helps us to make sure that we use each digit exactly as many times as it appears in num.

  2. Count Digits: We start by calculating the frequency of each digit and the total number of digits n in num. Every digit is reduced and its occurrences are recorded until num becomes zero.

  3. Create an Answer Array: An answer array ans with two elements initializes both num1 and num2 to zero. As we proceed through the digits by increasing order, we will build up these two numbers.

  4. Greedy Assignment: We greedily assign the smallest available digits first while decrementing their count in the counter. We iterate through a range defined by the number of digits n and for each digit, find the smallest digit that has not been fully used up (i.e., its count in the counter is not zero).

  5. Alternate Construction: For each iteration, we alternate between appending the chosen digit to num1 and num2. This is handled by the bit manipulation i & 1, which alternates between 0 and 1 with each increment of i, therefore distributing the digits evenly and contributing to the smaller sum.

  6. Sum Calculation: After the alternating assignment, we calculate the sum of the two generated numbers num1 and num2 and return the result as the minimum possible sum. Since we're concatenating smaller digits first and evenly splitting the digits, this sum would be the smallest possible.

The algorithm's time complexity is reported as O(n), where n is the number of digits in num, because we scan through the input number once and iterate through the counter based on the number of digits. The space complexity is O(C), where C is the count of unique digits in num, which is less than or equal to 10 (since these are decimal digits).

The approach leverages a balanced distribution of digits to both num1 and num2 with a focus on placing lower digits in the less significant positions to achieve the minimum sum. This is done effectively using direct counting and iteration without the need for sorting, which makes it a neat and efficient solution.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's take the number num = 1122 and walk through the solution approach to find the minimum sum of two non-negative integers num1 and num2 that are permutations of num.

  1. Initialize a Counter: We use a counter to keep the frequency of digits in 1122. Here, the digits '1' and '2' each occur twice.

  2. Count Digits: We count the occurrences of each digit and note that the total number of digits n is 4. The frequencies are {'1': 2, '2': 2}.

  3. Create an Answer Array: We initialize ans with two elements, both set to 0, which eventually will represent num1 and num2.

  4. Greedy Assignment: We want to place smaller digits first to minimize the sum. Since we only have '1' and '2', we start with '1'.

  5. Alternate Construction: Starting from the least significant place, we proceed like this:

    • First digit: Assign '1' to num1, so num1 = 1.
    • Second digit: Assign '1' to num2, so num2 = 1.
    • Third digit: Assign '2' to num1, so num1 = 21.
    • Fourth digit: Assign '2' to num2, so num2 = 21.
  6. Sum Calculation: After assignment, we have num1 = 21 and num2 = 21. The minimum sum is 21 + 21 = 42.

By following the steps outlined in the solution approach, we assigned the digits to num1 and num2 in an alternating fashion, starting with the smallest digits. This ensured that we got the minimum sum of all possible permutations of the integer 1122.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def splitNum(self, num: int) -> int:
5        # Create a counter to keep track of the frequency of each digit
6        digit_counter = Counter()
7      
8        # Variable to keep track of the length of the number
9        num_length = 0
10        # Split the number into digits and populate the counter
11        while num > 0:
12            digit_counter[num % 10] += 1
13            num //= 10
14            num_length += 1
15      
16        # Initialize an array to hold our two new numbers
17        split_numbers = [0] * 2
18        # Variable to track the current digit we are considering
19        current_digit = 0
20      
21        # Iterate over the length of the original number
22        for index in range(num_length):
23            # Find the next smallest digit available by checking the counter
24            while digit_counter[current_digit] == 0:
25                current_digit += 1
26            # Decrease the count for the current digit as we're now using it
27            digit_counter[current_digit] -= 1
28            # Depending on the index being even/odd, add the digit to the corresponding number
29            split_numbers[index % 2] = split_numbers[index % 2] * 10 + current_digit
30      
31        # Return the sum of the two new numbers
32        return sum(split_numbers)
33
1class Solution {
2  
3    // This method takes an integer and splits it into two numbers such that the sum of the two is minimized
4    public int splitNum(int num) {
5        // Create an array to count the occurrences of each digit
6        int[] digitCount = new int[10];
7        // Variable to hold the total number of digits in num
8        int totalDigits = 0;
9      
10        // Count the occurrences of each digit in the input number
11        while (num > 0) {
12            int digit = num % 10;  // Extract the last digit of num
13            digitCount[digit]++;   // Increment its count in the array
14            num /= 10;             // Remove the last digit from num
15            totalDigits++;         // Increment the total number of digits
16        }
17      
18        // Array to hold the two numbers formed from splitting
19        int[] parts = new int[2];
20        // We will all ever go up to the total number of digits in the original number
21        for (int i = 0, digitIndex = 0; i < totalDigits; ++i) {
22            // Find the smallest non-zero count digit
23            while (digitCount[digitIndex] == 0) {
24                digitIndex++;
25            }
26            // Decrease the count of the chosen digit
27            digitCount[digitIndex]--;
28            // Construct the split numbers digit by digit, alternating between the two numbers
29            parts[i % 2] = parts[i % 2] * 10 + digitIndex;
30        }
31      
32        // Return the sum of the two constructed numbers
33        return parts[0] + parts[1];
34    }
35}
36
1class Solution {
2public:
3    // Method to split a number and sum the parts.
4    int splitNum(int num) {
5        // Array to count frequency of each digit in 'num'.
6        int digitCounts[10] = {0};
7      
8        // Total number of digits in 'num'.
9        int digitTotal = 0;
10      
11        // Counting digit frequencies and the total number of digits.
12        for (; num > 0; num /= 10) {
13            ++digitCounts[num % 10];
14            ++digitTotal;
15        }
16      
17        // Array to store the two numbers formed from the digits.
18        int splitNumbers[2] = {0};
19      
20        // Iterating through the number of digits.
21        for (int i = 0, digitIndex = 0; i < digitTotal; ++i) {
22            // Find the next non-zero frequency digit.
23            while (digitCounts[digitIndex] == 0) {
24                ++digitIndex;
25            }
26          
27            // Decrease the count of found digit.
28            --digitCounts[digitIndex];
29          
30            // Add the found digit to one of the two numbers.
31            splitNumbers[i % 2] = splitNumbers[i % 2] * 10 + digitIndex;
32        }
33      
34        // Return the sum of the two split numbers.
35        return splitNumbers[0] + splitNumbers[1];
36    }
37};
38
1function splitNum(num: number): number {
2    // Initialize a count array with 10 zeroes for digits 0-9.
3    const digitCount: number[] = Array(10).fill(0);
4    // Variable to keep track of the total number of digits.
5    let digitTotal = 0;
6
7    // Count digit occurrences and total digits until num is greater than 0.
8    for (; num > 0; num = Math.floor(num / 10)) {
9        ++digitCount[num % 10]; // Increment count for the current least significant digit.
10        ++digitTotal; // Increment total number of digits.
11    }
12
13    // Initialize an array to store the two new numbers formed from the digits.
14    const splitNumbers: number[] = Array(2).fill(0);
15
16    // Reconstruct the two numbers by alternating digits starting from the smallest.
17    for (let i = 0, digitIndex = 0; i < digitTotal; ++i) {
18        // Find the smallest digit that has not been used up.
19        while (digitCount[digitIndex] === 0) {
20            ++digitIndex;
21        }
22        // Use the current smallest digit and update the appropriate number.
23        --digitCount[digitIndex];
24        splitNumbers[i & 1] = splitNumbers[i & 1] * 10 + digitIndex;
25    }
26
27    // Return the sum of the two constructed numbers.
28    return splitNumbers[0] + splitNumbers[1];
29}
30

Time and Space Complexity

Time Complexity

The time complexity of the code can be analyzed as follows:

  1. The while loop runs as long as 'num' has digits, contributing O(n) time complexity where n is the number of digits in num.

  2. Inside the loop, the modulo and division operations are O(1) operations but they're repeated for each digit, contributing again O(n).

  3. After the initial while loop, there's a nested loop where the outer loop runs n times (for i in range(n)), and the inner while loop may run up to 10 times (as there are 10 possible digits 0-9). However, on average, if digits are evenly distributed, the inner loop will run a constant number of times; thus, the nested loop contributes O(n).

  4. The multiplication and addition inside the nested loop are O(1) operations.

Considering all the above, the overall time complexity of the splitNum function is O(n) since none of the steps depend on the number of digits logarithmically or have a more significant impact than linear in terms of digits.

Space Complexity

The space complexity of the code can be analyzed as follows:

  1. A Counter object is used to store the frequency of each digit, which requires at most O(1) space because there are only 10 digits (0-9) regardless of the size of num.

  2. An array ans of size 2 is used, requiring O(1) space.

  3. Variables n, j, and other loop variables use O(1) space.

In total, the space complexity is O(1) because the space required does not change with the size of the input num.

The reference answer suggests O(n) for both time and space complexities. However, the given code has a time complexity of O(n) and a space complexity of O(1), assuming that n is the number of digits in num. There seems to be a discrepancy in the analysis regarding space complexity.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

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

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

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


Load More