Greedy vs DP How to Know Which to Apply in DSA Go - Complexity Comparison
We want to understand how to decide between greedy and dynamic programming (DP) approaches by looking at their time costs.
Which method runs faster and when should we pick one over the other?
Analyze the time complexity of these two approaches solving the same problem.
// Greedy approach example
func GreedySolution(items []int) int {
total := 0
for _, item := range items {
if item > 0 {
total += item
}
}
return total
}
// DP approach example
func DPSolution(items []int) int {
n := len(items)
dp := make([]int, n+1)
for i := 1; i <= n; i++ {
dp[i] = max(dp[i-1], dp[i-1]+items[i-1])
}
return dp[n]
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
The greedy code sums positive items quickly. The DP code builds solutions step-by-step remembering past results.
Look at loops and repeated calculations.
- Greedy primary operation: One loop over all items.
- Greedy loop count: Runs once for each item (n times).
- DP primary operation: One loop filling dp array with n steps.
- DP loop count: Runs once for each item (n times), but does extra work storing and comparing.
Both methods loop through the input once, but DP does more work inside the loop.
| Input Size (n) | Approx. Operations Greedy | Approx. Operations DP |
|---|---|---|
| 10 | 10 simple adds | 10 compares and stores |
| 100 | 100 simple adds | 100 compares and stores |
| 1000 | 1000 simple adds | 1000 compares and stores |
Pattern observation: Both grow linearly, but DP does slightly more work per item.
Time Complexity: O(n)
This means both methods take time proportional to the number of items, but DP has extra overhead.
[X] Wrong: "Greedy is always faster and better than DP."
[OK] Correct: Greedy is faster only when it works correctly; DP handles more complex cases but with some extra cost.
Knowing when to pick greedy or DP shows you understand problem patterns and efficiency, a key skill interviewers look for.
"What if the DP solution used recursion with memoization instead of iteration? How would the time complexity change?"
Practice
Solution
Step 1: Understand greedy choice property
A greedy algorithm works when choosing the best option at each step leads to a global optimum.Step 2: Check optimal substructure
Optimal substructure means the optimal solution can be built from optimal solutions of subproblems.Final Answer:
When the problem has the greedy choice property and optimal substructure -> Option DQuick Check:
Greedy = greedy choice + optimal substructure [OK]
- Confusing overlapping subproblems with greedy applicability
- Using greedy when problem needs full exploration
- Ignoring optimal substructure requirement
Solution
Step 1: Understand Go map declaration
In Go, maps are declared with make(map[keyType]valueType) to initialize properly.Step 2: Check each option
memo := make(map[int]int) uses make with correct syntax. memo := []int{} is a slice, not a map. memo := map[]int{} has invalid syntax. memo := new(map[int]int) uses new which returns pointer, not initialized map.Final Answer:
memo := make(map[int]int) -> Option BQuick Check:
Use make(map[key]value) for maps [OK]
- Using new(map[int]int) which returns nil map pointer
- Confusing slices with maps
- Incorrect map syntax like map[]int{}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
weights := []int{1, 3, 4, 5}
values := []int{1, 4, 5, 7}
capacity := 7
dp := make([]int, capacity+1)
for i := 0; i < len(weights); i++ {
for w := capacity; w >= weights[i]; w-- {
dp[w] = max(dp[w], dp[w-weights[i]] + values[i])
}
}
fmt.Println(dp[capacity])
}Solution
Step 1: Understand the code - 0/1 Knapsack DP
The code uses bottom-up DP to find max value for capacity 7 with given weights and values.Step 2: Trace DP array updates
Items: (1,1), (3,4), (4,5), (5,7). DP updates from capacity down to weight. Final dp[7] = max value achievable is 11.Final Answer:
11 -> Option CQuick Check:
DP knapsack max value = 11 [OK]
- Iterating w from low to high causing wrong results
- Confusing weights and values arrays
- Forgetting to update dp[w] with max
func coinChange(coins []int, amount int) int {
count := 0
for _, coin := range coins {
count += amount / coin
amount %= coin
}
if amount != 0 {
return -1
}
return count
}
func main() {
coins := []int{1, 3, 4}
amount := 6
fmt.Println(coinChange(coins, amount))
}Solution
Step 1: Analyze greedy coin change logic
The greedy approach works only if coins are sorted descending to pick largest coin first.Step 2: Check coins order
Coins are {1,3,4} ascending, so greedy picks 1 first, leading to wrong minimal count.Final Answer:
Coins slice is not sorted descending -> Option AQuick Check:
Sort coins descending for greedy coin change [OK]
- Not sorting coins descending
- Assuming greedy always works for coin change
- Ignoring leftover amount check
Solution
Step 1: Check greedy choice property for coin set
For coins {1,5,6}, greedy picks 6 then 5, total 2 coins for 11, which is minimal. However, greedy does not always work for all amounts with these coins.Step 2: Consider counterexamples and DP suitability
In some cases greedy fails (e.g., amount 7 with coins {1,5,6} greedy picks 6+1=2 coins but better is 1+1+5=3 coins, so greedy works here but not always. DP always finds minimal coins by exploring all subproblems.Final Answer:
Dynamic Programming, because greedy fails due to no greedy choice property -> Option AQuick Check:
Use DP if greedy choice property missing [OK]
- Assuming greedy always works for coin change
- Ignoring overlapping subproblems in coin change
- Choosing greedy because coins are sorted ascending
Ready to prove it?
Pass the test and this pattern moves unknown
