diff --git a/docs/PROBLEMS.md b/docs/PROBLEMS.md index 2e128b5..8751290 100644 --- a/docs/PROBLEMS.md +++ b/docs/PROBLEMS.md @@ -179,6 +179,7 @@ Complete list of all solved problems, organized by difficulty and topic. - [0072 - Edit Distance](../leetcode/medium/0072-edit-distance) ![Medium](https://img.shields.io/badge/Medium-orange) - [0139 - Word Break](../leetcode/medium/0139-word-break) ![Medium](https://img.shields.io/badge/Medium-orange) - [0198 - House Robber](../leetcode/medium/0198-house-robber) ![Medium](https://img.shields.io/badge/Medium-orange) +- [0322 - Coin Change](../leetcode/medium/0322-coin-change) ![Medium](https://img.shields.io/badge/Medium-orange) - [0413 - Arithmetic Slices](../leetcode/medium/0413-arithmetic-slices) ![Medium](https://img.shields.io/badge/Medium-orange) - [0718 - Maximum Length of Repeated Subarray](../leetcode/medium/0718-maximum-length-of-repeated-subarray) ![Medium](https://img.shields.io/badge/Medium-orange) - [1250 - Longest Common Subsequence](../leetcode/medium/1250-longest-common-subsequence) ![Medium](https://img.shields.io/badge/Medium-orange) @@ -294,6 +295,7 @@ Complete list of all solved problems, organized by difficulty and topic. - [0072 - Edit Distance](../leetcode/medium/0072-edit-distance) - Medium - [0139 - Word Break](../leetcode/medium/0139-word-break) - Medium - [0198 - House Robber](../leetcode/medium/0198-house-robber) - Medium +- [0322 - Coin Change](../leetcode/medium/0322-coin-change) - Medium ### Graphs (BFS/DFS) diff --git a/leetcode/medium/0322-coin-change/POST_MORTEM.md b/leetcode/medium/0322-coin-change/POST_MORTEM.md new file mode 100644 index 0000000..58941f9 --- /dev/null +++ b/leetcode/medium/0322-coin-change/POST_MORTEM.md @@ -0,0 +1,195 @@ +# Interview Debrief — Coin Change + +**Date:** 2026-02-13 +**Problem:** [322. Coin Change](https://leetcode.com/problems/coin-change/) — Medium +**Duration:** ~75 minutes (estimated based on 4 cycles) +**Mode:** Guided TDD (Coach Mode) + +**Duration Estimation:** + +- Cycle 1: ~10 minutes (base case) +- Cycle 2: ~35 minutes (full DP implementation + debugging initialization bug) +- Cycle 3: ~10 minutes (verification test) +- Cycle 4: ~10 minutes (learner-authored test) +- Discussion/pattern checkpoints: ~10 minutes + +--- + +## Session Overview + +The candidate demonstrated strong problem-solving ability and successfully implemented a classic unbounded knapsack DP solution. They correctly identified the Dynamic Programming pattern early, articulated the state representation and recurrence relation, and worked through a significant debugging challenge independently. Communication was clear and thoughtful, though complexity analysis needed refinement. Overall, this was a solid medium-problem performance with good debugging resilience. + +--- + +## Problem Solving + +### Score: 4/5 + +**Strengths:** + +- Correctly identified Dynamic Programming as the appropriate pattern in cycle 1 +- Understood the subproblem structure: `dp[i]` = minimum coins for amount `i` +- Correctly derived array size as `amount + 1` +- Grasped the recurrence relation `dp[i] = min(dp[i], dp[i - coin] + 1)` after guided questions +- Asked clarifying questions about constraints before proposing tests (showed good problem-solving discipline) + +**Areas for Improvement:** + +- Initially struggled to articulate the recurrence relation clearly (needed follow-up questions on what to add and how to choose minimum) +- Took 2-3 iterations to fully clarify what `dp[i]` stores and how to compute it + +**Help Needed:** Minimal — Level 1 guiding questions during pattern checkpoint in cycle 2. Once the recurrence relation was clear, implementation was independent. + +--- + +## Coding + +### Score: 3.5/5 + +**Strengths:** + +- Clean, readable code with proper TypeScript syntax +- Correctly implemented nested loops with proper bounds checking (`i - coin >= 0`) +- Used `Math.min()` appropriately for the DP recurrence +- Successfully wrote a learner-authored test in cycle 4 with clear naming + +**Areas for Improvement:** + +- Made a critical initialization bug in cycle 2: used `Number.MIN_SAFE_INTEGER` instead of `MAX_SAFE_INTEGER`, breaking the `Math.min()` logic +- Then attempted to fix with `-1` initialization, which also broke the logic for valid cases +- These mistakes suggest incomplete mental simulation of how `Math.min()` behaves with initial sentinel values + +**Debugging Process:** + +- After I pointed out the issue with `MIN_SAFE_INTEGER`, the candidate needed a concrete example (`coins = [2], amount = 2`) to see why it failed +- Attempted an alternative approach (`-1` initialization) that still had the same underlying issue +- Successfully corrected to `MAX_SAFE_INTEGER` after understanding that the sentinel must be larger than any possible answer +- Debugging was methodical but required guidance to see the root cause + +--- + +## Verification + +### Score: 4/5 + +**Strengths:** + +- Proactively asked about constraints (empty arrays, negative amounts) before proposing tests — excellent practice +- Chose strategic test cases: base case (amount = 0), impossible case (even coins, odd amount), optimal solution with multiple coins +- Test progression was logical and incremental +- Wrote a clear, descriptive learner-authored test in cycle 4 + +**Areas for Improvement:** + +- Did not catch the initialization bug through mental testing before running the code +- Could have traced through a simple example (e.g., `amount = 2`) manually to verify the logic + +--- + +## Communication + +### Score: 4.5/5 + +**Strengths:** + +- Asked thoughtful clarifying questions about constraints upfront +- Clearly articulated pattern understanding: "Dynamic programming... overlapping subproblems... minimum between current and previous" +- Responded well to follow-up questions, refining answers iteratively +- Asked excellent meta-question: "Should it be O(n²) if both loops iterate over the same value?" — shows active learning + +**Areas for Improvement:** + +- Initial explanations were sometimes vague (e.g., "store the current amount based on the coin") and needed refinement through Q&A + +--- + +## Complexity Analysis + +### Score: 3/5 + +**Strengths:** + +- Correctly identified space complexity as O(n) for the dp array +- Understood that nested loops contribute to time complexity +- After correction, grasped the distinction between O(n²) and O(n × m) and asked a great follow-up question to solidify understanding + +**Areas for Improvement:** + +- Initially stated time complexity as O(n²), missing that the inner loop iterates over `coins.length`, not `amount` +- Needed correction to understand that different iteration variables yield O(n × m), not O(n²) +- This is a common mistake but important to catch in interviews, especially for multi-variable DP problems + +--- + +## Pattern Recognition + +- **Pattern Used:** Dynamic Programming (Unbounded Knapsack variant) +- **Key Insight:** Build up solutions for amounts 0 to target by trying each coin and taking the minimum across all options. The recurrence `dp[i] = min(dp[i], dp[i - coin] + 1)` captures this optimally. +- **Pattern Checkpoint Results:** 2/2 cycles with pattern checkpoints (cycle 1: understood state structure; cycle 2: derived recurrence relation after guided questions) +- **Related Problems to Practice:** + - [518. Coin Change II](https://leetcode.com/problems/coin-change-ii/) — Count number of ways (not minimum) to make change + - [377. Combination Sum IV](https://leetcode.com/problems/combination-sum-iv/) — Order matters (permutations) + - [279. Perfect Squares](https://leetcode.com/problems/perfect-squares/) — Same pattern with perfect square "coins" + +--- + +## Session Progression + +The candidate progressed steadily through test-ownership levels, showing growing confidence and independence in both test design and implementation. + +| Cycle | What Was Tested | Test Ownership Level | Hint Level | Pattern Checkpoint | Notes | +| ----- | --------------------------------------------- | --------------------- | ---------- | ------------------ | ------------------------------------------------------------------------------- | +| 1 | Base case (amount = 0) | L1 (Test Intent) | 0 | PASS | Simple base case, good starting point | +| 2 | Impossible case (coins=[2], amount=3) | L1 (Test Intent) | 1 | PASS (after Q&A) | Full DP implementation; debugging cycle for initialization bug (MIN → -1 → MAX) | +| 3 | Optimal solution (coins=[1,2,5], amount=11) | L2 (Assertion Sketch) | 0 | N/A | Test already passed (implementation complete from cycle 2) | +| 4 | Impossible odd amount (coins=[2,4], amount=7) | L3 (Learner-Authored) | 0 | N/A | Candidate wrote full test independently | + +--- + +## No-Hire Trigger Check + +- **Critical Triggers Observed:** None +- **Recovery Evidence:** N/A — no critical triggers occurred +- **Guardrail Applied:** None + +The candidate did not exhibit any of the critical triggers: + +- ✅ Explained the core invariant (`dp[i]` = minimum coins for amount `i`) correctly +- ✅ Did not need Level 3 hints for core algorithm logic +- ✅ Explained final time/space complexity (with correction) +- ✅ Could justify why the solution is correct (DP recurrence builds optimal solutions) +- ✅ Debugging was methodical and converged on the correct fix +- ✅ No critical bugs left unresolved at session end + +--- + +## Overall Assessment + +**Interview Recommendation:** Recommend +**Hiring Decision:** Hire +**Would Move Forward to Next Round:** Yes +**Confidence:** High + +**Summary:** +The candidate successfully solved a medium-difficulty dynamic programming problem with minimal guidance. They identified the correct pattern, derived the recurrence relation, and implemented a clean solution. The initialization bug in cycle 2 was concerning but the candidate debugged it methodically with hints and arrived at the correct fix. Communication was strong, especially in asking clarifying questions about constraints and complexity nuances. I would move forward with this candidate to the next interview round. + +**Strengths to Build On:** + +- Asking about constraints and edge cases before diving into implementation +- Clear articulation of the DP pattern and state structure +- Methodical debugging approach when issues arise +- Strong test design (strategic, incremental, clear naming) + +**Priority Areas for Growth:** + +- **Mental simulation before coding:** Trace through a simple example manually to catch initialization bugs before running tests +- **Complexity analysis precision:** Be careful to distinguish between O(n²) and O(n × m) when loops iterate over different variables +- **Recurrence relation clarity:** Practice stating DP recurrences precisely upfront to reduce back-and-forth + +**Recommended Next Steps:** + +- Practice more unbounded knapsack problems to solidify the pattern (Coin Change II, Combination Sum IV, Perfect Squares) +- For each DP problem, write out the recurrence relation explicitly before coding +- Practice analyzing multi-variable time complexity (e.g., O(n × m), O(n × k × m)) to avoid O(n²) misclassifications + +--- diff --git a/leetcode/medium/0322-coin-change/README.md b/leetcode/medium/0322-coin-change/README.md new file mode 100644 index 0000000..7ae5206 --- /dev/null +++ b/leetcode/medium/0322-coin-change/README.md @@ -0,0 +1,51 @@ +# [322. Coin Change](https://leetcode.com/problems/coin-change/) + +![Medium](https://img.shields.io/badge/Medium-orange) + +## Problem Description + +You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money. + +Return the **fewest number of coins** that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`. + +You may assume that you have an **infinite number** of each kind of coin. + +## Examples + +### Example 1 + +**Input:** `coins = [1,2,5]`, `amount = 11` + +**Output:** `3` + +**Explanation:** 11 = 5 + 5 + 1 + +### Example 2 + +**Input:** `coins = [2]`, `amount = 3` + +**Output:** `-1` + +**Explanation:** Cannot form 3 using only coin 2. + +### Example 3 + +**Input:** `coins = [1]`, `amount = 0` + +**Output:** `0` + +**Explanation:** Zero amount requires zero coins. + +### Example 4 + +**Input:** `coins = [2,4]`, `amount = 7` + +**Output:** `-1` + +**Explanation:** Impossible to form odd amount using only even-valued coins. + +## Constraints + +- `1 <= coins.length <= 12` +- `1 <= coins[i] <= 2^31 - 1` +- `0 <= amount <= 10^4` diff --git a/leetcode/medium/0322-coin-change/coin-change.test.ts b/leetcode/medium/0322-coin-change/coin-change.test.ts new file mode 100644 index 0000000..4f9ef52 --- /dev/null +++ b/leetcode/medium/0322-coin-change/coin-change.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { coinChange } from './coin-change'; + +describe('Coin Change', () => { + it('should return 0 when amount is 0', () => { + expect(coinChange([1], 0)).toBe(0); + }); + + it('should return -1 when amount cannot be made with available coins', () => { + expect(coinChange([2], 3)).toBe(-1); + }); + + it('should return the minimum number of coins needed for a valid amount', () => { + expect(coinChange([1, 2, 5], 11)).toBe(3); + }); + + it('should return -1 when amount cannot be made with multiple available coins', () => { + expect(coinChange([2, 4], 7)).toBe(-1); + }); +}); diff --git a/leetcode/medium/0322-coin-change/coin-change.ts b/leetcode/medium/0322-coin-change/coin-change.ts new file mode 100644 index 0000000..005f9e5 --- /dev/null +++ b/leetcode/medium/0322-coin-change/coin-change.ts @@ -0,0 +1,18 @@ +export function coinChange(coins: number[], amount: number): number { + if (amount === 0) { + return 0; + } + + const dp = Array.from({ length: amount + 1 }, () => Number.MAX_SAFE_INTEGER); + dp[0] = 0; + + for (let i = 1; i <= amount; i++) { + for (const coin of coins) { + if (i - coin >= 0) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + } + + return dp[amount] === Number.MAX_SAFE_INTEGER ? -1 : dp[amount]; +}