3116. Kth Smallest Amount With Single Denomination Combination
Problem Description
You have an integer array coins representing different coin denominations and an integer k. You have an infinite supply of coins of each denomination.
The key constraint is that you cannot combine coins of different denominations - you can only use multiple coins of the same denomination to form amounts.
For example, if coins = [3, 5], you can form:
- Using only coins of value 3: 3, 6, 9, 12, 15, 18, ...
- Using only coins of value 5: 5, 10, 15, 20, 25, ...
The valid amounts you can make are: 3, 5, 6, 9, 10, 12, 15, 18, 20, ...
Your task is to find the k-th smallest amount that can be formed following this rule.
For instance, if coins = [3, 5] and k = 4, the first few smallest amounts are 3, 5, 6, 9, so the answer would be 9.
The problem asks you to return this k-th smallest amount as an integer.
Intuition
The key insight is to recognize that this problem has a monotonic property: if we can form at least k amounts that are less than or equal to some value x, then we can definitely form at least k amounts for any value greater than x. This monotonicity suggests using binary search.
Instead of directly finding the k-th smallest amount, we can reframe the problem: find the smallest value x such that there are exactly k amounts we can form that are less than or equal to x.
For a given value x, how do we count how many valid amounts are ≤ x?
For a single coin of value a, the amounts we can form are a, 2a, 3a, .... The number of these amounts that are ≤ x is ⌊x/a⌋.
With multiple coins, we might think to just sum up ⌊x/coins[i]⌋ for each coin, but this would overcount. For example, with coins [3, 6], the amount 6 can be formed using either 2×3 or 1×6, but we should only count it once.
This is where the inclusion-exclusion principle comes in. Some amounts can be formed by multiple coins - specifically, amounts that are multiples of the LCM (Least Common Multiple) of those coins.
For coins [a, b]:
- Add amounts formed by
a:⌊x/a⌋ - Add amounts formed by
b:⌊x/b⌋ - Subtract amounts formed by both (multiples of
lcm(a,b)):⌊x/lcm(a,b)⌋
This pattern extends to any number of coins using inclusion-exclusion: add counts for single coins, subtract counts for pairs (to remove duplicates), add back counts for triples (since we over-subtracted), and so on.
Since we have at most 15 coins, we can use bit manipulation to enumerate all possible subsets (2^15 possibilities) and apply inclusion-exclusion to get the exact count of valid amounts ≤ x.
Once we can count amounts for any x, binary search finds the smallest x where the count reaches k.
Learn more about Math, Binary Search and Combinatorics patterns.
Solution Implementation
1class Solution:
2 def findKthSmallest(self, coins: List[int], k: int) -> int:
3 def count_multiples_up_to(max_value: int) -> int:
4 """
5 Count how many valid amounts can be formed up to max_value.
6 Uses inclusion-exclusion principle to avoid double counting.
7
8 Args:
9 max_value: The upper bound to check multiples up to
10
11 Returns:
12 Count of valid amounts <= max_value
13 """
14 total_count = 0
15
16 # Iterate through all non-empty subsets of coins using bitmask
17 for subset_mask in range(1, 1 << len(coins)):
18 lcm_value = 1
19
20 # Calculate LCM of all coins in current subset
21 for coin_index, coin_value in enumerate(coins):
22 if subset_mask >> coin_index & 1:
23 lcm_value = lcm(lcm_value, coin_value)
24 if lcm_value > max_value:
25 break
26
27 # Apply inclusion-exclusion principle
28 subset_size = subset_mask.bit_count()
29 if subset_size & 1:
30 total_count += max_value // lcm_value
31 else:
32 total_count -= max_value // lcm_value
33
34 return total_count
35
36 def feasible(mid: int) -> bool:
37 """Check if there are at least k valid amounts <= mid."""
38 return count_multiples_up_to(mid) >= k
39
40 # Binary search using the standard template
41 left, right = 1, 10**11
42 first_true_index = -1
43
44 while left <= right:
45 mid = (left + right) // 2
46 if feasible(mid):
47 first_true_index = mid
48 right = mid - 1
49 else:
50 left = mid + 1
51
52 return first_true_index
531class Solution {
2 private int[] coins;
3 private int k;
4
5 /**
6 * Finds the k-th smallest positive integer that is divisible by at least one coin value.
7 * Uses binary search on the answer combined with inclusion-exclusion principle.
8 */
9 public long findKthSmallest(int[] coins, int k) {
10 this.coins = coins;
11 this.k = k;
12
13 // Binary search using the standard template
14 long left = 1;
15 long right = (long) 1e11;
16 long firstTrueIndex = -1;
17
18 while (left <= right) {
19 long mid = left + (right - left) / 2;
20 if (feasible(mid)) {
21 firstTrueIndex = mid;
22 right = mid - 1;
23 } else {
24 left = mid + 1;
25 }
26 }
27
28 return firstTrueIndex;
29 }
30
31 /**
32 * Check if there are at least k valid amounts <= maxValue.
33 */
34 private boolean feasible(long maxValue) {
35 return countMultiples(maxValue) >= k;
36 }
37
38 /**
39 * Counts how many positive integers up to maxValue are divisible by at least one coin.
40 * Uses inclusion-exclusion principle with bitmask.
41 */
42 private long countMultiples(long maxValue) {
43 long count = 0;
44 int n = coins.length;
45
46 for (int bitmask = 1; bitmask < (1 << n); ++bitmask) {
47 long lcmValue = 1;
48
49 for (int j = 0; j < n; ++j) {
50 if ((bitmask >> j & 1) == 1) {
51 lcmValue = lcm(lcmValue, coins[j]);
52 if (lcmValue > maxValue) {
53 break;
54 }
55 }
56 }
57
58 int subsetSize = Integer.bitCount(bitmask);
59 if (subsetSize % 2 == 1) {
60 count += maxValue / lcmValue;
61 } else {
62 count -= maxValue / lcmValue;
63 }
64 }
65
66 return count;
67 }
68
69 private long lcm(long a, long b) {
70 return a * b / gcd(a, b);
71 }
72
73 private long gcd(long a, long b) {
74 return b == 0 ? a : gcd(b, a % b);
75 }
76}
771class Solution {
2public:
3 long long findKthSmallest(vector<int>& coins, int k) {
4 using ll = long long;
5 int n = coins.size();
6
7 // Count how many valid amounts exist <= maxValue
8 auto countMultiples = [&](ll maxValue) -> ll {
9 ll totalCount = 0;
10
11 for (int mask = 1; mask < (1 << n); ++mask) {
12 ll lcmValue = 1;
13
14 for (int bitPos = 0; bitPos < n; ++bitPos) {
15 if ((mask >> bitPos) & 1) {
16 lcmValue = lcm(lcmValue, static_cast<ll>(coins[bitPos]));
17 if (lcmValue > maxValue) {
18 break;
19 }
20 }
21 }
22
23 int subsetSize = __builtin_popcount(mask);
24 if (subsetSize & 1) {
25 totalCount += maxValue / lcmValue;
26 } else {
27 totalCount -= maxValue / lcmValue;
28 }
29 }
30
31 return totalCount;
32 };
33
34 // Check if there are at least k valid amounts <= mid
35 auto feasible = [&](ll mid) -> bool {
36 return countMultiples(mid) >= k;
37 };
38
39 // Binary search using the standard template
40 ll left = 1;
41 ll right = 1e11;
42 ll firstTrueIndex = -1;
43
44 while (left <= right) {
45 ll mid = left + (right - left) / 2;
46 if (feasible(mid)) {
47 firstTrueIndex = mid;
48 right = mid - 1;
49 } else {
50 left = mid + 1;
51 }
52 }
53
54 return firstTrueIndex;
55 }
56};
571/**
2 * Finds the kth smallest positive integer that is divisible by at least one element in coins array.
3 * Uses binary search combined with inclusion-exclusion principle.
4 */
5function findKthSmallest(coins: number[], k: number): number {
6 const coinsCount = coins.length;
7
8 /**
9 * Count how many valid amounts exist <= maxValue.
10 */
11 const countMultiples = (maxValue: bigint): bigint => {
12 let count = 0n;
13
14 for (let subset = 1; subset < (1 << coinsCount); ++subset) {
15 let currentLcm = 1n;
16
17 for (let coinIndex = 0; coinIndex < coinsCount; ++coinIndex) {
18 if ((subset >> coinIndex) & 1) {
19 currentLcm = lcm(currentLcm, BigInt(coins[coinIndex]));
20 if (currentLcm > maxValue) {
21 break;
22 }
23 }
24 }
25
26 const subsetSize = bitCount(subset);
27 if (subsetSize & 1) {
28 count += maxValue / currentLcm;
29 } else {
30 count -= maxValue / currentLcm;
31 }
32 }
33
34 return count;
35 };
36
37 /**
38 * Check if there are at least k valid amounts <= mid.
39 */
40 const feasible = (mid: bigint): boolean => {
41 return countMultiples(mid) >= BigInt(k);
42 };
43
44 // Binary search using the standard template
45 let left = 1n;
46 let right = BigInt(1e11);
47 let firstTrueIndex = -1n;
48
49 while (left <= right) {
50 const mid = (left + right) / 2n;
51 if (feasible(mid)) {
52 firstTrueIndex = mid;
53 right = mid - 1n;
54 } else {
55 left = mid + 1n;
56 }
57 }
58
59 return Number(firstTrueIndex);
60}
61
62function gcd(a: bigint, b: bigint): bigint {
63 return b === 0n ? a : gcd(b, a % b);
64}
65
66function lcm(a: bigint, b: bigint): bigint {
67 return (a * b) / gcd(a, b);
68}
69
70function bitCount(i: number): number {
71 i = i - ((i >>> 1) & 0x55555555);
72 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
73 i = (i + (i >>> 4)) & 0x0f0f0f0f;
74 i = i + (i >>> 8);
75 i = i + (i >>> 16);
76 return i & 0x3f;
77}
78Solution Approach
The solution uses binary search combined with the inclusion-exclusion principle to find the k-th smallest amount.
Binary Search Template:
We use the standard binary search template to find the smallest value where at least k valid amounts exist:
left, right = 1, 10**11 first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
The feasible(mid) function returns True if there are at least k valid amounts ≤ mid.
Counting Valid Amounts:
For a given value mid, we count how many valid amounts exist using inclusion-exclusion:
-
Enumerate all non-empty subsets of coins using bitmask (from
1to2^n - 1) -
Calculate LCM of each subset:
lcm_value = 1 for coin_index, coin_value in enumerate(coins): if subset_mask >> coin_index & 1: lcm_value = lcm(lcm_value, coin_value) if lcm_value > mid: break -
Apply inclusion-exclusion:
- For subsets with odd number of elements: add
mid // lcm_value - For subsets with even number of elements: subtract
mid // lcm_value
- For subsets with odd number of elements: add
Why Inclusion-Exclusion Works:
- Single coins (size 1): Add counts like
⌊mid/a⌋,⌊mid/b⌋ - Pairs (size 2): Subtract overlaps like
⌊mid/lcm(a,b)⌋ - Triples (size 3): Add back over-corrections like
⌊mid/lcm(a,b,c)⌋
Feasible Function Pattern: The feasible function creates a boolean pattern over the search space:
false, false, false, ..., true, true, true, ...
We find the first true position, which is exactly the k-th smallest amount.
The time complexity is O(2^n × log(max_value)) where n is the number of coins (at most 15).
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 finding the 4th smallest amount with coins = [3, 6] and k = 4.
Step 1: Understanding what amounts we can form
- Using coin 3: 3, 6, 9, 12, 15, ...
- Using coin 6: 6, 12, 18, 24, ...
- Combined (sorted): 3, 6, 9, 12, ... (note that 6 appears in both but we count it once)
Step 2: Binary Search Process
We'll search for the smallest value x such that there are at least 4 amounts ≤ x.
Let's trace through some binary search iterations:
Check x = 5:
- Subsets to consider: {3}, {6}, {3,6}
- Subset {3} (size=1, odd): Add ⌊5/3⌋ = 1
- Subset {6} (size=1, odd): Add ⌊5/6⌋ = 0
- Subset {3,6} (size=2, even): Subtract ⌊5/lcm(3,6)⌋ = ⌊5/6⌋ = 0
- Total count = 1 + 0 - 0 = 1
- Since 1 < 4, we need a larger x
Check x = 10:
- Subset {3} (size=1, odd): Add ⌊10/3⌋ = 3 (amounts: 3, 6, 9)
- Subset {6} (size=1, odd): Add ⌊10/6⌋ = 1 (amount: 6)
- Subset {3,6} (size=2, even): Subtract ⌊10/lcm(3,6)⌋ = ⌊10/6⌋ = 1 (overlap: 6)
- Total count = 3 + 1 - 1 = 3
- Since 3 < 4, we need a larger x
Check x = 12:
- Subset {3} (size=1, odd): Add ⌊12/3⌋ = 4 (amounts: 3, 6, 9, 12)
- Subset {6} (size=1, odd): Add ⌊12/6⌋ = 2 (amounts: 6, 12)
- Subset {3,6} (size=2, even): Subtract ⌊12/lcm(3,6)⌋ = ⌊12/6⌋ = 2 (overlaps: 6, 12)
- Total count = 4 + 2 - 2 = 4
- Since 4 ≥ 4, this satisfies our condition!
Check x = 11:
- Subset {3} (size=1, odd): Add ⌊11/3⌋ = 3
- Subset {6} (size=1, odd): Add ⌊11/6⌋ = 1
- Subset {3,6} (size=2, even): Subtract ⌊11/6⌋ = 1
- Total count = 3 + 1 - 1 = 3
- Since 3 < 4, x = 11 is too small
Step 3: Binary Search Conclusion
The binary search finds that x = 12 is the smallest value where we have at least 4 amounts ≤ x.
Therefore, the 4th smallest amount is 12.
We can verify: The amounts in order are 3, 6, 9, 12, and indeed 12 is the 4th one.
Key Insight from the Example: Notice how inclusion-exclusion correctly handled the overlap at 6 and 12:
- Both values were counted once by coin 3 and once by coin 6
- The subtraction of lcm(3,6) = 6 removed these duplicates
- This gave us the exact count of unique amounts
Time and Space Complexity
Time Complexity: O(n × 2^n × log(k × M))
The time complexity consists of three main components:
-
Binary search range: The binary search is performed on the range
[0, 10^11), which takesO(log(10^11))iterations. Since we're looking for the k-th smallest value that can be formed by multiples of coins, and the maximum coin value is M, the actual search range is approximatelyO(log(k × M)). -
Check function per binary search iteration: For each binary search iteration, the
checkfunction is called, which:- Iterates through all possible subsets of coins:
2^nsubsets (from 1 to2^n - 1) - For each subset, computes the LCM of selected coins, which takes
O(n)time in the worst case (iterating through all coins when all bits are set) - The LCM calculation itself has a cost, but it's dominated by the iteration through coins
- Iterates through all possible subsets of coins:
-
Overall: Combining these factors:
O(log(k × M))binary search iterations ×O(2^n)subsets ×O(n)operations per subset =O(n × 2^n × log(k × M))
Space Complexity: O(1)
The space complexity is constant as the algorithm only uses:
- A few integer variables (
cnt,v,m,i,j) - No additional data structures that scale with input size
- The recursive calls in
bisect_leftuseO(log(k × M))stack space, but this is typically considered negligible compared to the dominant factors
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
A critical mistake is using the while left < right with right = mid pattern instead of the standard template:
# WRONG - Non-standard template variant while left < right: mid = (left + right) // 2 if feasible(mid): right = mid else: left = mid + 1 return left
Correct approach using the standard template:
left, right = 1, 10**11 first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
The standard template explicitly tracks the answer with first_true_index, uses while left <= right, and always moves bounds by exactly 1 (right = mid - 1).
2. Misunderstanding the Problem Constraint
Many people initially think this is a standard coin change problem where you can mix different denominations. However, the constraint explicitly states you can only use multiple coins of the same denomination.
Wrong interpretation: With coins = [3, 5], thinking you can make 8 (3 + 5)
Correct interpretation: You cannot make 8 because it requires mixing denominations
3. Incorrect Inclusion-Exclusion Logic
A common mistake is getting the inclusion-exclusion signs backwards:
# WRONG - Signs are reversed! if subset_size & 1: # Odd size total_count -= max_value // lcm_value # Should be ADD else: # Even size total_count += max_value // lcm_value # Should be SUBTRACT
Solution: Remember that inclusion-exclusion starts with adding single elements (odd-sized subsets) and subtracting their intersections (even-sized subsets).
4. LCM Overflow Not Handled Properly
The LCM can grow very large, potentially exceeding the search bounds:
# WRONG - No overflow check
for coin_index, coin_value in enumerate(coins):
if subset_mask >> coin_index & 1:
lcm_value = lcm(lcm_value, coin_value)
# Missing: if lcm_value > max_value: break
Solution: Always check if lcm_value > max_value: break after computing each LCM step.
5. Incorrect Binary Search Bounds
Using too small an upper bound for binary search. With k potentially being up to 10^9 and coins as small as 1, the k-th smallest value could be quite large.
Solution: Use a sufficiently large upper bound like 10**11 to ensure the answer falls within the search range.
What's the relationship between a tree and a graph?
Recommended Readings
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
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Want a Structured Path to Master System Design Too? Don’t Miss This!