You are given three inputs:
baseCosts
: An array of integers, where each element represents the cost of a different base dessert.toppingCosts
: An array of integers, where each element represents the cost of a different topping type.target
: An integer representing the target cost you want to achieve or get as close as possible to.baseCosts
.toppingCosts
(i.e., for each topping, you can use it 0, 1, or 2 times).target
. If there are multiple costs equally close, return the lower cost.
At first glance, the problem seems to suggest generating all possible combinations of bases and toppings, then picking the one closest to target
. However, this brute-force approach could be inefficient, especially as the number of toppings increases (since each topping has three choices: 0, 1, or 2 times).
To optimize, we notice:
target
.target
, or equally close but lower, we update our answer.We solve the problem using the following steps:
target
than our best so far (or, if equally close, is lower). If so, update our answer.
target
(optional), we can skip further recursion for efficiency.
This approach is efficient because:
Example Input:
baseCosts = [1, 7]
toppingCosts = [3, 4]
target = 10
This process ensures we check every valid combination and pick the best.
Brute-force approach:
The main factor is the number of toppings, since each can be used 0, 1, or 2 times, giving 3n combinations.
The Closest Dessert Cost problem is a classic combinatorial optimization challenge. By systematically trying all combinations of bases and toppings (using recursion or iteration), and always tracking the closest total to the target, we guarantee finding the best answer. The solution is made efficient by limiting the number of topping types and by using recursion to avoid unnecessary computations. The key insight is that with small input sizes, exploring all possible topping combinations is both feasible and effective.
class Solution:
def closestCost(self, baseCosts, toppingCosts, target):
self.best = None
def dfs(i, cost):
# At each topping, decide 0, 1, or 2 times
if i == len(toppingCosts):
# Update best if this cost is closer (or equally close but lower)
if (self.best is None or
abs(cost - target) < abs(self.best - target) or
(abs(cost - target) == abs(self.best - target) and cost < self.best)):
self.best = cost
return
# Try using this topping 0, 1, or 2 times
for cnt in range(3):
dfs(i + 1, cost + cnt * toppingCosts[i])
for base in baseCosts:
dfs(0, base)
return self.best
class Solution {
public:
int best;
int target;
void dfs(const vector<int>& toppingCosts, int i, int cost) {
if (i == toppingCosts.size()) {
if (best == -1 ||
abs(cost - target) < abs(best - target) ||
(abs(cost - target) == abs(best - target) && cost < best)) {
best = cost;
}
return;
}
for (int cnt = 0; cnt <= 2; ++cnt) {
dfs(toppingCosts, i + 1, cost + cnt * toppingCosts[i]);
}
}
int closestCost(vector<int>& baseCosts, vector<int>& toppingCosts, int target) {
this->target = target;
best = -1;
for (int base : baseCosts) {
dfs(toppingCosts, 0, base);
}
return best;
}
};
class Solution {
int best;
int target;
void dfs(int[] toppingCosts, int i, int cost) {
if (i == toppingCosts.length) {
if (best == -1 ||
Math.abs(cost - target) < Math.abs(best - target) ||
(Math.abs(cost - target) == Math.abs(best - target) && cost < best)) {
best = cost;
}
return;
}
for (int cnt = 0; cnt <= 2; cnt++) {
dfs(toppingCosts, i + 1, cost + cnt * toppingCosts[i]);
}
}
public int closestCost(int[] baseCosts, int[] toppingCosts, int target) {
this.target = target;
best = -1;
for (int base : baseCosts) {
dfs(toppingCosts, 0, base);
}
return best;
}
}
var closestCost = function(baseCosts, toppingCosts, target) {
let best = null;
function dfs(i, cost) {
if (i === toppingCosts.length) {
if (
best === null ||
Math.abs(cost - target) < Math.abs(best - target) ||
(Math.abs(cost - target) === Math.abs(best - target) && cost < best)
) {
best = cost;
}
return;
}
for (let cnt = 0; cnt <= 2; cnt++) {
dfs(i + 1, cost + cnt * toppingCosts[i]);
}
}
for (let base of baseCosts) {
dfs(0, base);
}
return best;
};