Skip to content
2 changes: 2 additions & 0 deletions docs/PROBLEMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
214 changes: 214 additions & 0 deletions leetcode/medium/0300-longest-increasing-subsequence/POST_MORTEM.md
Original file line number Diff line number Diff line change
@@ -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

---
44 changes: 44 additions & 0 deletions leetcode/medium/0300-longest-increasing-subsequence/README.md
Original file line number Diff line number Diff line change
@@ -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?
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle empty input before taking max

When nums is empty, dp is also empty and Math.max(...dp) returns -Infinity, which is not a valid LIS length and can propagate incorrect results for callers outside LeetCode’s constrained input set. Adding an early return (typically 0) for length === 0 prevents this incorrect sentinel value from escaping.

Useful? React with 👍 / 👎.

}
Loading