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
andnum2
alternately. - Ensure that the least significant digit of
num
(the rightmost) goes to the least significant place innum1
andnum2
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
andnum2
.
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.
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:
-
Initialize a Counter: A counter (from Python's
collections
standard library) is used to track the frequency of each digit in the input numbernum
. This helps us to make sure that we use each digit exactly as many times as it appears innum
. -
Count Digits: We start by calculating the frequency of each digit and the total number of digits
n
innum
. Every digit is reduced and its occurrences are recorded untilnum
becomes zero. -
Create an Answer Array: An answer array
ans
with two elements initializes bothnum1
andnum2
to zero. As we proceed through the digits by increasing order, we will build up these two numbers. -
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). -
Alternate Construction: For each iteration, we alternate between appending the chosen digit to
num1
andnum2
. This is handled by the bit manipulationi & 1
, which alternates between 0 and 1 with each increment ofi
, therefore distributing the digits evenly and contributing to the smaller sum. -
Sum Calculation: After the alternating assignment, we calculate the sum of the two generated numbers
num1
andnum2
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 EvaluatorExample 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
.
-
Initialize a Counter: We use a counter to keep the frequency of digits in
1122
. Here, the digits '1' and '2' each occur twice. -
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}. -
Create an Answer Array: We initialize
ans
with two elements, both set to 0, which eventually will representnum1
andnum2
. -
Greedy Assignment: We want to place smaller digits first to minimize the sum. Since we only have '1' and '2', we start with '1'.
-
Alternate Construction: Starting from the least significant place, we proceed like this:
- First digit: Assign '1' to
num1
, sonum1 = 1
. - Second digit: Assign '1' to
num2
, sonum2 = 1
. - Third digit: Assign '2' to
num1
, sonum1 = 21
. - Fourth digit: Assign '2' to
num2
, sonum2 = 21
.
- First digit: Assign '1' to
-
Sum Calculation: After assignment, we have
num1 = 21
andnum2 = 21
. The minimum sum is21 + 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:
-
The
while
loop runs as long as 'num' has digits, contributingO(n)
time complexity wheren
is the number of digits innum
. -
Inside the loop, the modulo and division operations are
O(1)
operations but they're repeated for each digit, contributing againO(n)
. -
After the initial
while
loop, there's a nested loop where the outer loop runsn
times (for i in range(n)
), and the innerwhile
loop may run up to10
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 contributesO(n)
. -
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:
-
A
Counter
object is used to store the frequency of each digit, which requires at mostO(1)
space because there are only 10 digits (0-9) regardless of the size ofnum
. -
An array
ans
of size2
is used, requiringO(1)
space. -
Variables
n
,j
, and other loop variables useO(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.
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
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!