Implementation - The Coder's Brain
Phase 1: The Representation Problem
Algorithm says: "Track which subsequences need which numbers"
Coder asks: "How do I REPRESENT subsequences in code?"
First Instinct (The Naive Coder)
"I'll create a Subsequence object!"
class Subsequence {
List<Integer> numbers;
void add(int x) { ... }
boolean needsNext(int x) { ... }
int length() { ... }
}
List<Subsequence> allSubsequences = new ArrayList<>();
Problem discovered immediately:
For each number x:
- Search through allSubsequences
- Find which ones end at x-1
- Pick one to extend
→ O(n²) time! (n numbers × searching list)
→ Memory bloat! (storing entire chains)
Phase 2: The Minimalist Question
Coder realizes: "Wait, what do I ACTUALLY need to know?"
Let's trace what the algorithm asks at each step:
When processing number x:
Question 1: "Can I extend something?"
→ Need to know: "Does any subsequence end at x-1?"
Question 2: "Can I start new?"
→ Need to know: "Do x+1 and x+2 exist unused?"
That's IT!
Key insight:
"I don't need to know WHICH subsequence, or WHAT'S IN IT—I only need COUNTS!"
Phase 3: The Data Structure Selection
Concern 1: Tracking unused numbers
Question: "How many copies of each number remain?"
Options:
Option A: Keep the original array, mark used ones
[1,2,3,3,4,5]
✓ ✓ ✓ ? ? ?
→ Hard to update, lots of scanning
Option B: Use a Counter/Frequency Map
freq = {1:1, 2:1, 3:2, 4:1, 5:1}
→ O(1) lookup and update!
Choice: HashMap<Integer, Integer> freq
Why HashMap?
- Key = the number itself
- Value = how many copies remain
- Operations needed:
- Check if available:
freq.get(x) > 0
- Use one:
freq.put(x, freq.get(x) - 1)
- Both O(1)!
- Check if available:
Concern 2: Tracking incomplete subsequences
Question: "Which subsequences are waiting for their next number?"
Mental model:
Imagine subsequences as "hungry mouths" 🍔
[1,2] is waiting, mouth open for 3
[3,4] is waiting, mouth open for 5
[2,3,4,5,6] is waiting, mouth open for 7
How do I organize these mouths?
The breakthrough thought:
"I don't care WHO is waiting—I care WHAT they're waiting for!"
Reorganize by demand:
Instead of:
Subsequence A needs 3
Subsequence B needs 3
Subsequence C needs 5
Think:
Number 3 is needed by 2 subsequences
Number 5 is needed by 1 subsequence
→ need = {3: 2, 5: 1}
Choice: HashMap<Integer, Integer> need
Why this structure?
- Key = the number that's needed
- Value = how many subsequences need it
- Operations needed:
- Check demand:
need.get(x) > 0
- Satisfy one demand:
need.put(x, need.get(x) - 1)
- Create new demand:
need.put(x+1, need.get(x+1) + 1)
- All O(1)!
- Check demand:
Phase 4: The State Machine (Coder's View)
The loop structure:
for each number x in sorted array:
// STATE CHECK: Is x already consumed?
if (freq[x] == 0) {
skip // Earlier decision already used this x
}
// DECISION TREE:
// Branch 1: EXTEND path
if (need[x] > 0) {
// State transitions:
freq[x]-- // consume from supply
need[x]-- // satisfy one demand
need[x+1]++ // create new demand
}
// Branch 2: START path
else if (freq[x+1] > 0 AND freq[x+2] > 0) {
// State transitions:
freq[x]-- // consume starter
freq[x+1]-- // consume middle
freq[x+2]-- // consume ender
need[x+3]++ // create demand for continuation
}
// Branch 3: FAIL path
else {
return "fail"
}
Phase 5: Understanding need
What need
Really Represents
need[7] = 3
Translation:
"Three subsequences have their LAST element at 6,
and they're all expecting 7 to be their NEXT element"
Visual representation:
Subsequence A: [..., 5, 6, ?] ─┐
Subsequence B: [..., 4, 5, 6, ?] ├─→ All need 7 next
Subsequence C: [6, ?] ─┘
need[7] = 3
Why This Structure Is Genius
Compare to alternative:
// Alternative: Track each subsequence explicitly
List<Subsequence> subs;
for (Subsequence sub : subs) {
if (sub.lastElement() == x - 1) {
sub.add(x);
break;
}
}
→ O(n) per number = O(n²) total
With need map:
if (need[x] > 0) {
need[x]--;
need[x+1]++;
}
→ O(1) per number = O(n) total
The abstraction:
- We collapse ALL subsequences ending at the same number
- We only track the COUNT, not the identity
- This works because we don't care WHICH subsequence extends—any will do!
Complete Java Implementation
public String canSplit(int[] nums) {
// Build frequency map
Map<Integer, Integer> freq = new HashMap<>();
for (int num : nums) {
freq.put(num, freq.getOrDefault(num, 0) + 1);
}
// Track subsequences waiting for next number
Map<Integer, Integer> need = new HashMap<>();
for (int x : nums) {
// Skip if already used
if (freq.get(x) == 0) {
continue;
}
// Option 1: EXTEND existing subsequence
if (need.getOrDefault(x, 0) > 0) {
freq.put(x, freq.get(x) - 1);
need.put(x, need.get(x) - 1);
need.put(x + 1, need.getOrDefault(x + 1, 0) + 1);
}
// Option 2: START new subsequence
else if (freq.getOrDefault(x + 1, 0) > 0 &&
freq.getOrDefault(x + 2, 0) > 0) {
freq.put(x, freq.get(x) - 1);
freq.put(x + 1, freq.get(x + 1) - 1);
freq.put(x + 2, freq.get(x + 2) - 1);
need.put(x + 3, need.getOrDefault(x + 3, 0) + 1);
}
// Option 3: FAIL
else {
return "fail";
}
}
return "pass";
}
Time & Space Complexity
- Time: O(n) - single pass through array
- Space: O(n) - two hashmaps
Mental Model: Reservation System
Think of need
as a reservation system at a restaurant:
need[7] = 3 means:
"Three tables have ordered the next course,
and they ALL want dish #7 next"
When x=7
arrives from the kitchen:
need[7]-- → Serve one table
need[8]++ → That table now wants dish #8
You don't track WHICH table—you track HOW MANY tables want each dish.