diff --git a/docs/PROBLEMS.md b/docs/PROBLEMS.md index 8751290..cbe27c0 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) +- [0300 - Longest Increasing Subsequence](../leetcode/medium/0300-longest-increasing-subsequence) ![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) @@ -295,6 +296,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 +- [0300 - Longest Increasing Subsequence](../leetcode/medium/0300-longest-increasing-subsequence) - Medium - [0322 - Coin Change](../leetcode/medium/0322-coin-change) - Medium ### Graphs (BFS/DFS) diff --git a/leetcode/medium/0300-longest-increasing-subsequence/POST_MORTEM.md b/leetcode/medium/0300-longest-increasing-subsequence/POST_MORTEM.md new file mode 100644 index 0000000..28a8031 --- /dev/null +++ b/leetcode/medium/0300-longest-increasing-subsequence/POST_MORTEM.md @@ -0,0 +1,214 @@ +# Interview Debrief — Longest Increasing Subsequence + +**Date:** 2026-02-17 +**Problem:** [Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/) — Medium +**Duration:** ~90 minutes +**Mode:** Guided TDD (Coach Mode) + +--- + +## Session Overview + +The candidate worked through LIS using a guided TDD approach across 6 cycles. The +core algorithm was identified and implemented correctly in cycle 1 with no hints +needed — a strong signal. The main gap was in articulating invariants precisely: +the candidate's first invariant statement described loop bounds rather than the DP +state property. After two rounds of follow-up questions they landed on the correct +statement. Test ownership progressed from L1 through L3 with growing confidence, +and the candidate authored two full `it()` blocks in cycles 5-6 with sound reasoning. + +--- + +## Problem Solving — Score: 4/5 + +**Strengths:** + +- Immediately identified Dynamic Programming as the right pattern and correctly + named `dp[i]` as the key state variable (LIS length ending at index i) without + any hints. +- Translated the recurrence `dp[i] = max(dp[i], dp[j] + 1)` into working nested + loop code on the first attempt — no false starts or approach pivots needed. + +**Areas for Improvement:** + +- The initial invariant statement — "we are still in bounds of iterating over the + array" — described a loop guard, not the DP property. Needed two follow-up + questions before arriving at "`dp[i]` always equals the length of the longest + strictly increasing subsequence ending at `nums[i]`, with a minimum of 1." + Being able to state this upfront, unprompted, is the mark of strong DP fluency. + +**Help Needed:** Minimal — no algorithm hints required. One L1 checkpoint +clarification on the invariant definition. + +--- + +## Coding — Score: 4/5 + +**Strengths:** + +- Clean, idiomatic TypeScript. `const dp: number[] = new Array(length).fill(1)` + initialises the base case correctly in one line. +- The nested loop structure (`for i`, inner `for j < i`) with the `Math.max` + accumulation is textbook and readable. +- `return Math.max(...dp)` is concise and correct. + +**Areas for Improvement:** + +- Minor: `expect` lines in authored tests were missing semicolons in cycles 5 and 6. Consistent punctuation matters in real codebases and under review. + +**Debugging Process:** + +- Not tested in this session — the implementation was correct on the first + attempt. Would want to see a debugging cycle in a future session to evaluate + systematic fault-finding. + +--- + +## Verification — Score: 3/5 + +**Strengths:** + +- Correctly anticipated that a fully descending array returns 1 and explained why: + "each time the input is not increasing so the value is 1 since it is the only + element that is increasing." Shows understanding of how the strict inequality + gates the recurrence. +- Proposed the single-element and already-sorted cases without prompting — good + boundary instinct. + +**Areas for Improvement:** + +- Did not propose a negative-numbers test case. The constraints allow + `-10^4 <= nums[i] <= 10^4` and the algorithm handles negatives correctly, but + explicitly testing it would show awareness of the full input domain. +- Did not consider a two-element case (e.g. `[1, 2]` → 2, `[2, 1]` → 1) as a + minimal non-trivial input — useful for catching off-by-one errors in early + implementations. + +--- + +## Communication — Score: 3/5 + +**Strengths:** + +- Gave a coherent high-level description of the pattern on the first checkpoint: + "problems can build off of sub problems that were previously solved" — correct + framing of DP. +- Explained the complexity reasoning clearly when asked: named O(n²) time and + O(n) space with a correct, if informal, justification. + +**Areas for Improvement:** + +- The initial invariant response was vague ("we are still in bounds") and required + prompting. In a real interview, the interviewer would not ask follow-up + questions; the candidate would be expected to state the invariant precisely + before coding. +- Complexity explanation said "iterates over i twice" rather than describing the + triangular scan. Aim for: "for each of n elements, we scan up to i previous + elements, giving n(n-1)/2 comparisons — O(n²)." + +--- + +## Complexity Analysis — Score: 3/5 + +**Strengths:** + +- Correctly identified O(n²) time and O(n) space without prompting. +- Named the right variables (dp array → space, nested loop → time). + +**Areas for Improvement:** + +- The "why" for time complexity was slightly imprecise: "iterates over i twice" + suggests two independent passes rather than a nested triangular scan. The + distinction matters when reasoning about amortised or tighter bounds. +- Did not mention the O(n log n) follow-up variant (patience sorting / binary + search). Awareness of the optimal approach and when it matters is a + differentiator at senior levels. + +--- + +## Pattern Recognition + +- **Pattern Used:** 1D Dynamic Programming (bottom-up, tabulation) +- **Key Insight:** Every element is both a valid single-element subsequence (base + case: `dp[i] = 1`) and a potential extension of any shorter increasing + subsequence ending before it. The recurrence `dp[i] = max(dp[i], dp[j] + 1)` + for all `j < i` where `nums[j] < nums[i]` captures all possibilities in O(n²). +- **Pattern Checkpoint Results:** 1/1 correct after two follow-up questions (PASS + with guidance) +- **Related Problems to Practice:** + - [300 follow-up — O(n log n) LIS](https://leetcode.com/problems/longest-increasing-subsequence/) + — Same problem, patience sorting approach; builds deeper understanding of the + same pattern with a different data structure (tails array + binary search) + - [1143 — Longest Common Subsequence](https://leetcode.com/problems/longest-common-subsequence/) + — 2D DP extension of the same "extend or reset" recurrence thinking; a natural + next step + - [354 — Russian Doll Envelopes](https://leetcode.com/problems/russian-doll-envelopes/) + — LIS applied to 2D pairs after sorting; harder variant that tests whether you + understand the pattern deeply enough to apply it in disguise + +--- + +## Session Progression + +| Cycle | What Was Tested | Ownership | Hints | Checkpoint | Notes | +| ----- | ---------------------------- | --------- | ----- | -------------------- | ------------------------------------------------- | +| 1 | Example 1: mixed array → 4 | L1 | 0 | PASS (with guidance) | Implemented full algorithm in one cycle; no hints | +| 2 | Example 2: mixed array → 4 | L1 | 0 | SKIP | Passed immediately; noted over-implementation | +| 3 | All identical elements → 1 | L2 | 0 | SKIP | Proposed case correctly; missed assertion sketch | +| 4 | Single element → 1 | L2 | 0 | SKIP | Full assertion provided; clean L2 contribution | +| 5 | Already sorted ascending → n | L3 | 0 | SKIP | First full it() block; minor missing semicolon | +| 6 | Fully descending → 1 | L3 | 0 | SKIP | Correct reasoning explained unprompted; good L3 | + +--- + +## No-Hire Trigger Check + +- **Critical Triggers Observed:** none +- **Recovery Evidence:** n/a +- **Guardrail Applied:** none + +--- + +## Overall Assessment + +**Interview Recommendation:** Lean Recommend +**Hiring Decision:** Lean Hire +**Would Move Forward to Next Round:** Yes +**Confidence:** Medium + +**Summary:** +The candidate demonstrated a solid foundation in dynamic programming — pattern +identification was immediate, implementation was clean and correct, and the test +suite grew purposefully through the session. The primary gap is in invariant +articulation: arriving at the right answer through guiding questions is different +from stating it fluently before touching the keyboard, which is what a real +interview expects. With deliberate practice on explaining DP state definitions +precisely, this candidate is well-positioned for a strong performance. + +**Strengths to Build On:** + +- Fast, correct pattern identification with no algorithm hints needed +- Clean, readable TypeScript that translates intent directly into code +- Good boundary awareness (sorted, descending, single element) in test design + +**Priority Areas for Growth:** + +1. **Invariant articulation** — Before coding any DP solution, practice stating + in one sentence: "At all times, `dp[i]` equals \_\_\_." Do this before opening + the editor. +2. **Complexity precision** — Practise explaining O(n²) as "a triangular scan: n + elements, each looking back at up to i predecessors" rather than "nested loop." +3. **Full input domain testing** — Habit-build checking negative numbers, mixed + signs, and two-element arrays as instinctive edge cases. + +**Recommended Next Steps:** + +- Solve [1143 — Longest Common Subsequence](https://leetcode.com/problems/longest-common-subsequence/) + next — it forces 2D DP state definition and is a direct extension of this + session's recurrence thinking +- Attempt the O(n log n) LIS variant to deepen understanding of the patience + sorting insight +- Before each DP problem, write out the state definition and invariant as a + comment before writing any code — treat it as a mandatory first step + +--- diff --git a/leetcode/medium/0300-longest-increasing-subsequence/README.md b/leetcode/medium/0300-longest-increasing-subsequence/README.md new file mode 100644 index 0000000..8fcd2c8 --- /dev/null +++ b/leetcode/medium/0300-longest-increasing-subsequence/README.md @@ -0,0 +1,44 @@ +# 300. Longest Increasing Subsequence + +[![Medium](https://img.shields.io/badge/Medium-orange)](https://leetcode.com/problems/longest-increasing-subsequence/) + +[LeetCode Problem](https://leetcode.com/problems/longest-increasing-subsequence/) + +## Problem + +Given an integer array `nums`, return the length of the **longest strictly increasing subsequence**. + +A **subsequence** is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements. + +## Examples + +**Example 1:** + +```text +Input: nums = [10,9,2,5,3,7,101,18] +Output: 4 +Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. +``` + +**Example 2:** + +```text +Input: nums = [0,1,0,3,2,3] +Output: 4 +``` + +**Example 3:** + +```text +Input: nums = [7,7,7,7,7,7,7] +Output: 1 +``` + +## Constraints + +- `1 <= nums.length <= 2500` +- `-10^4 <= nums[i] <= 10^4` + +## Follow-up + +Can you come up with an algorithm that runs in O(n log n) time complexity? diff --git a/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.test.ts b/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.test.ts new file mode 100644 index 0000000..705b937 --- /dev/null +++ b/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { lengthOfLIS } from './longest-increasing-subsequence'; + +describe('Longest Increasing Subsequence', () => { + it('returns 4 for [10,9,2,5,3,7,101,18] (example 1)', () => { + expect(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])).toBe(4); + }); + + it('returns 4 for [0,1,0,3,2,3] (example 2)', () => { + expect(lengthOfLIS([0, 1, 0, 3, 2, 3])).toBe(4); + }); + + it('returns 1 when all elements are identical', () => { + expect(lengthOfLIS([1, 1, 1, 1, 1, 1])).toBe(1); + }); + + it('returns 1 for a single element array', () => { + expect(lengthOfLIS([42])).toBe(1); + }); + + it('returns the length of the input array when already sorted', () => { + expect(lengthOfLIS([1, 2, 3, 4, 5])).toBe(5); + }); + + it('should return 1 when the input is fully descending', () => { + expect(lengthOfLIS([5, 4, 3, 2, 1])).toBe(1); + }); +}); diff --git a/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.ts b/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.ts new file mode 100644 index 0000000..05ebe15 --- /dev/null +++ b/leetcode/medium/0300-longest-increasing-subsequence/longest-increasing-subsequence.ts @@ -0,0 +1,14 @@ +export function lengthOfLIS(nums: number[]): number { + const length = nums.length; + const dp: number[] = new Array(length).fill(1); + + for (let i = 0; i < length; i++) { + for (let j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + + return Math.max(...dp); +}