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 Approach
The solution uses binary search combined with the inclusion-exclusion principle to find the k-th smallest amount.
Binary Search Setup:
We use bisect_left
to find the smallest value x
where check(x)
returns True
. The search range is from 0 to 10^11
(a sufficiently large upper bound).
The check
Function:
This function determines if there are at least k
valid amounts less than or equal to mx
.
The implementation uses bit manipulation to enumerate all non-empty subsets of coins:
- We iterate from
1
to2^n - 1
wheren = len(coins)
- Each number
i
represents a subset: if the j-th bit is set, we includecoins[j]
Inclusion-Exclusion Implementation:
For each subset represented by i
:
-
Calculate LCM of the subset:
v = 1 for j, x in enumerate(coins): if i >> j & 1: # Check if j-th bit is set v = lcm(v, x) if v > mx: # Optimization: stop if LCM exceeds mx break
-
Apply inclusion-exclusion rule:
m = i.bit_count() # Number of coins in this subset if m & 1: # Odd number of elements cnt += mx // v # Add else: # Even number of elements cnt -= mx // v # Subtract
- For subsets with odd number of elements: add
⌊mx/LCM⌋
- For subsets with even number of elements: subtract
⌊mx/LCM⌋
- For subsets with odd number of elements: add
Why this works:
- Single coins (odd size = 1): We add counts like
⌊mx/a⌋
,⌊mx/b⌋
- Pairs of coins (even size = 2): We subtract overlaps like
⌊mx/lcm(a,b)⌋
- Triples (odd size = 3): We add back over-corrections like
⌊mx/lcm(a,b,c)⌋
- And so on...
Final Binary Search:
bisect_left(range(10**11), True, key=check)
finds the smallest value where check
returns True
, which is exactly the k-th smallest amount we can form.
The time complexity is O(2^n × log(max_value))
where n
is the number of coins (at most 15), making this approach efficient for the given constraints.
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
Solution Implementation
1class Solution:
2 def findKthSmallest(self, coins: List[int], k: int) -> int:
3 def count_multiples_up_to(max_value: int) -> bool:
4 """
5 Check if there are at least k multiples of coin values 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 True if count of multiples >= k, False otherwise
13 """
14 total_count = 0
15
16 # Iterate through all non-empty subsets of coins using bitmask
17 # (1 << len(coins)) gives us 2^n possible subsets
18 for subset_mask in range(1, 1 << len(coins)):
19 lcm_value = 1
20
21 # Calculate LCM of all coins in current subset
22 for coin_index, coin_value in enumerate(coins):
23 # Check if coin at coin_index is in current subset
24 if subset_mask >> coin_index & 1:
25 lcm_value = lcm(lcm_value, coin_value)
26 # Early termination if LCM exceeds max_value
27 if lcm_value > max_value:
28 break
29
30 # Count number of set bits (size of current subset)
31 subset_size = subset_mask.bit_count()
32
33 # Apply inclusion-exclusion principle:
34 # - Add if subset size is odd (include)
35 # - Subtract if subset size is even (exclude to avoid double counting)
36 if subset_size & 1: # Odd size subset
37 total_count += max_value // lcm_value
38 else: # Even size subset
39 total_count -= max_value // lcm_value
40
41 return total_count >= k
42
43 # Binary search for the smallest value where count >= k
44 # Search range: [0, 10^11) should cover all practical cases
45 # bisect_left finds the leftmost position where check returns True
46 return bisect_left(range(10**11), True, key=count_multiples_up_to)
47
1class 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 * @param coins Array of coin values
10 * @param k The k-th position to find
11 * @return The k-th smallest positive integer divisible by at least one coin
12 */
13 public long findKthSmallest(int[] coins, int k) {
14 this.coins = coins;
15 this.k = k;
16
17 // Binary search range: minimum is 1, maximum is 10^11
18 long left = 1;
19 long right = (long) 1e11;
20
21 // Binary search for the k-th smallest value
22 while (left < right) {
23 long mid = (left + right) >> 1; // Equivalent to (left + right) / 2
24
25 // Check if there are at least k numbers <= mid that are divisible by coins
26 if (check(mid)) {
27 right = mid; // mid could be the answer, search in lower half
28 } else {
29 left = mid + 1; // mid is too small, search in upper half
30 }
31 }
32
33 return left;
34 }
35
36 /**
37 * Counts how many positive integers up to maxValue are divisible by at least one coin.
38 * Uses inclusion-exclusion principle with bitmask to handle overlapping counts.
39 *
40 * @param maxValue The upper bound to check
41 * @return true if count >= k, false otherwise
42 */
43 private boolean check(long maxValue) {
44 long count = 0;
45 int n = coins.length;
46
47 // Iterate through all non-empty subsets using bitmask (1 to 2^n - 1)
48 for (int bitmask = 1; bitmask < (1 << n); ++bitmask) {
49 long lcmValue = 1;
50
51 // Calculate LCM of coins in current subset
52 for (int j = 0; j < n; ++j) {
53 // Check if j-th coin is included in current subset
54 if ((bitmask >> j & 1) == 1) {
55 lcmValue = lcm(lcmValue, coins[j]);
56
57 // Optimization: if LCM exceeds maxValue, no contribution to count
58 if (lcmValue > maxValue) {
59 break;
60 }
61 }
62 }
63
64 // Apply inclusion-exclusion principle
65 int subsetSize = Integer.bitCount(bitmask);
66 if (subsetSize % 2 == 1) {
67 // Odd-sized subset: add to count
68 count += maxValue / lcmValue;
69 } else {
70 // Even-sized subset: subtract from count (remove double counting)
71 count -= maxValue / lcmValue;
72 }
73 }
74
75 return count >= k;
76 }
77
78 /**
79 * Calculates the least common multiple (LCM) of two numbers.
80 * Formula: LCM(a, b) = (a * b) / GCD(a, b)
81 *
82 * @param a First number
83 * @param b Second number
84 * @return The LCM of a and b
85 */
86 private long lcm(long a, long b) {
87 return a * b / gcd(a, b);
88 }
89
90 /**
91 * Calculates the greatest common divisor (GCD) using Euclidean algorithm.
92 *
93 * @param a First number
94 * @param b Second number
95 * @return The GCD of a and b
96 */
97 private long gcd(long a, long b) {
98 return b == 0 ? a : gcd(b, a % b);
99 }
100}
101
1class Solution {
2public:
3 long long findKthSmallest(vector<int>& coins, int k) {
4 using ll = long long;
5
6 // Binary search range: minimum 1, maximum 1e11
7 ll left = 1, right = 1e11;
8 int n = coins.size();
9
10 // Lambda function to count how many multiples exist <= maxValue
11 // Uses inclusion-exclusion principle
12 auto countMultiples = [&](ll maxValue) {
13 ll totalCount = 0;
14
15 // Iterate through all non-empty subsets of coins (2^n - 1 combinations)
16 for (int mask = 1; mask < (1 << n); ++mask) {
17 ll lcmValue = 1;
18
19 // Calculate LCM of coins in current subset
20 for (int bitPos = 0; bitPos < n; ++bitPos) {
21 if ((mask >> bitPos) & 1) { // Check if bit is set
22 lcmValue = lcm(lcmValue, static_cast<ll>(coins[bitPos]));
23 // Early termination if LCM exceeds maxValue
24 if (lcmValue > maxValue) {
25 break;
26 }
27 }
28 }
29
30 // Apply inclusion-exclusion principle
31 int subsetSize = __builtin_popcount(mask);
32 if (subsetSize & 1) { // Odd-sized subset: add
33 totalCount += maxValue / lcmValue;
34 } else { // Even-sized subset: subtract
35 totalCount -= maxValue / lcmValue;
36 }
37 }
38
39 // Return true if we have at least k multiples
40 return totalCount >= k;
41 };
42
43 // Binary search to find the kth smallest multiple
44 while (left < right) {
45 ll mid = (left + right) >> 1; // Equivalent to (left + right) / 2
46
47 if (countMultiples(mid)) {
48 // If mid has >= k multiples, search in left half
49 right = mid;
50 } else {
51 // If mid has < k multiples, search in right half
52 left = mid + 1;
53 }
54 }
55
56 return left;
57 }
58};
59
1/**
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 * @param coins - Array of coin denominations
5 * @param k - The position of the smallest number to find
6 * @returns The kth smallest number divisible by at least one coin
7 */
8function findKthSmallest(coins: number[], k: number): number {
9 // Binary search range: from 1 to 10^11
10 let left = 1n;
11 let right = BigInt(1e11);
12 const coinsCount = coins.length;
13
14 /**
15 * Checks if there are at least k numbers <= maxValue that are divisible by at least one coin
16 * Uses inclusion-exclusion principle to count valid numbers
17 * @param maxValue - The upper bound to check
18 * @returns True if count >= k, false otherwise
19 */
20 const checkIfKthOrLess = (maxValue: bigint): boolean => {
21 let count = 0n;
22
23 // Iterate through all non-empty subsets of coins (2^n - 1 subsets)
24 for (let subset = 1; subset < (1 << coinsCount); ++subset) {
25 let currentLcm = 1n;
26
27 // Calculate LCM of all coins in current subset
28 for (let coinIndex = 0; coinIndex < coinsCount; ++coinIndex) {
29 // Check if coin at coinIndex is in current subset
30 if ((subset >> coinIndex) & 1) {
31 currentLcm = lcm(currentLcm, BigInt(coins[coinIndex]));
32 // Early termination if LCM exceeds maxValue
33 if (currentLcm > maxValue) {
34 break;
35 }
36 }
37 }
38
39 // Apply inclusion-exclusion principle
40 const subsetSize = bitCount(subset);
41 if (subsetSize & 1) {
42 // Odd-sized subset: add to count
43 count += maxValue / currentLcm;
44 } else {
45 // Even-sized subset: subtract from count
46 count -= maxValue / currentLcm;
47 }
48 }
49
50 return count >= BigInt(k);
51 };
52
53 // Binary search for the exact kth smallest value
54 while (left < right) {
55 const mid = (left + right) >> 1n;
56 if (checkIfKthOrLess(mid)) {
57 right = mid;
58 } else {
59 left = mid + 1n;
60 }
61 }
62
63 return Number(left);
64}
65
66/**
67 * Calculates the greatest common divisor using Euclidean algorithm
68 * @param a - First number
69 * @param b - Second number
70 * @returns The GCD of a and b
71 */
72function gcd(a: bigint, b: bigint): bigint {
73 return b === 0n ? a : gcd(b, a % b);
74}
75
76/**
77 * Calculates the least common multiple of two numbers
78 * @param a - First number
79 * @param b - Second number
80 * @returns The LCM of a and b
81 */
82function lcm(a: bigint, b: bigint): bigint {
83 return (a * b) / gcd(a, b);
84}
85
86/**
87 * Counts the number of set bits (1s) in the binary representation of a number
88 * Uses Brian Kernighan's algorithm with bit manipulation optimizations
89 * @param i - The number to count bits for
90 * @returns The number of set bits
91 */
92function bitCount(i: number): number {
93 // Remove every second bit
94 i = i - ((i >>> 1) & 0x55555555);
95 // Sum pairs of bits
96 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
97 // Sum nibbles
98 i = (i + (i >>> 4)) & 0x0f0f0f0f;
99 // Sum bytes
100 i = i + (i >>> 8);
101 // Sum 16-bit values
102 i = i + (i >>> 16);
103 // Return only the relevant bits (max 32 bits means max count is 32, which fits in 6 bits)
104 return i & 0x3f;
105}
106
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
check
function is called, which:- Iterates through all possible subsets of coins:
2^n
subsets (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_left
useO(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. Misunderstanding the Problem Constraint
The most critical pitfall is misinterpreting what amounts can be formed. Many people initially think this is a standard coin change problem where you can mix different denominations (e.g., using one coin of value 3 and one coin of value 5 to make 8). However, the constraint explicitly states you can only use multiple coins of the same denomination for any given amount.
Wrong interpretation: With coins = [3, 5]
, thinking you can make 8 (3 + 5)
Correct interpretation: You cannot make 8 because it requires mixing denominations
2. 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).
3. 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
Without the overflow check, you might:
- Waste computation on LCMs that are already too large
- Risk integer overflow in extreme cases
Solution: Always check if lcm_value > max_value: break
after computing each LCM step.
4. Incorrect Binary Search Bounds
Using too small an upper bound for binary search:
# WRONG - Upper bound too small
return bisect_left(range(10**6), True, key=count_multiples_up_to)
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.
5. Off-by-One Error in Binary Search
Confusing when to use bisect_left
vs bisect_right
:
# WRONG - Using bisect_right when we need the first valid position
return bisect_right(range(10**11), True, key=count_multiples_up_to)
Since we want the smallest value where the count is at least k, we need bisect_left
to find the leftmost position where the condition becomes true.
6. Not Understanding Why This Approach Works
Many might wonder why we're counting multiples using inclusion-exclusion instead of generating and sorting all possible amounts. The key insight is that:
- We're counting how many valid amounts exist up to a given value
- Binary search finds the exact value where this count equals k
- This avoids explicitly generating all amounts, which would be inefficient
Example to clarify: With coins = [3, 5]
and checking up to 10:
- Multiples of 3: {3, 6, 9} → count = 3
- Multiples of 5: {5, 10} → count = 2
- Multiples of lcm(3,5)=15: {} → count = 0
- Total using inclusion-exclusion: 3 + 2 - 0 = 5 valid amounts ≤ 10
In a binary min heap, the maximum element can be found in:
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!