2310. Sum of Numbers With Units Digit K
Problem Description
You need to find a set of positive integers where each integer ends with the digit k
(units digit is k
), and the sum of all integers in the set equals num
.
The goal is to return the minimum possible size of such a set. If it's impossible to create such a set, return -1
.
Key points to understand:
- Every number in your set must have
k
as its last digit (e.g., ifk = 3
, valid numbers could be 3, 13, 23, 33, etc.) - The same number can appear multiple times in the set
- The sum of all numbers in the set must equal exactly
num
- You want the smallest possible set size
- An empty set has a sum of 0
For example:
- If
num = 58
andk = 9
, you could use the set{9, 49}
which has size 2 - If
num = 37
andk = 2
, you could use the set{2, 12, 12, 12}
or other combinations, but you want the minimum size
The solution works by recognizing that any number ending in k
can be written as 10x + k
for some non-negative integer x
. If we have n
such numbers summing to num
, then num - n*k
must be divisible by 10 (since it represents the sum of all the 10x
parts). The code finds the smallest n
where this condition holds.
Intuition
Let's think about what numbers we can use. Any number ending with digit k
can be written as 10x + k
where x
is some non-negative integer. For example, if k = 3
, we have numbers like 3 (when x = 0
), 13 (when x = 1
), 23 (when x = 2
), and so on.
Now, if we use n
such numbers to sum up to num
, we can write:
(10x₁ + k) + (10x₂ + k) + ... + (10xₙ + k) = num
- Rearranging:
10(x₁ + x₂ + ... + xₙ) + n*k = num
- Therefore:
10(x₁ + x₂ + ... + xₙ) = num - n*k
For this equation to have a valid solution, num - n*k
must be divisible by 10 (since the left side is always a multiple of 10). Also, num - n*k
must be non-negative because the sum x₁ + x₂ + ... + xₙ
cannot be negative.
The key insight is that we want the minimum n
. So we start checking from n = 1
and incrementally increase it. The first n
that makes num - n*k
both non-negative and divisible by 10 is our answer.
Why does this give us the minimum? Because once we find a valid n
, we know we can distribute the value (num - n*k)/10
among our n
numbers. We could use larger values of n
, but that would mean using more numbers, which contradicts our goal of finding the minimum set size.
Special case: If num = 0
, we need zero numbers (empty set), so we return 0 immediately.
Learn more about Greedy, Math and Dynamic Programming patterns.
Solution Approach
The implementation follows directly from our mathematical insight that num - n*k
must be divisible by 10 for a valid solution.
First, we handle the special case where num = 0
. An empty set has a sum of 0, so we return 0 immediately.
For non-zero values of num
, we iterate through possible set sizes from 1 to num
:
for i in range(1, num + 1):
if (t := num - k * i) >= 0 and t % 10 == 0:
return i
In each iteration with set size i
:
- Calculate
t = num - k * i
, which represents the remaining value after subtractingi
instances of the units digitk
- Check two conditions:
t >= 0
: Ensures we haven't subtracted too much (the remaining value must be non-negative)t % 10 == 0
: Ensures the remaining value is divisible by 10
When both conditions are met, we've found our answer. The value i
represents the minimum number of integers needed where each ends with digit k
and their sum equals num
.
Why does this work? Because when t
is divisible by 10, we can distribute the value t/10
among our i
numbers as the tens and higher digits. For example, if we need i = 3
numbers ending in k
, and t = 30
, we could use numbers like (10 + k)
, (10 + k)
, (10 + k)
, distributing the 30 as three 10s.
If no valid i
is found after checking all possibilities from 1 to num
, we return -1
, indicating it's impossible to form such a set.
The algorithm has a time complexity of O(num)
in the worst case, as we might need to check up to num
iterations. The space complexity is O(1)
as we only use a constant amount of extra space.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example with num = 58
and k = 9
.
We need to find the minimum number of positive integers, each ending with digit 9, that sum to 58.
Starting with our approach, we check different set sizes i
:
When i = 1:
- Calculate
t = num - k * i = 58 - 9 * 1 = 49
- Check: Is
t >= 0
? Yes (49 ≥ 0) - Check: Is
t % 10 == 0
? No (49 % 10 = 9) - Not valid, continue.
When i = 2:
- Calculate
t = num - k * i = 58 - 9 * 2 = 40
- Check: Is
t >= 0
? Yes (40 ≥ 0) - Check: Is
t % 10 == 0
? Yes (40 % 10 = 0) - Valid! Return
i = 2
So the minimum size is 2. How do we construct such a set?
Since t = 40
and t/10 = 4
, we need to distribute 4 among our 2 numbers as the tens digit. We could use:
- 9 (which is 010 + 9) and 49 (which is 410 + 9)
- Or 19 (which is 110 + 9) and 39 (which is 310 + 9)
- Or 29 (which is 210 + 9) and 29 (which is 210 + 9)
All these sets have exactly 2 numbers ending in 9 that sum to 58. The algorithm correctly identifies that 2 is the minimum possible size.
Solution Implementation
1class Solution:
2 def minimumNumbers(self, num: int, k: int) -> int:
3 # Edge case: if target number is 0, we need 0 elements
4 if num == 0:
5 return 0
6
7 # Try using i elements, where each element ends with digit k
8 # Start from 1 element up to num elements
9 for i in range(1, num + 1):
10 # Calculate the remaining value after using i elements ending with k
11 remaining_value = num - k * i
12
13 # Check if the solution is valid:
14 # 1. remaining_value must be non-negative (we can't have negative numbers)
15 # 2. remaining_value % 10 must be 0 (the units digit constraint is satisfied)
16 if remaining_value >= 0 and remaining_value % 10 == 0:
17 return i
18
19 # If no valid solution found, return -1
20 return -1
21
1class Solution {
2 public int minimumNumbers(int num, int k) {
3 // Base case: if num is 0, we need 0 numbers to sum to it
4 if (num == 0) {
5 return 0;
6 }
7
8 // Try different counts of numbers from 1 to num
9 for (int count = 1; count <= num; ++count) {
10 // Calculate the remaining sum after using 'count' numbers ending with 'k'
11 // Each number ending with 'k' contributes 'k' to the units digit
12 int remainingSum = num - k * count;
13
14 // Check if the remaining sum is valid:
15 // 1. remainingSum >= 0: ensures we don't exceed the target
16 // 2. remainingSum % 10 == 0: ensures the units digit matches
17 // (since k * count handles the units digit contribution)
18 if (remainingSum >= 0 && remainingSum % 10 == 0) {
19 return count;
20 }
21 }
22
23 // If no valid combination found, return -1
24 return -1;
25 }
26}
27
1class Solution {
2public:
3 int minimumNumbers(int num, int k) {
4 // Base case: if target sum is 0, we need 0 numbers
5 if (num == 0) {
6 return 0;
7 }
8
9 // Try using i numbers, where each number ends with digit k
10 for (int i = 1; i <= num; ++i) {
11 // Calculate the remaining value after accounting for i numbers ending with k
12 // The sum of i numbers ending with k will have last digit (i * k) % 10
13 int remaining = num - k * i;
14
15 // Check if this configuration is valid:
16 // 1. remaining >= 0: ensures we don't exceed the target
17 // 2. remaining % 10 == 0: ensures the last digit matches correctly
18 if (remaining >= 0 && remaining % 10 == 0) {
19 return i;
20 }
21 }
22
23 // No valid configuration found
24 return -1;
25 }
26};
27
1/**
2 * Finds the minimum number of integers from the set whose sum equals num
3 * @param num - The target sum to achieve
4 * @param k - The units digit that all integers in the set must have
5 * @returns The minimum number of integers needed, or -1 if impossible
6 */
7function minimumNumbers(num: number, k: number): number {
8 // If target sum is 0, we need 0 numbers
9 if (num === 0) {
10 return 0;
11 }
12
13 // Get the units digit of the target sum
14 const targetUnitsDigit: number = num % 10;
15
16 // Try using 1 to 10 numbers from the set
17 // We only need to check up to 10 because the units digit pattern repeats
18 for (let count: number = 1; count <= 10; count++) {
19 // Calculate the sum of 'count' numbers, each ending with digit k
20 const currentSum: number = count * k;
21
22 // Check if this sum is valid:
23 // 1. The sum should not exceed our target
24 // 2. The units digit of the sum should match the target's units digit
25 if (currentSum <= num && currentSum % 10 === targetUnitsDigit) {
26 return count;
27 }
28 }
29
30 // If no valid combination found, return -1
31 return -1;
32}
33
Time and Space Complexity
The time complexity is O(num)
, where num
is the input value. The loop iterates from 1 to num
in the worst case, performing constant-time operations in each iteration (arithmetic operations and modulo checks). Since the loop can run up to num
times, the time complexity is linear with respect to the value of num
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for the loop variable i
and the temporary variable t
, regardless of the input size. No additional data structures are created that scale with the input.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting the Zero Edge Case
A common mistake is not handling num = 0
correctly. When the target sum is 0, the answer should be 0 (empty set), not -1 or 1.
Incorrect approach:
def minimumNumbers(self, num: int, k: int) -> int:
for i in range(1, num + 1): # Starting from 1, missing the 0 case
if (num - k * i) >= 0 and (num - k * i) % 10 == 0:
return i
return -1
Solution: Always check for num = 0
first and return 0 immediately.
2. Incorrect Loop Boundary
Another pitfall is using an incorrect upper bound for the loop. Some might think to optimize by using a smaller boundary like num // k
, but this can miss valid solutions.
Incorrect approach:
def minimumNumbers(self, num: int, k: int) -> int:
if num == 0:
return 0
for i in range(1, num // k + 1): # Wrong boundary - too restrictive
if (num - k * i) >= 0 and (num - k * i) % 10 == 0:
return i
return -1
Why it's wrong: When k = 1
and num = 10
, we need 10 elements (each being 1), but num // k + 1 = 11
would work. However, if k = 3
and num = 10
, we might need up to 10 elements theoretically, but num // k + 1 = 4
would cut off potential solutions.
Solution: Use range(1, num + 1)
to ensure all possibilities are checked. The early return ensures efficiency.
3. Misunderstanding the Divisibility Check
Some might incorrectly check if num % 10 == k
instead of checking if (num - k * i) % 10 == 0
.
Incorrect approach:
def minimumNumbers(self, num: int, k: int) -> int:
if num == 0:
return 0
if num % 10 != k: # Wrong logic - too restrictive
return -1
# ... rest of the code
Why it's wrong: This assumes we can only solve the problem if num
itself ends with k
, which is incorrect. For example, num = 58
and k = 9
has a valid solution {9, 49}
, even though 58 doesn't end with 9.
Solution: The correct check is (num - k * i) % 10 == 0
for each potential set size i
.
4. Integer Overflow Concerns (Language-Specific)
In languages with fixed integer sizes, calculating k * i
might cause overflow for large values.
Potential issue in other languages:
# In languages like C++ or Java with int32 remaining = num - k * i # Could overflow if k * i is very large
Solution: In Python, this isn't an issue due to arbitrary precision integers. In other languages, consider using long/int64 types or add additional boundary checks to prevent overflow.
Which of the following is a good use case for backtracking?
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!