Language Selection
Choose your preferred programming language
Problem Statement
You are given:
baseCosts: costs of different base ice creams (choose exactly one base)toppingCosts: costs of different toppings (for each topping, you may choose 0, 1, or 2 of it)target: desired total cost
Return the total cost of a dessert that is closest to target.
Tie-breaker:
- If two totals are equally close to
target, return the smaller total.
Input / Output Format
Input
baseCosts: integer arraytoppingCosts: integer arraytarget: integer
Output
- integer: closest achievable total cost
Constraints (LeetCode #1774)
1 <= baseCosts.length <= 101 <= toppingCosts.length <= 101 <= baseCosts[i], toppingCosts[i] <= 10^41 <= target <= 10^4
Examples
Example 1
Input
baseCosts = [1,7]toppingCosts = [3,4]target = 10
Explanation Possible totals:
- Base 7 + topping 3 = 10 (exact)
Return
10.
Example 2
Input
baseCosts = [2,3]toppingCosts = [4,5,100]target = 18
Explanation Try combinations (0/1/2 of each topping). One close option:
- Base 3 + 5 + 5 + 4 = 17 (distance 1)
Other nearby totals might be 18? Not achievable here; so return
17.
Example 3 (Tie-breaker)
Input
baseCosts = [3]toppingCosts = [2]target = 4
Explanation Possible totals:
- 3 (diff 1)
- 5 (3 + 2, diff 1)
Both are equally close; return smaller =>
3.
Edge Example (Already above target)
Input
baseCosts = [10]toppingCosts = [1]target = 1
Explanation
You must pick one base, so minimum total is 10. Return 10.
Approach 1: Brute Force
Idea
Enumerate all topping selections (each topping has 3 choices: 0, 1, 2), compute every possible topping sum, then combine with each base cost and pick the best total by:
- minimal
abs(total - target) - if tie, minimal
total
Since toppingCosts.length <= 10, total topping combinations are 3^10 = 59049, which is manageable.
Algorithm
- Generate all possible topping sums via DFS/backtracking:
- For topping
i, branch into adding0,1*topping[i],2*topping[i]
- For topping
- For each base
band each topping sums, evaluate totalb + s. - Track the best total using the tie-breaking rule.
Python Implementation (Brute Force)
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
topping_sums = []
def dfs(i: int, curr: int) -> None:
if i == len(toppingCosts):
topping_sums.append(curr)
return
cost = toppingCosts[i]
dfs(i + 1, curr) # 0 of this topping
dfs(i + 1, curr + cost) # 1 of this topping
dfs(i + 1, curr + 2 * cost)# 2 of this topping
dfs(0, 0)
best = None
best_diff = float("inf")
for b in baseCosts:
for s in topping_sums:
total = b + s
diff = abs(total - target)
if diff < best_diff or (diff == best_diff and (best is None or total < best)):
best_diff = diff
best = total
return best
Complexity
- Number of topping sums:
3^mwherem = len(toppingCosts) <= 10 - Time:
O(3^m * n)wheren = len(baseCosts) <= 10→ at most ~590k evaluations - Space:
O(3^m)to store topping sums
Approach 2: Optimized Solution (Pruning DFS)
When/Why Optimize?
Brute force is already fine for constraints, but in interviews you can show pruning to reduce work and demonstrate search optimization.
Key Optimization
Instead of generating all topping sums first, do DFS per base and prune:
- Maintain a global best answer.
- If current cost is already above target, adding more toppings only increases cost. However, you might still need to consider “slightly above” costs because they could be closest.
So we can prune whencurr - targetis already worse than current best difference and all future additions are non-negative (they are).
Algorithm
- Initialize
bestas the minimum base cost (or any base). - For each base
b, DFS over toppings:- At each node, update
bestwithcurr. - If
curr > targetandcurr - target > abs(best - target)then prune (cannot improve by adding more). - Recurse with choices
+0,+t,+2t.
- At each node, update
This keeps correctness and often explores far fewer nodes.
Python (Optimized DFS)
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
best = min(baseCosts)
def better(x: int, y: int) -> int:
# return the better of x and y according to problem rules
dx, dy = abs(x - target), abs(y - target)
if dx != dy:
return x if dx < dy else y
return x if x < y else y
def dfs(i: int, curr: int) -> None:
nonlocal best
best = better(best, curr)
# Prune: if we're already above target and worse than best, adding more won't help
if curr > target and curr - target > abs(best - target):
return
if i == len(toppingCosts):
return
t = toppingCosts[i]
dfs(i + 1, curr) # 0
dfs(i + 1, curr + t) # 1
dfs(i + 1, curr + 2 * t) # 2
for b in baseCosts:
dfs(0, b)
return best
Java (Optimized DFS)
import java.util.*;
class Solution {
private int target;
private int best;
private int[] topping;
private int better(int a, int b) {
int da = Math.abs(a - target), db = Math.abs(b - target);
if (da != db) return da < db ? a : b;
return Math.min(a, b);
}
private void dfs(int i, int curr) {
best = better(best, curr);
if (curr > target && curr - target > Math.abs(best - target)) return;
if (i == topping.length) return;
int t = topping[i];
dfs(i + 1, curr);
dfs(i + 1, curr + t);
dfs(i + 1, curr + 2 * t);
}
public int closestCost(int[] baseCosts, int[] toppingCosts, int target) {
this.target = target;
this.topping = toppingCosts;
best = baseCosts[0];
for (int b : baseCosts) best = Math.min(best, b);
for (int b : baseCosts) dfs(0, b);
return best;
}
}
Go (Optimized DFS)
package main
import "math"
func closestCost(baseCosts []int, toppingCosts []int, target int) int {
best := baseCosts[0]
for _, b := range baseCosts {
if b < best {
best = b
}
}
better := func(a, b int) int {
da := int(math.Abs(float64(a - target)))
db := int(math.Abs(float64(b - target)))
if da != db {
if da < db {
return a
}
return b
}
if a < b {
return a
}
return b
}
var dfs func(i int, curr int)
dfs = func(i int, curr int) {
best = better(best, curr)
if curr > target && curr-target > int(math.Abs(float64(best-target))) {
return
}
if i == len(toppingCosts) {
return
}
t := toppingCosts[i]
dfs(i+1, curr)
dfs(i+1, curr+t)
dfs(i+1, curr+2*t)
}
for _, b := range baseCosts {
dfs(0, b)
}
return best
}
JavaScript (Optimized DFS)
var closestCost = function(baseCosts, toppingCosts, target) {
let best = Math.min(...baseCosts);
function better(a, b) {
const da = Math.abs(a - target), db = Math.abs(b - target);
if (da !== db) return da < db ? a : b;
return Math.min(a, b);
}
function dfs(i, curr) {
best = better(best, curr);
if (curr > target && (curr - target) > Math.abs(best - target)) return;
if (i === toppingCosts.length) return;
const t = toppingCosts[i];
dfs(i + 1, curr);
dfs(i + 1, curr + t);
dfs(i + 1, curr + 2 * t);
}
for (const b of baseCosts) dfs(0, b);
return best;
};
C# (Optimized DFS)
using System;
public class Solution {
private int target;
private int best;
private int[] topping;
private int Better(int a, int b) {
int da = Math.Abs(a - target), db = Math.Abs(b - target);
if (da != db) return da < db ? a : b;
return Math.Min(a, b);
}
private void Dfs(int i, int curr) {
best = Better(best, curr);
if (curr > target && curr - target > Math.Abs(best - target)) return;
if (i == topping.Length) return;
int t = topping[i];
Dfs(i + 1, curr);
Dfs(i + 1, curr + t);
Dfs(i + 1, curr + 2 * t);
}
public int ClosestCost(int[] baseCosts, int[] toppingCosts, int target) {
this.target = target;
this.topping = toppingCosts;
best = baseCosts[0];
foreach (var b in baseCosts) best = Math.Min(best, b);
foreach (var b in baseCosts) Dfs(0, b);
return best;
}
}
Complexity
Worst-case remains:
- Time:
O(n * 3^m) - Space:
O(m)recursion depth (no need to store all sums) In practice, pruning often reduces explored states.
Key Insights
- Each topping has three discrete choices:
{0, 1, 2}→ classic DFS/backtracking with branching factor 3. - Constraints are small (
m <= 10), making3^mfeasible. - The tie-breaker (“if equally close, choose smaller”) must be enforced everywhere you compare candidates.
- Pruning works because topping costs are non-negative: once you overshoot target by more than the best known difference, adding more can’t improve closeness.
Edge Cases
- Exact match exists (return target).
- All bases already exceed target; must still pick one base (answer is smallest base or best by closeness if toppings can’t reduce).
- Tie in distance: e.g., totals
target-1andtarget+1→ returntarget-1. toppingCostsempty (not per constraints here, but good to handle conceptually): answer is closest base.- Large topping values where only 0 helps; ensure DFS still considers “skip topping” path.
- Multiple bases; best may come from a non-min base due to topping combinations.
Test Cases (Python)
import unittest
class TestClosestDessertCost(unittest.TestCase):
def test_example1(self):
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
best = min(baseCosts)
def better(x: int, y: int) -> int:
dx, dy = abs(x - target), abs(y - target)
if dx != dy:
return x if dx < dy else y
return x if x < y else y
def dfs(i: int, curr: int) -> None:
nonlocal best
best = better(best, curr)
if curr > target and curr - target > abs(best - target):
return
if i == len(toppingCosts):
return
t = toppingCosts[i]
dfs(i + 1, curr)
dfs(i + 1, curr + t)
dfs(i + 1, curr + 2 * t)
for b in baseCosts:
dfs(0, b)
return best
self.assertEqual(Solution().closestCost([1,7], [3,4], 10), 10)
def test_tie_break(self):
# base 3, topping 2, target 4: totals 3 and 5 both diff 1 => return 3
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
best = min(baseCosts)
def better(a, b):
da, db = abs(a-target), abs(b-target)
if da != db: return a if da < db else b
return a if a < b else b
def dfs(i, curr):
nonlocal best
best = better(best, curr)
if curr > target and curr - target > abs(best - target): return
if i == len(toppingCosts): return
t = toppingCosts[i]
dfs(i+1, curr)
dfs(i+1, curr+t)
dfs(i+1, curr+2*t)
for b in baseCosts: dfs(0, b)
return best
self.assertEqual(Solution().closestCost([3], [2], 4), 3)
def test_all_bases_above_target(self):
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
best = min(baseCosts)
def better(a, b):
da, db = abs(a-target), abs(b-target)
if da != db: return a if da < db else b
return a if a < b else b
def dfs(i, curr):
nonlocal best
best = better(best, curr)
if curr > target and curr - target > abs(best - target): return
if i == len(toppingCosts): return
t = toppingCosts[i]
dfs(i+1, curr)
dfs(i+1, curr+t)
dfs(i+1, curr+2*t)
for b in baseCosts: dfs(0, b)
return best
self.assertEqual(Solution().closestCost([10], [1], 1), 10)
def test_exact_with_double_topping(self):
# base 5 + 2*3 = 11
from typing import List
class Solution:
def closestCost(self, baseCosts: List[int], toppingCosts: List[int], target: int) -> int:
best = min(baseCosts)
def better(a, b):
da, db = abs(a-target), abs(b-target)
if da != db: return a if da < db else b
return a if a < b else b
def dfs(i, curr):
nonlocal best
best = better(best, curr)
if curr > target and curr - target > abs(best - target): return
if i == len(toppingCosts): return
t = toppingCosts[i]
dfs(i+1, curr)
dfs(i+1, curr+t)
dfs(i+1, curr+2*t)
for b in baseCosts: dfs(0, b)
return best
self.assertEqual(Solution().closestCost([5], [3], 11), 11)
if __name__ == "__main__":
unittest.main()
Common Mistakes
- Forgetting the tie-breaker: when distances are equal, must return the smaller total.
- Accidentally allowing “no base chosen” (you must pick exactly one base).
- Not exploring the “0 topping” choice for each topping.
- Using pruning that is too aggressive (e.g., pruning immediately when
curr > targetwithout comparing against best). - Integer overflow concerns in some languages (less likely here, but still keep totals in
int/longappropriately).
Interview Tips
- Start by restating: “One base, each topping 0/1/2, minimize absolute difference, tie to smaller.”
- Call out the key observation:
m <= 10so3^mis feasible; propose DFS/backtracking. - Explain tie-break logic as a helper
better(a,b)comparator. - If asked to optimize, introduce pruning based on current best difference once you exceed target.
- Discuss complexity confidently: worst-case
O(n * 3^m)and why that’s acceptable under constraints. - Mention alternative viewpoint: generate all topping sums first (meet-in-the-middle is possible, but unnecessary here).