Greedy
Master this topic with zero to advance depth.
Jump Game II
You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0].
Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any nums[i + j] where:
0 <= j <= nums[i]andi + j < n
Return the minimum number of jumps to reach nums[n - 1]. The test cases are generated such that you can reach nums[n - 1].
Visual Representation
Index: 0 1 2 3 4
Value: 2 3 1 1 4
Step 1: At index 0 (val 2). Can jump to 1 or 2.
Step 2: From index 1 (val 3). Can jump to 4 (Goal!).
Total Jumps: 2Examples
Jump 1 step from index 0 to 1, then 3 steps to the last index.
Level I: Dynamic Programming (Bottom-Up)
Intuition
Define dp[i] as the minimum jumps to reach the end starting from index i. To find dp[i], we 'look ahead' at all reachable indices j and pick the best one.
Thought Process
dp[i]is the min jumps from indexiton-1.- Base case:
dp[n-1] = 0. - Recurrence:
dp[i] = 1 + min(dp[j])for alli < j <= i + nums[i]. - Initialize
dpwith infinity.
Pattern: DAG Shortest Path
Detailed Dry Run
nums = [2, 3, 1, 1, 4]
| i | Range | Best Next | dp[i] |
|---|---|---|---|
| 4 | - | - | 0 |
| 3 | [4] | idx 4 (0) | 1 |
| 2 | [3] | idx 3 (1) | 2 |
| 1 | [2,3,4] | idx 4 (0) | 1 |
| 0 | [1,2] | idx 1 (1) | 2 |
Level II: Breadth-First Search (Queue-Based)
Intuition
Each jump can be seen as a 'level' in BFS. Indices reachable in 1 jump are Level 1, indices reachable in 2 jumps are Level 2, etc. Use a queue to traverse until is reached.
Thought Process
- Queue stores
(index, levels). - Use
visitedset to avoid cycles/redundancy. - For each popped index, push all unvisited neighbors .
Pattern: Level-Order Traversal
Detailed Dry Run
nums = [2, 3, 1, 1, 4] q: [(0,0)]. Pop (0,0). Push (1,1), (2,1). Vis={0,1,2}. Pop (1,1). Push (3,2), (4,2). Reach end! Return 2.
Level III: Optimal Greedy (Window-Based BFS)
Intuition
Maintain a 'window' of reachable indices for the current number of jumps. When we reach the end of the current window, we increment jump count and move to the farthest point reachable from that window.
Thought Process
jumps,curEnd,farthestvariables.- Iterate through array (excluding last index).
- Update
farthest = max(farthest, i + nums[i]). - If
i == curEnd, updatecurEnd = farthest,jumps++.
Pattern: Greedy Reachability
Detailed Dry Run
[2,3,1,1,4] i=0: f=2. i==curEnd(0). end=2, jumps=1. i=1: f=4. OK. i=2: f=4. i==curEnd(2). end=4, jumps=2. Result: 2.
Gas Station
There are n gas stations along a circular route, where the amount of gas at the station is gas[i].
You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from the station to its next station.
Given two integer arrays gas and cost, return the starting gas station's index if you can travel around the circuit once in the clockwise direction, otherwise return -1.
Circular Logic
If you start at index , you must be able to visit .
Examples
Start at station 3 (index 3). Fill 4 units of gas. Your tank = 4. Cost to next = 1. Remaining = 3. Final tank check covers the loop.
Level I: Brute Force (Simulation)
Intuition
Try starting from every single gas station and simulate the journey. If you can complete the circle without the tank becoming negative, that's your starting station.
Visual Representation
Stations: [S0, S1, S2]
Try S0: S0 -> S1 -> S2 -> S0 (Check tank >= 0 at each step)
Try S1: S1 -> S2 -> S0 -> S1
Try S2: S2 -> S0 -> S1 -> S2Thought Process
- Iterate
startfrom to . - For each
start, initializetank = 0. - Iterate
ifrom to to visit all stations using(start + i) % n. - If
tank < 0at any point, stop and try nextstart.
Pattern: Circular Simulation
Detailed Dry Run
gas=[1,2], cost=[2,1] S0: tank=1-2=-1. FAIL. S1: tank=2-1=1. Next: tank=1+(1-2)=0. SUCCESS!
Level II: Greedy with Total Sum Check
Intuition
Before attempting a simulation, check if a solution even exists. If , it is mathematically impossible to complete the circuit regardless of where you start.
Thought Process
- Calculate
totalGasandtotalCost. - If
totalGas < totalCost, return -1. - Otherwise, use the greedy property: if you fail to reach station starting from , any station between and will also fail to reach .
Pattern: Pruning / Feasibility Check
Detailed Dry Run
gas=[1,2,3], cost=[2,2,2]. Total gas=6, cost=6. Possible. Start S0: tank=1-2=-1. FAIL. Reset start to S1. Start S1: tank=2-2=0. OK. Start S2: tank=0+(3-2)=1. OK. Return S1.
Level III: Optimal Greedy (One-Pass)
Intuition
We can combine the total sum check into the main loop. Track totalDiff as we go. If totalDiff is negative at the end, return -1. Otherwise, the last valid start candidate is guaranteed to work.
Visual Representation
Indices: [ 0, 1, 2, 3, 4]
Diffs: [-2, -2, -2, 3, 3]
Loop:
i=0: tank=-2. Fail. start=1, tank=0.
i=1: tank=-2. Fail. start=2, tank=0.
i=2: tank=-2. Fail. start=3, tank=0.
i=3: tank=3. OK.
i=4: tank=6. OK.
TotalDiff = 0 >= 0. Result: start index 3.Thought Process
- Use
totalTankto keep running sum of all(gas[i] - cost[i]). - Use
currentTankto track gas from currentstartcandidate. - If
currentTankdrops below 0, the currentstart(and all stations between it andi) is invalid. ResetcurrentTankand setstart = i + 1. - Final answer:
totalTank >= 0 ? start : -1.
Pattern: Candidate Elimination
Detailed Dry Run
gas=[1,2,3,4,5], cost=[3,4,5,1,2]
| i | diff | curTank | totalTank | start |
|---|---|---|---|---|
| 0 | -2 | -2 (Reset) | -2 | 1 |
| 1 | -2 | -2 (Reset) | -4 | 2 |
| 2 | -2 | -2 (Reset) | -6 | 3 |
| 3 | 3 | 3 | -3 | 3 |
| 4 | 3 | 6 | 0 | 3 |
| totalTank=0. Return 3. |
Partition Labels
You are given a string s. We want to partition the string into as many parts as possible so that each letter appears in at most one part.
Return a list of integers representing the size of these parts.
Visual Representation
s = "ababcbacadefegdehijhklij"
Letter 'a' appears at indices [0, 2, 6, 8]
If we take 'a', we MUST take everything up to index 8.
Inside [0, 8], we have 'b' (ends at 5), 'c' (ends at 7).
Max end index for characters in [0, 8] is index 8.
Partition 1: "ababcbaca" (Size 9)Examples
The partition is "ababcbaca", "defegde", "hijhklij".
Level I: Dynamic Search
Intuition
For every partition starting at i, we find the last occurrence of s[i]. This is our initial end. Then, for every character between i and end, we update end if that character's last occurrence is even further.
Thought Process
- Iterate
ifrom 0 to . - Start a new partition:
start = i,end = lastOccurrence(s[i]). - Loop
jfromitoend:end = max(end, lastOccurrence(s[j])).
- When
jreachesend, the partition is complete. Addend - start + 1to result. - Move
itoend + 1.
Pattern: Window Expansion
Detailed Dry Run
s = "abaccb"
- i=0 ('a'), end=2 ('a' at index 2). Partition: [0, 2]
- Check inner: j=1 ('b'), last 'b' is 5. Update end=5.
- Check inner: j=3 ('c'), last 'c' is 4. end stays 5.
- j reaches 5. Partition size = 5-0+1 = 6.
Level II: Merge Intervals
Intuition
Treat each character as an interval from its first occurrence to its last occurrence. Any overlapping intervals MUST be merged into a single partition. This reduces the problem to a standard interval merging task.
Thought Process
- Find
firstandlastoccurrence for each character 'a'-'z'. - Create a list of intervals
[first, last]for characters that appear in the string. - Sort intervals by start time.
- Merge overlapping intervals.
- The size of each merged interval is a partition length.
Pattern: Interval Merging
Detailed Dry Run
s = "abaccb" Intervals: a[0,2], b[1,5], c[3,4]
- Sorted: [0,2], [1,5], [3,4]
- Merge [0,2] and [1,5] -> [0,5] (overlaps)
- Merge [0,5] and [3,4] -> [0,5] (overlaps) Result: [6]
Level III: Optimal Greedy (One-Pass)
Intuition
As we scan the string, we keep track of the maximum last index seen so far for characters in the current partition. When our current index i catches up to this last index, we've found a valid partition.
Thought Process
- Store the
lastIndexfor every character in a map/array. - Maintain
startandendpointers. - Iterate
ifrom 0 to :end = max(end, lastIndex[s[i]]).- If
i == end:- Found partition! Size =
i - start + 1. - Update
start = i + 1.
- Found partition! Size =
Pattern: Greedy Boundary Tracking
Detailed Dry Run
s = "ababcbaca..."
| i | char | lastIndex[char] | end | Action |
|---|---|---|---|---|
| 0 | a | 8 | 8 | - |
| 1 | b | 5 | 8 | - |
| ... | ... | ... | 8 | - |
| 8 | a | 8 | 8 | i == end! Size = 8-0+1 = 9. start = 9. |
Minimum Number of Arrows to Burst Balloons
There are some spherical balloons taped onto a flat wall that represents the XY-plane. The balloons are represented as a 2D integer array points where points[i] = [x_start, x_end] denotes a balloon whose horizontal diameter stretches between x_start and x_end.
An arrow can be shot up exactly vertically from different points along the x-axis. A balloon with x_start and x_end is burst by an arrow shot at x if x_start <= x <= x_end. There is no limit to the number of arrows that can be shot.
Return the minimum number of arrows that must be shot to burst all balloons.
Visual Representation
Points: [[10,16], [2,8], [1,6], [7,12]]
Sort by end points:
[1, 6], [2, 8], [7, 12], [10, 16]
Arrow 1: Shoot at x=6. Bursts [1,6] and [2,8].
Arrow 2: Shoot at x=12. Bursts [7,12] and [10,16].
Total: 2 arrows.Examples
One arrow at 6 bursts [1,6] and [2,8]. Another arrow at 12 bursts [7,12] and [10,16].
Level I: Greedy Simulation (Brute Force)
Intuition
If we don't know the optimal sorting trick, a natural brute-force thought is: 'Let's find the coordinate that bursts the maximum number of balloons right now, shoot an arrow there, remove the burst balloons, and repeat until no balloons are left.' The candidate coordinates to shoot at are simply the start and end points of the remaining balloons.
Thought Process
- Maintain a list of remaining balloons.
- While the list is not empty, iterate through all start and end points of the remaining balloons.
- For each point, count how many balloons it intersects. Find the point that yields the maximum count.
- Increment
arrows, filter out the burst balloons, and repeat.
Detailed Dry Run
points = [[1,6],[2,8],[7,12]]
- Points: 1,6,2,8,7,12. Count 6: bursts [1,6],[2,8] (2).
- Max burst is 2 at x=6. arrows=1. Remaining: [[7,12]]
- Repeat for [[7,12]]: best point 12. arrows=2. Empty.
Level II: Greedy (Sort by Start & Find Overlap)
Intuition
If we sort by start point, we can maintain a 'current intersection' range. Every new balloon must overlap with this intersection. If it doesn't, we shoot an arrow and start a new intersection range.
Thought Process
- Sort by
start point. - Pick the first balloon as the initial intersection
[currentStart, currentEnd]. - For each subsequent balloon
[bStart, bEnd]:- If
bStart <= currentEnd(they overlap):- Narrow the intersection:
currentEnd = min(currentEnd, bEnd).
- Narrow the intersection:
- Else (no overlap with current intersection):
- Shoot an arrow!
- Update
currentEnd = bEndand incrementarrows.
- If
Pattern: Intersection Tracking
Detailed Dry Run
pointsSorted = [[1,6],[2,8],[7,12]]
- p[0]=[1,6]. currEnd=6, arrows=1.
- p[1]=[2,8]. 2 <= 6 (overlap). currEnd = min(6, 8) = 6.
- p[2]=[7,12]. 7 > 6 (no overlap). arrows=2, currEnd=12.
Level III: Optimal Greedy (Sort by End)
Intuition
To minimize arrows, we want each arrow to burst as many balloons as possible. By sorting by the end point, we fix the rightmost boundary for an arrow. If a balloon's start point is within the current arrow's range (i.e., start <= current_arrow_pos), it is burst. Otherwise, we need a new arrow at the new balloon's end point.
Why Sort by End?
Sorting by the end point ensures that the current balloon we are considering is the one that 'expires' soonest. Putting an arrow at its end point is the best greedy choice because it covers this balloon AND has the maximum chance of covering future balloons that start before this end point.
Pattern: Interval Overlap
Detailed Dry Run
points = [[10,16], [2,8], [1,6], [7,12]] After Sort: [[1,6], [2,8], [7,12], [10,16]]
| Step | Balloon | Range | lastArrowAt | arrows | Action |
|---|---|---|---|---|---|
| 1 | [1, 6] | [1, 6] | 6 | 1 | Shoot at 6 (end of first) |
| 2 | [2, 8] | [2, 8] | 6 | 1 | 2 <= 6? YES (Burst!) |
| 3 | [7, 12] | [7, 12] | 12 | 2 | 7 > 6? YES. Shoot at 12 (end) |
| 4 | [10, 16] | [10, 16] | 12 | 2 | 10 <= 12? YES (Burst!) |
Visualization:
(1)-------(6) <- Arrow 1 at 6
(2)-----------(8)
(7)-------(12) <- Arrow 2 at 12
(10)--------(16)Non-overlapping Intervals
Given an array of intervals intervals where intervals[i] = [start_i, end_i], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.
Visual Representation
Intervals: [[1,2], [2,3], [3,4], [1,3]]
1--2
2--3
3--4
1-----3
To keep most intervals (3), we must remove [1,3].
Result: 1Examples
[1,3] can be removed and the rest of intervals are non-overlapping.
Level I: Recursive Selection (Brute Force)
Intuition
The problem asks for the minimum removals to remove all overlaps. This is equivalent to finding the maximum number of non-overlapping intervals. We can generate all combinations of intervals (take it or leave it) and find the maximum valid set.
Thought Process
- Sort the intervals by
starttime so we can easily check for overlaps sequentially. - For each interval at
currIndex, we have two choices:- Include it: Only possible if it doesn't overlap with the last included interval
prevIndex(i.e.,intervals[prevIndex].end <= intervals[currIndex].start). We add 1 to our count and move tocurrIndex + 1. - Exclude it: We move to
currIndex + 1without adding to our count.
- Include it: Only possible if it doesn't overlap with the last included interval
- Return the maximum of these two choices for all steps.
Detailed Dry Run
intervalsSorted = [[1,2], [1,3], [2,3]]
- solve(-1, 0):
- Take [1,2]: 1 + solve(0, 1) -> 1 + 1 (takes [2,3]) = 2
- Leave [1,2]: solve(-1, 1) -> max(take[1,3], leave[1,3]) = 1 Max kept = 2. Removals = 3 - 2 = 1.
Level II: Dynamic Programming (LIS Variation)
Intuition
This problem is equivalent to finding the Longest Chain of Pairs or the Longest Increasing Subsequence. We want to find the maximum number of intervals k that can be kept. Then the result is N - k.
Thought Process
- Sort the intervals by
starttime. - Create a
dparray wheredp[i]is the maximum number of non-overlapping intervals ending at indexi. - For each
i, look at allj < i. Ifintervals[j].end <= intervals[i].start, then we can extend the chain:dp[i] = max(dp[i], dp[j] + 1).
Pattern: Dynamic Programming / LIS
Detailed Dry Run
intervalsSorted = [[1,2], [1,3], [2,3]], dp=[1,1,1]
- i=1 [1,3]: j=0 [1,2] overlaps. dp[1]=1.
- i=2 [2,3]: j=0 [1,2] no overlap. dp[2]=max(1, dp[0]+1)=2.
- i=2 [2,3]: j=1 [1,3] overlaps. dp[2]=2. Max kept = 2. Removals = 3 - 2 = 1.
Level III: Optimal Greedy (Sort by End)
Intuition
To keep the maximum number of intervals, we should always pick the interval that ends the earliest. This is the classic Interval Scheduling problem. Why? Because an earlier end time leaves the most possible room for subsequent intervals.
Thought Process
- Sort by
endpoint. - Keep track of the
lastEndtime of the last accepted interval. - For each interval:
- If
start >= lastEnd, accept it and updatelastEnd. - Else, it overlaps; increment
removalscount.
- If
Pattern: Interval Scheduling
Detailed Dry Run
intervals = [[1,2], [2,3], [3,4], [1,3]] After Sort: [[1,2], [2,3], [1,3], [3,4]]
| Step | Interval | Range | lastEnd | Action |
|---|---|---|---|---|
| 1 | [1, 2] | [1, 2] | 2 | Keep [1,2]. lastEnd = 2 |
| 2 | [2, 3] | [2, 3] | 3 | 2 >= 2? YES. Keep [2,3]. lastEnd = 3 |
| 3 | [1, 3] | [1, 3] | 3 | 1 < 3? NO. Remove [1,3]. |
| 4 | [3, 4] | [3, 4] | 4 | 3 >= 3? YES. Keep [3,4]. lastEnd = 4 |
Result: 1 removal.
Queue Reconstruction by Height
You are given an array of people, people, which are the attributes of some people in a queue (not necessarily in order). Each people[i] = [h_i, k_i] represents the i-th person of height h_i who has exactly k_i other people in front of them with a height greater than or equal to h_i.
Reconstruct and return the queue that is represented by the input array people.
Visual Representation
Input: [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
Step 1: Sort by height DESC, then k index ASC.
[[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]
Step 2: Insert into list at index k.
Insert [7,0] at 0: [[7,0]]
Insert [7,1] at 1: [[7,0], [7,1]]
Insert [6,1] at 1: [[7,0], [6,1], [7,1]]
Insert [5,0] at 0: [[5,0], [7,0], [6,1], [7,1]]
...Examples
Result matches the height/k-index constraints.
Level I: Brute Force Simulation (Find Empty Slots)
Intuition
If we sort people by height ascending, we can process each person and find their relative position by counting 'empty slots' in the final array. Each empty slot is reserved for someone taller (or same height) than the current person.
Thought Process
- Sort by height ASC, then DESC for same height.
- Create an empty result array of size .
- For each person
[h, k]:- We need people in front of us who are .
- Find the -th empty slot in the result array and place the person there.
Detailed Dry Run
people = [[7,0], [4,4], [7,1], [5,0], [5,2], [6,1]] Sorted (H asc, K desc): [[4,4], [5,2], [5,0], [6,1], [7,1], [7,0]]
- [4,4]: Put in 5th empty slot (idx 4). res=[_ _ _ _ [4,4] _]
- [5,2]: Put in 3rd empty slot (idx 2). res=[_ _ [5,2] _ [4,4] _]
- [5,0]: Put in 1st empty slot (idx 0). res=[[5,0] _ [5,2] _ [4,4] _]
- [6,1]: Put in 2nd empty slot (idx 3). res=[[5,0] _ [5,2] [6,1] [4,4] _]
Level II: Optimal Greedy (Sort and Insert)
Intuition
Taller people don't care about shorter people behind them. If we process taller people first, their relative k index in the final queue will be exactly equal to their index in the list.
Thought Process
- Sort DESC by height, then ASC by
k. - Insert each person into a dynamic list at index
k. - Shorter people inserted later won't change the count of taller people in front of existing elements.
Pattern: Greedy Insertion
Detailed Dry Run
people = [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]
| Step | Person | Action | Queue |
|---|---|---|---|
| 1 | [7,0] | Insert at 0 | [[7,0]] |
| 2 | [7,1] | Insert at 1 | [[7,0], [7,1]] |
| 3 | [6,1] | Insert at 1 | [[7,0], [6,1], [7,1]] |
| 4 | [5,0] | Insert at 0 | [[5,0], [7,0], [6,1], [7,1]] |
| 5 | [5,2] | Insert at 2 | [[5,0], [7,0], [5,2], [6,1], [7,1]] |
| 6 | [4,4] | Insert at 4 | [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] |
Level III: Logarithmic Optimal (Binary Indexed Tree)
Intuition
Finding the -th empty slot in is the bottleneck in the simulation. We can speed this up to using a Binary Indexed Tree (BIT) or Segment Tree by tracking the number of available slots.
Thought Process
- Use the height ASC sort from Level I.
- Imagine an array where 1 means 'empty' and 0 means 'filled'.
- We want a position where .
- We can find this using Binary Search over the BIT in or walking the Segment Tree in .
Detailed Dry Run
Sorted: [[4,4], [5,2], [5,0], [6,1], [7,1], [7,0]] BIT tracks empty slots (initially all 1s).
- [4,4]: Find k=4. BIT query/search gives pos 5. res[4]=[4,4]. BIT update(5, -1).
- [5,2]: Find k=2. BIT query search gives pos 3. res[2]=[5,2]. BIT update(3, -1).
Task Scheduler
Given a characters array tasks, representing the tasks a CPU needs to do, where each letter represents a different task. For each unit of time, the CPU could complete either one task or stay idle.
There is a cooldown period n between two same tasks, meaning at least n units of time must pass between any two executions of the same task.
Return the least number of units of time to finish all tasks.
Visual Representation
tasks = ["A","A","A","B","B","B"], n = 2
Timeline:
A -> B -> idle -> A -> B -> idle -> A -> B
1 2 3 4 5 6 7 8
Total Time: 8Examples
A possible sequence: A -> B -> idle -> A -> B -> idle -> A -> B.
Level I: Simple Simulation
Intuition
At each unit of time, we want to pick the most frequent task that is currently available (not in cooldown). We can simulate this step-by-step.
Thought Process
- Count frequencies of all tasks.
- Maintain an array
nextAvailableTimefor each task. - At each time
t, pick the available task with the highest remaining frequency. - If no task is available, stay idle and increment time.
Detailed Dry Run
tasks=[A,A,B], n=2 t=0: best=A (count 2). nextAvail[A]=3, rem=2. t=1: best=B (count 1). nextAvail[B]=4, rem=1. t=2: no one avail (A needs t=3). idle. t=3: best=A (count 1). nextAvail[A]=6, rem=0. Total: 4 units.
Level II: Greedy with Max-Heap
Intuition
Instead of scanning all tasks every time, we use a Max-Heap to always pick the highest frequency task. Tasks in cooldown are stored in a temporary wait-queue and added back to the heap once their cooldown expires.
Thought Process
- Push all task frequencies into a Max-Heap.
- Use a Queue to store
{remaining_freq, release_time}for tasks in cooldown. - At each unit of time:
- Check the Queue: If a task's
release_time <= current_time, push it back to the Heap. - Pop the highest frequency task from the Heap, decrement its frequency, and if it's still > 0, add it to the wait-queue with
release_time = current_time + n + 1.
- Check the Queue: If a task's
Pattern: Priority Queue Simulation
Detailed Dry Run
tasks=[A,A,A,B,B,B], n=2 Heap: [(A,3), (B,3)] t=0: Pop (A,3). Add to waitQueue: (A,2) at t=3. time=1. t=1: Pop (B,3). Add to waitQueue: (B,2) at t=4. time=2. t=2: Heap empty. Wait till t=3... time=3. t=3: WaitQueue release (A,2). Heap: [(A,2)]...
Level III: Optimal Frequency Analysis (Math)
Intuition
The total time is bounded by the most frequent task. If 'A' is the most frequent (frequency ), we have blocks. Each of the first blocks must be followed by slots (either other tasks or idle).
Thought Process
- Let
max_fbe the maximum frequency. - The base structure is
(max_f - 1) * (n + 1)slots. - Any other tasks with the same
max_ffrequency will fill one extra slot at the very end. - Total time =
(max_f - 1) * (n + 1) + num_tasks_with_max_f. - If this result is smaller than the total number of tasks, it means no idle slots are needed, so the answer is
tasks.length.
Pattern: Math / Frequency Analysis
Detailed Dry Run
tasks = [A,A,A,B,B,B], n = 2
| Variable | Value | Explanation |
|---|---|---|
| maxFreq | 3 | 'A' and 'B' both appear 3 times |
| countMax | 2 | Two tasks (A, B) have max frequency |
| Formula | (3-1)*(2+1) + 2 | 2 * 3 + 2 = 8 |
| Final | max(8, 6) | 8 |
Visualization:
[A, B, _] [A, B, _] [A, B] -> Total 8 slots.
Minimum Number of Taps to Water Garden
There is a one-dimensional garden from 0 to n. There are n + 1 taps at points [0, 1, ..., n]. Tap i can water the area [i - ranges[i], i + ranges[i]].
Return the minimum number of taps to water the entire garden [0, n]. If impossible, return -1.
Visual Representation
n = 5, ranges = [3, 4, 1, 1, 0, 0]
Tap 0: [0, 3]
Tap 1: [0, 5] <--- Covers everything alone!
Tap 2: [1, 3]
...
Optimal: 1 (Tap 1)Examples
Tap at index 1 covers [0,5], which is the entire garden.
Level I: Brute Force (Recursive Backtracking)
Intuition
Try every subset of taps and check if they together cover the garden [0, n]. This can be implemented using recursion by deciding for each tap whether to include it or not.
Thought Process
solve(tapIdx, currentCoverage)- At each step, either take the current tap or skip it.
- Base case: If
tapIdx == n + 1, check ifcurrentCoverage >= n. - This approach is extremely slow due to combinations.
Detailed Dry Run
n=3, taps=[[0,2], [1,3]]
- solve(0, 0): Take [0,2] -> solve(1, 2) -> Take [1,3] -> solve(2, 3) -> SUCCESS (2 taps)
- solve(0, 0): Skip [0,2] -> solve(1, 0) -> Take [1,3] -> FAIL (Gap at [0,1])
Level II: Dynamic Programming
Intuition
Define dp[i] as the minimum number of taps needed to water the garden from index to . For each tap, we update all indices it covers.
Thought Process
- Initialize
dparray of sizen+1with a large value (infinity). dp[0] = 0(no taps needed to cover index 0).- For each tap
iwith range[left, right]:- For every
jsuch thatleft <= j <= right:dp[right] = min(dp[right], dp[j] + 1).
- For every
Pattern: Interval DP / Shortest Path in DAG
Detailed Dry Run
n=5, ranges=[3,4,1,1,0,0], dp=[0, inf, inf, inf, inf, inf]
- Tap 0: [0,3]. dp[1..3] = min(inf, dp[0]+1) = 1. dp=[0, 1, 1, 1, inf, inf]
- Tap 1: [0,5]. dp[1..5] = min(cur, dp[0..1]+1) = 1. dp=[0, 1, 1, 1, 1, 1]
Level III: Optimal Greedy (Jump Game II Variation)
Intuition
This is equivalent to finding the minimum jumps to reach n. We transform each tap into a 'jump' from its left boundary to its right boundary.
Thought Process
- Preprocess: For each start point , find the maximum end point it can reach:
maxReach[left] = max(maxReach[left], right). - Now it's Jump Game II: Iterate from to , keeping track of the current coverage end and the farthest reach possible.
Pattern: Interval Coverage / Jump Game II
Detailed Dry Run
n=5, ranges=[3,4,1,1,0,0] MaxReach: [5, 0, 3, 0, 4, 5] (Index 0 can reach 5 via Tap 1)
| i | farthest | end | taps | Action |
|---|---|---|---|---|
| 0 | 5 | 0 | 1 | i == end, taps++, end = 5 |
| 1-4 | 5 | 5 | 1 | farthest doesn't change |
| Result: 1 |
Maximum Length of Pair Chain
You are given an array of n pairs pairs where pairs[i] = [left_i, right_i] and left_i < right_i.
A pair p2 follows p1 if p1[1] < p2[0]. Return the length of the longest chain.
Visual Representation
Pairs: [[1,2], [7,8], [4,5]]
1. Sort by end points: [1,2], [4,5], [7,8]
2. Chain: [1,2] -> [4,5] -> [7,8]
Length: 3Examples
Longest chain: [1,2] -> [3,4]. Note: [2,3] cannot follow [1,2] because 2 is not < 2.
Level I: Brute Force (Recursive Backtracking)
Intuition
Try all possible subsequences of pairs and check if they form a valid chain. To optimize slightly, we can first sort by start time and use recursion with a 'pick or skip' strategy.
Thought Process
solve(idx, prevEnd)- For each pair
idx:- If
pairs[idx].start > prevEnd, we can pick it:1 + solve(idx + 1, pairs[idx].end). - We can always skip it:
solve(idx + 1, prevEnd).
- If
- Return the maximum of both.
Detailed Dry Run
pairs = [[1,2], [2,3], [3,4]]
- solve(0, -inf): Pick [1,2] -> solve(1, 2) -> Pick [3,4] -> SUCCESS (2 pairs)
- solve(0, -inf): Skip [1,2] -> solve(1, -inf) -> Pick [2,3] -> solve(2, 3) -> Pick [3,4] (FAIL: 3 not > 3)
Level II: Dynamic Programming (LIS Variation)
Intuition
This problem is perfectly analogous to Longest Increasing Subsequence (LIS). We want to find the longest chain where each pair follows the previous one.
Thought Process
- Sort the pairs by their start element.
- Initialize a
dparray wheredp[i]is the length of the longest chain ending at indexi(initially all 1). - For each
i, check allj < i: ifpairs[j].end < pairs[i].start, thendp[i] = max(dp[i], dp[j] + 1).
Pattern: Dynamic Programming / LIS
Detailed Dry Run
pairs = [[1,2], [2,3], [3,4]], dp=[1, 1, 1]
- i=1 ([2,3]): j=0 ([1,2]). 2 is not > 2. dp[1]=1.
- i=2 ([3,4]): j=0 ([1,2]). 3 > 2. dp[2]=max(1, dp[0]+1)=2.
- i=2 ([3,4]): j=1 ([2,3]). 3 is not > 3. dp[2]=2. Max dp = 2.
Level III: Optimal Greedy (Interval Scheduling-Style)
Intuition
To fit the maximum number of intervals into a chain, we should always pick the pair that ends the earliest. This leaves the maximum room for subsequent pairs.
Thought Process
- Sort pairs by their end element
pair[1]. - Maintain
currEndof the chain. - For each pair, if its
start > currEnd, add it to the chain and updatecurrEnd.
Pattern: Interval Selection / Activity Selection
Detailed Dry Run
pairs = [[1,2], [7,8], [4,5]] Sorted by end: [[1,2], [4,5], [7,8]]
| Pair | currEnd | Action |
|---|---|---|
| [1,2] | 2 | Pick [1,2], count=1 |
| [4,5] | 5 | 4 > 2? YES. Pick [4,5], count=2 |
| [7,8] | 8 | 7 > 5? YES. Pick [7,8], count=3 |
Meeting Rooms II
Given an array of meeting time intervals intervals where intervals[i] = [start_i, end_i], return the minimum number of conference rooms required.
Visual Representation
Intervals: [[0, 30], [5, 10], [15, 20]]
Room 1: [0--------------------------30]
Room 2: [5---10] [15---20]
Max overlapping at any time: 2
Result: 2Examples
We need two rooms; one for [0, 30] and another for [5, 10] and [15, 20] sequentially.
Level I: Brute Force (Iterate All Time Points)
Intuition
Count the number of overlapping meetings at every possible time point. The maximum count across all time points is the minimum number of rooms needed.
Thought Process
- Find the
minStartandmaxEndacross all intervals. - For every time from
minStarttomaxEnd:- Count how many meetings are active at time (i.e.,
start <= t < end).
- Count how many meetings are active at time (i.e.,
- Return the maximum count found.
Detailed Dry Run
[[0, 30], [5, 10], [15, 20]]
- t=0: active=[[0,30]]. Count=1.
- t=5: active=[[0,30], [5,10]]. Count=2.
- t=15: active=[[0,30], [15,20]]. Count=2. Max count = 2.
Level II: Chronological Sorting (Sweep Line)
Intuition
When a meeting starts, we need a room; when it ends, we free a room. If we process all start and end points in chronological order, the max number of overlapping meetings at any point is our answer.
Thought Process
- Separate starts and ends into two sorted arrays.
- Use two pointers to iterate through them.
- If
startTime < endTime, a new meeting starts before an old one ends: incrementroomsandstartPtr. - Else, a meeting ends: decrement
roomsandendPtr.
Pattern: Sweep Line / Two Pointers
Detailed Dry Run
Starts: [0, 5, 15], Ends: [10, 20, 30]
- t=0: Start [0,30]. rooms=1. max=1.
- t=5: Start [5,10]. rooms=2. max=2.
- t=10: End [5,10]. rooms=1.
- t=15: Start [15,20]. rooms=2. max=2.
Level III: Optimal Greedy (Min-Heap)
Intuition
As we process meetings by start time, we use a Min-Heap to track the end times of rooms currently in use. The top of the heap is the room that will be free soonest.
Thought Process
- Sort meetings by start time.
- Add the first meeting's end time to a Min-Heap.
- For each next meeting, if its
start >= heap.peek()(earliest end), we can reuse that room:heap.pop(). - Always push current meeting's end time to the heap.
- Answer is
heap.size().
Pattern: Resource Allocation / Priority Queue
Detailed Dry Run
[[0,30],[5,10],[15,20]] Sorted
- [0,30]: Heap=[30], rooms=1
- [5,10]: 5 < 30? YES. Heap=[10, 30], rooms=2
- [15,20]: 15 >= 10? YES. Pop 10, Push 20. Heap=[20, 30], rooms=2
Candy Distribution
Each child has a rating. You must give candies such that:
- Every child gets at least 1 candy.
- Children with a higher rating get more candies than their neighbors.
Return the minimum total candies.
Visual Representation
Ratings: [1, 0, 2]
Pass 1 (L->R):
[1, 0, 2] -> [1, 1, 2] (fixes child at index 2 rank > index 1)
Pass 2 (R->L):
[1, 0, 2] -> [2, 1, 2] (fixes child at index 0 rank > index 1)
Total: 5Examples
Candies: [2, 1, 2]. Total = 5.
Level I: Brute Force (Iterative Satisfaction)
Intuition
Repeatedly scan the ratings and candies. If a child has a higher rating than a neighbor but doesn't have more candies, give them one more. Repeat until no more changes are needed.
Thought Process
- Initialize
candieswith 1 for all. - In a loop, keep track if any changes were made (
changed = false). - For each child
i:- If
ratings[i] > ratings[i-1]andcandies[i] <= candies[i-1], setcandies[i] = candies[i-1] + 1, changed = true. - Same for
i+1.
- If
- Stop when
changedis false.
Detailed Dry Run
ratings=[1,2,3], initial=[1,1,1] Pass 1: [1,2,1] (i=1), [1,2,3] (i=2). changed=true. Pass 2: No changes. Total=6.
Level II: Peak and Valley (Single Pass)
Intuition
We can think of the ratings as a sequence of mountains (peaks) and valleys. The number of candies increases as we climb a mountain from a valley, and decreases as we descend. The 'peak' child gets the maximum needed from both sides.
Thought Process
- Use variables
up,down, andpeakto track the current mountain slopes. - If rating increases,
up++,down=0,peak=up. Candies +=up + 1. - If rating decreases,
down++,up=0. Candies +=down. - If
down > peak, we need to add one extra candy to the peak to keep it higher than the descending slope.
Pattern: Slope Analysis
Detailed Dry Run
ratings = [1, 2, 1]
- i=1 (up): up=1, peak=1, candies = 1 + (1+1) = 3.
- i=2 (down): down=1, up=0. down <= peak (1 <= 1). candies = 3 + 1 = 4. Sum = 4? Wait, [1, 2, 1] should be [1, 2, 1] -> 4. Correct.
Level III: Optimal Greedy (Two Independent Passes)
Intuition
A child must satisfy two conditions: candies[i] > candies[i-1] if ratings[i] > ratings[i-1], and candies[i] > candies[i+1] if ratings[i] > ratings[i+1]. We can solve these separately.
Thought Process
- Start all children with 1 candy.
- L-to-R: If
rating[i] > rating[i-1], setcandies[i] = candies[i-1] + 1. - R-to-L: If
rating[i] > rating[i+1], setcandies[i] = max(candies[i], candies[i+1] + 1).
Pattern: Constraint Satisfaction in Multiple Passes
Detailed Dry Run
ratings = [1, 2, 8, 7, 3, 4, 1] L->R: [1, 2, 3, 1, 1, 2, 1] R->L: [1, 2, 3, 2, 1, 2, 1] Result: 12
Minimum Cost to Connect Sticks
You have sticks with positive integer lengths. You can connect any two sticks into one by paying a cost equal to their sum. Return the minimum cost to connect all sticks.
Visual Representation
Sticks: [2, 4, 3]
1. Combine 2 and 3: Cost = 5. Remaining: [5, 4]
2. Combine 5 and 4: Cost = 9. Remaining: [9]
Total: 14Examples
Combining 2+3 first followed by 4 is cheaper than other combinations.
Level I: Brute Force (Try All Pairs)
Intuition
Try every possible sequence of connecting two sticks and find the one that results in the minimum total cost. This can be implemented using recursion by trying all pairs at each step.
Thought Process
- If only 1 stick remains, return 0.
- For every pair of sticks :
- Combine them:
cost = sticks[i] + sticks[j]. - Recursively solve for the new set of sticks.
- Track the minimum total cost across all possible initial pairs.
- Combine them:
Detailed Dry Run
sticks = [2, 4, 3]
- Option 1: (2,4) -> [6,3]. Then (6,3) -> [9]. Cost = 6 + 9 = 15.
- Option 2: (2,3) -> [5,4]. Then (5,4) -> [9]. Cost = 5 + 9 = 14. (MIN)
- Option 3: (4,3) -> [7,2]. Then (7,2) -> [9]. Cost = 7 + 9 = 16.
Level II: Sorting and Re-sorting
Intuition
At each step, we identify the two smallest sticks by sorting the list, combine them, and repeat. While better than brute force, re-sorting at every step is inefficient.
Thought Process
- While more than 1 stick exists:
- Sort the current list of sticks.
- Take the two smallest elements, sum them.
- Add the sum to
totalCost, remove the two elements, and add the sum back.
- Repeat until 1 stick remains.
Detailed Dry Run
sticks = [2, 4, 3]
- Sort: [2, 3, 4]. Combine 2+3=5. Total=5. Sticks: [5, 4]
- Sort: [4, 5]. Combine 4+5=9. Total=14. Sticks: [9] Result: 14
Level III: Optimal Greedy (Min-Heap)
Intuition
To minimize the total cost, we should always combine the two smallest sticks currently available. This is the Huffman Coding principle.
Thought Process
- Add all sticks to a Min-Heap.
- While
heap.size() > 1:- Pop
s1ands2(two smallest). cost = s1 + s2.totalCost += cost, Pushcostback to heap.
- Pop
Pattern: Huffman Coding (Greedy Merging)
Detailed Dry Run
Sticks: [1, 8, 3, 5]
- Pop 1, 3. Sum = 4. Total = 4. Heap: [4, 5, 8]
- Pop 4, 5. Sum = 9. Total = 13. Heap: [8, 9]
- Pop 8, 9. Sum = 17. Total = 30. Result: 30
Patching Array
Add minimum elements to a sorted array so all numbers in [1, n] can be formed by sums.
Visual Representation
nums = [1, 3], n = 6
Reach: [1, 1] (via 1)
Next miss: 2. nums[i]=3 > 2. PATCH 2.
Reach: [1, 3] (via 1, 2)
Next miss: 4. nums[i]=3 <= 4. Use 3.
Reach: [1, 6] (via 1, 2, 3)
Done. Patches: 1Examples
Adding 2 allows forming sums up to 6.
Level I: Brute Force (Recursion / Subsets)
Intuition
Try all possible combinations of patches (numbers from 1 to n) and check if each subset sum can form all numbers in [1, n]. This is extremely slow but guaranteed to find the minimum patches.
Thought Process
isPossible(current_nums, n): Checks if all numbers in [1, n] can be formed.solve(current_nums, count): Recursively tries adding each possible numberpthat is not incurrent_nums.- Return the minimum
countsuch thatisPossibleis true.
Detailed Dry Run
nums = [1, 3], n = 6
- isPossible([1, 3], 6)? No (cannot form 2).
- Try adding 2: [1, 2, 3]. sums: 1, 2, 3, 4(1+3), 5(2+3), 6(1+2+3). YES. Min patches = 1.
Level II: Dynamic Programming (Boolean Knapsack)
Intuition
Treat this as a variation of the Subset Sum problem. For a given set of numbers, maintain a boolean array can_form where can_form[i] is true if sum i can be reached.
Thought Process
dp[i]= true if sumiis possible.- Start with original
nums. Updatedpfor each number. - If
dp[miss]is false for somemiss <= n, we must add a patch. - Greedy choice for patch is always the smallest missing number to maximize coverage.
Detailed Dry Run
nums = [1, 3], n = 6, dp=[T, F, F, F, F, F, F] (dp[0]=T)
- Use 1: dp=[T, T, F, F, F, F, F]
- Use 3: dp=[T, T, F, T, T, F, F]
- Miss = 2. Patch 2.
- Use 2: dp=[T, T, T, T, T, T, T]. DONE.
Level III: Optimal Greedy (Reachability Range)
Intuition
Maintain the smallest number miss that cannot be formed. If nums[i] <= miss, use it to extend the range. Otherwise, patch miss to double the reach.
Thought Process
miss= current gap.- If
nums[i] <= miss:miss += nums[i++]. - Else:
miss += miss,count++.
Pattern: Greedy Range Extension
Detailed Dry Run
nums = [1, 5, 10], n = 20 miss=1: Use 1, miss=2 miss=2: nums[1]=5 > 2. Patch 2, miss=4 miss=4: nums[1]=5 > 4. Patch 4, miss=8 miss=8: Use 5, miss=13 miss=13: Use 10, miss=23. DONE.
Reorganize String
Rearrange string so no two adjacent characters are identical.
Visual Representation
s = "aaabcc"
a: 3, b: 1, c: 2
1. Fill 'a' at 0, 2, 4 -> [a, _, a, _, a, _]
2. Fill 'c' at 1, 3 -> [a, c, a, c, a, _]
3. Fill 'b' at 5 -> [a, c, a, c, a, b]Examples
Interleaving 'a' and 'b' satisfies the requirement.
Level I: Brute Force (All Permutations)
Intuition
Generate all possible permutations of the input string and check if any of them satisfy the condition that no two adjacent characters are identical.
Thought Process
- Use recursion to generate all permutations.
- For each unique permutation, check adjacency:
s[i] == s[i+1]. - Return the first valid permutation found, or "" if none exist.
Detailed Dry Run
s = "aab" Permutations: ["aab", "aba", "baa"]
- "aab": a == a (FAIL)
- "aba": a != b, b != a (SUCCESS). Return "aba".
Level II: Greedy with Max-Heap
Intuition
Always pick the most frequent character that is different from the last one placed. A Max-Heap allows us to fetch the highest frequency character efficiently.
Thought Process
- Count frequencies and push into a Max-Heap
(count, char). - In each step, pop the top character.
- Add it to the result and keep it aside (don't push back yet) because we can't use it in the very next step.
- Push the character from the previous step back into the heap if it still has remaining count.
Pattern: Priority Queue / Interleaving
Detailed Dry Run
s = "aaabcc" Heap: [(a,3), (c,2), (b,1)]
- Pop (a,3). Res: "a". prev=(a,2)
- Pop (c,2). Res: "ac". Push prev (a,2). prev=(c,1)
- Pop (a,2). Res: "aca". Push prev (c,1). prev=(a,1) Result: "acacab"
Level III: Optimal Greedy (Frequency Interleaving)
Intuition
Place the most frequent character at even indices (0, 2, 4...). If it fits, we can always interleave the rest.
Thought Process
- Count frequencies. If any char count , impossible.
- Start with the most frequent char.
- Fill indices 0, 2, 4... then wrap to 1, 3, 5...
Pattern: Frequency-Based Positioning
Detailed Dry Run
s = "aab" Max char 'a' count = 2. Place at 0, 2. [a, _, a] Next char 'b' count = 1. Place at 1. [a, b, a]
Minimum Number of K Consecutive Bit Flips
You are given a binary array nums and an integer k. A k-bit flip is choosing a subarray of length k and reversing all bits. Return the minimum flips to make all elements 1.
Visual Representation
nums = [0, 1, 0], k = 1
i=0: 0 -> Flip! [1, 1, 0], ans=1
i=1: 1 -> OK.
i=2: 0 -> Flip! [1, 1, 1], ans=2
Total: 2Examples
Flip nums[0], then flip nums[2].
Level I: Brute Force Simulation
Intuition
Traverse the array from left to right. Whenever we encounter a 0, we flip the next k elements. If at any point we need to flip but don't have enough elements left, it's impossible.
Thought Process
- For each index
ifrom0ton-1:- If
nums[i] == 0:- Check if
i + k > n. If so, return -1. - Manually flip
nums[i...i+k-1]. - Increment
ans.
- Check if
- If
- Return
ans.
Detailed Dry Run
nums = [0, 1, 0, 1], k = 2
- i=0: nums[0]=0. Flip [0,1]. nums becomes [1, 0, 0, 1]. ans=1.
- i=1: nums[1]=0. Flip [1,2]. nums becomes [1, 1, 1, 1]. ans=2.
- i=2,3: nums[i]=1. OK. Result: 2
Level II: Greedy with Queue
Intuition
Instead of manually flipping elements, use a queue to track which indices still have an active flip effect. This avoids the inner loop.
Thought Process
- Maintain a queue of indices where a flip was initiated.
- At each index
i, remove expired flips from the queue (indices where ). - The number of flips currently affecting
iisqueue.size(). - If
nums[i]is effectively0(i.e.,nums[i] == queue.size() % 2), start a new flip ati.
Detailed Dry Run
nums = [0, 0, 0], k = 2 i=0: nums[0]=0, q=[] (size 0). 0==0. Push 0. q=[0], ans=1. i=1: nums[1]=0, q=[0] (size 1). 0!=1. OK. i=2: i-k=0. Expire 0. q=[]. nums[2]=0. 0==0. Push 2. i+k > n. Return -1.
Level III: Optimal Greedy (Sliding Window Flips)
Intuition
Traverse left to right. If nums[i] is 0 after all previous flips affecting it, we MUST flip the window starting at i. Use a queue or difference array to track active flips efficiently.
Thought Process
curFlips= count of active flips affecting indexi.- When index
ireachesk, remove the effect of the flip that started ati-k. - If
nums[i]is effectively0(i.e.,nums[i] == curFlips % 2), flip it.
Pattern: Greedy Sliding Window Flip
Detailed Dry Run
nums = [0,0,0,1,0], k=3 i=0: nums[0]=0, cur=0. Flip! ans=1, cur=1, mark[0]=1 i=1,2: cur=1, nums[i] effectively 1. OK. i=3: i>=3, remove mark[0]. cur=0. nums[3]=1. OK. i=4: nums[4]=0, cur=0. Flip! i+k > n. FAIL.
Weighted Job Scheduling
Find maximum profit from non-overlapping jobs with given startTime, endTime, and profit.
Visual Representation
Jobs (S, E, P):
J1: [1, 3, 50], J2: [2, 4, 10], J3: [3, 5, 40], J4: [3, 6, 70]
Combination J1 (50) + J4 (70) = 120 (Max)Examples
The subset of jobs [J1, J4] gives maximum profit.
Level I: Recursive Backtracking
Intuition
Try all possible subsets of compatible jobs and return the maximum profit. To handle compatibility, we sort by start time and at each step, decide to either pick the current job and skip all overlapping ones, or skip the current job.
Thought Process
solve(idx): Max profit starting from jobidx.- Decision 1: Skip job
idx->solve(idx + 1). - Decision 2: Pick job
idx. Find the next jobk > idxthat starts afterjobs[idx].end. Result =profit[idx] + solve(k). - Return
max(Decision 1, Decision 2).
Detailed Dry Run
Jobs: [1,3,50], [2,4,10], [3,5,40]
- solve(0):
- Skip J1: solve(1)
- Pick J1: 50 + solve(2) (J3 starts at 3, J1 ends at 3). Result = 50 + 40 = 90.
- Return 90.
Level II: Dynamic Programming (O(N^2))
Intuition
This is a variation of the Longest Increasing Subsequence (LIS) problem. For each job i, we look at all previous jobs j < i and if they are compatible, we update dp[i] = max(dp[i], dp[j] + profit[i]).
Thought Process
- Sort jobs by end time.
dp[i]= max profit ending at jobi.- For each
i, iterate allj < iand check ifjobs[j].end <= jobs[i].start. - This approach is and will pass for medium constraints but TLE for .
Detailed Dry Run
Jobs: [J1:1-3, 50], [J2:2-4, 10], [J3:3-5, 40]
- dp[0] = 50 (J1)
- dp[1] = 10 (J2). J1 overlaps with J2.
- dp[2] = 40 + dp[0] = 90. J1 is compatible with J3. Max Profit = 90.
Level III: Optimal (DP + Binary Search)
Intuition
Sort by end time. For each job, the max profit is either excluding it (same as max profit of previous job) or including it (profit + max profit of latest compatible job).
Thought Process
- Combine data and sort by
endTime. dp[i]= max profit for firstijobs.- Search for the latest job
j < iwherejobs[j].end <= jobs[i].startusing binary search. dp[i] = max(dp[i-1], job[i].profit + dp[j]).
Pattern: Dynamic Programming + Binary Search
Detailed Dry Run
Sorted: [J1:[1,3,50], J2:[2,4,10], J3:[3,5,40], J4:[3,6,70]] J1: dp[0]=50 J2: j=-1, dp[1]=max(50, 10)=50 J3: j=0, dp[2]=max(50, 40+50)=90 J4: j=0, dp[3]=max(90, 70+50)=120
Course Schedule III
Take maximum number of courses given [duration, lastDay] constraints.
Visual Representation
Courses: [[100, 200], [1000, 1250], [200, 1300]]
1. [100, 200] -> time=100. Heap: [100].
2. [1000, 1250] -> time=1100. Heap: [100, 1000].
3. [200, 1300] -> time=1300. Heap: [100, 200, 1000].
Result: 3Examples
Take course 1, 3, and 2.
Level I: Recursive Backtracking
Intuition
Try all possible subsets of courses. For each subset, check if there exists an ordering that satisfies all deadlines. The maximum subset size that works is the answer.
Thought Process
- Generate all subsets of courses.
- For each subset, sort it by deadline and check if it's feasible.
- Return the maximum feasible subset size.
Pattern: Brute Force / Subsets
Detailed Dry Run
[[100,200], [1000, 1250], [200, 1300]]
- Try subset [[100,200], [200,1300]]: Feasible? 100<=200 (Y), 300<=1300 (Y). Size 2.
- Try subset [[100,200], [1000,1250], [200,1300]]: Feasible? 100<=200, 1100<=1250, 1300<=1300. YES. Size 3.
Level II: Dynamic Programming
Intuition
This can be viewed as an 0/1 Knapsack problem where the 'weight' is the duration and the 'limit' is the deadline. We sort by deadline first to ensure we process courses in an order that respects feasibility.
Thought Process
- Sort courses by
lastDay. dp[i][t]= max courses among firstithat can be finished by timet.- This is still too slow (). A better DP is
dp[i][j]= min time to finishjcourses using firsticourses.
Pattern: Dynamic Programming
Detailed Dry Run
Sorted: [[100,200], [200, 1300]] dp[0][0]=0, dp[0][1]=100 (others inf) dp[1][0]=0, dp[1][1]=min(100, 200)=100, dp[1][2]=100+200=300 (300<=1300? Yes)
Level III: Optimal Greedy (Max-Heap + Retroactive Swap)
Intuition
Process courses by deadline. If a course doesn't fit, replace the longest course already taken with this shorter one to save time.
Thought Process
- Sort by
lastDay. - Maintain
timeand a Max-Heap of durations. - Add current duration to
timeand heap. - If
time > lastDay, subtract the max duration fromtime(pop heap).
Pattern: Greedy with Retroactive Swap
Detailed Dry Run
[[100, 200], [500, 600], [200, 600]]
- [100, 200]: time=100, heap=[100]
- [500, 600]: time=600, heap=[500, 100]
- [200, 600]: time=800 > 600. Pop 500. time=300, heap=[200, 100] Result: 2
Two City Scheduling
A company is planning to interview 2n people. Given the array costs where costs[i] = [aCost_i, bCost_i], the cost of flying the person to city A is aCost_i, and the cost of flying the person to city B is bCost_i.
Return the minimum cost to fly every person to a city such that exactly n people arrive in each city.
Visual Representation
Costs: [[10, 20], [30, 200], [400, 50], [30, 20]]
Profit of picking A over B (a - b):
1. 10 - 20 = -10
2. 30 - 200 = -170 (Huge saving if A)
3. 400 - 50 = +350 (Huge saving if B)
4. 30 - 20 = +10
Sorted diffs: -170, -10, +10, +350
Pick first 2 for A, last 2 for B.Examples
Fly person 1 and 2 to city A, and person 3 and 4 to city B.
Level I: Recursive Backtracking
Intuition
Try every possible way to assign people to City A and people to City B. This results in combinations.
Thought Process
solve(idx, countA, countB): Assign personidx.- Option 1: Assign to A (if
countA < n). - Option 2: Assign to B (if
countB < n). - Return minimum total cost from both paths.
Pattern: Brute Force / Combinations
Detailed Dry Run
[[10,20], [30,200]]
- A(10) then B(200) = 210
- B(20) then A(30) = 50 Min = 50.
Level II: Dynamic Programming
Intuition
This is a classic 2D DP problem. dp[i][j] represents the minimum cost for the first i+j people, with i people assigned to City A and j people assigned to City B.
Thought Process
dp[i][j]= min cost foriin A andjin B.- Transition:
dp[i][j] = min(dp[i-1][j] + costA, dp[i][j-1] + costB). - Base case:
dp[0][0] = 0.
Pattern: 2D Dynamic Programming
Detailed Dry Run
[[10,20], [30,200], [400,50], [30,20]] dp[1][0] = 10, dp[0][1] = 20 dp[1][1] = min(10+200, 20+30) = 50 dp[2][0] = 10+30 = 40 (N=2) ...
Level III: Optimal Greedy (Sorting by Relative Cost)
Intuition
Imagine we send everyone to City B first. Now we need to pick people to 'swap' to City A. To minimize total cost, we should pick the people for whom the cost reduction (or least increase) of moving to A is greatest. This is .
Thought Process
- Assume everyone goes to B initially (Total = ).
- The 'refund' we get by moving person to A instead of B is , or conversely, the 'extra cost' is .
- Sort all people by .
- Pick the first people (those with the most negative or smallest diffs) and send them to A.
Pattern: Greedy Difference Sorting
Detailed Dry Run
Costs: [[10,20], [30,200], [400,50], [30,20]] Diffs (A-B): [-10, -170, 350, 10] Sorted by diff: [-170, -10, 10, 350] People: [30,200], [10,20] -> Go to A. [30,20], [400,50] -> Go to B. Total: 30 + 10 + 20 + 50 = 110.
Hand of Straights
Alice has some number of cards and she wants to rearrange the cards into groups so that each group is of size groupSize, and consists of groupSize consecutive cards.
Given an integer array hand and an integer groupSize, return true if she can rearrange the cards, or false otherwise.
Visual Representation
Hand: [1,2,3,6,2,3,4,7,8], Size: 3
1. Smallest is 1. Start group: [1, 2, 3]. Remaining: [6, 2, 3, 4, 7, 8]
2. Smallest is 2. Start group: [2, 3, 4]. Remaining: [6, 7, 8]
3. Smallest is 6. Start group: [6, 7, 8]. Remaining: []
Result: TrueExamples
Alice's hand can be rearranged as [1,2,3],[2,3,4],[6,7,8].
Level I: Recursive Backtracking
Intuition
Try all possible ways to group the cards. For each card, either start a new group or add it to an existing incomplete group. This is highly inefficient but follows the core requirement.
Thought Process
solve(remaining_cards): Pick a card and try to form a group ofgroupSizestarting with it.- Recurse with the remaining cards.
- If all cards are used, return
true.
Pattern: Brute Force / Backtracking
Detailed Dry Run
Hand: [1,2,3,4], Size: 2
- Try [1,2], left [3,4].
- Try [3,4], left []. Success.
Level II: Sorting + Simulation (O(N^2))
Intuition
Sort the hand first. Then, repeatedly find the smallest card and try to form a group of size groupSize starting from it by searching for subsequent cards in the array.
Thought Process
- Sort
hand. - Use a boolean array
usedto mark cards. - For each
ifrom0ton-1:- If
!used[i]:- Mark
used[i] = true. This is the start of a group. - Search for
hand[i]+1, hand[i]+2, ..., hand[i]+groupSize-1in the remaining array. - If any card is not found or already used, return
false.
- Mark
- If
Pattern: Sorting and Linear Scanning
Detailed Dry Run
Hand: [1,2,2,3,3,4], Size: 3. Sorted: [1,2,2,3,3,4]
- Start with 1. Find 2 (idx 1) and 3 (idx 3). Mark used. [X, X, 2, X, 3, 4]
- Next unused is 2. Find 3 (idx 4) and 4 (idx 5). Mark used. [X, X, X, X, X, X] Success.
Level III: Optimal Greedy (TreeMap/Frequency Map)
Intuition
To form consecutive groups, every 'start' of a group must be the smallest available card. If we always start groups with the smallest remaining card, we either succeed or hit a card that cannot complete a group.
Thought Process
- Count frequencies of all cards.
- Sort unique cards (using a TreeMap or sorted keys).
- For each card that still has remaining counts:
- It MUST be the start of
count[C]groups. - Each such group requires cards .
- If any of these cards have fewer counts than
count[C], returnfalse. - Decrement counts of all cards in the group.
- It MUST be the start of
Pattern: Greedy Frequency Grouping
Detailed Dry Run
Hand: [1,2,2,3,3,4], Size: 3 Map: {1:1, 2:2, 3:2, 4:1}
- Smallest is 1. Count=1. Start group [1,2,3]. Map becomes: {1:0, 2:1, 3:1, 4:1}
- Next smallest is 2. Count=1. Start group [2,3,4]. Map becomes: {2:0, 3:0, 4:0} Result: True.
Minimum Initial Energy to Finish Tasks
You are given an array tasks where tasks[i] = [actual_i, minimum_i]:
actual_iis the actual amount of energy you spend to finish the task.minimum_iis the minimum amount of energy you need to begin the task.
You can finish the tasks in any order. Return the minimum initial amount of energy you need to finish all tasks.
Visual Representation
Tasks: [[1, 3], [2, 4], [10, 11]]
Optimal Order: Sort by (minimum - actual) descending.
1. [1, 3]: diff 2
2. [2, 4]: diff 2
3. [10, 11]: diff 1
Simulation starting with 14:
[1, 3]: 14 >= 3. Spend 1. Left 13.
[2, 4]: 13 >= 4. Spend 2. Left 11.
[10,11]: 11 >= 11. Spend 10. Left 1.
Result: 14Examples
Starting with 14 energy: finish [1,3] (left 13), [2,4] (left 11), [10,11] (left 1).
Level I: Brute Force (All Permutations)
Intuition
Since we can finish tasks in any order, we can try all permutations of tasks. For each permutation, we calculate the minimum initial energy required to complete the tasks in that specific order. The answer is the minimum of these values.
Thought Process
- Generate all permutations of tasks.
- For each permutation:
- Start with current energy (working backwards) or try binary search on initial energy.
- Simpler: starting backwards from the last task,
energy = max(min_i, energy + actual_i).
- Return the minimum
energyfound across all permutations.
Pattern: Brute Force / Permutations
Level II: Binary Search on Initial Energy
Intuition
If we can finish the tasks with initial energy E, then we can also finish them with any energy E' > E. This monotonicity allows us to binary search for the minimum energy. However, even with binary search, we still need a greedy order to verify feasibility efficiently.
Thought Process
- Search range:
[sum(actual), 10^9]. canFinish(energy): Try a greedy order (e.g., sort byminimum - actualdesc) and see if the tasks are completed.- If
canFinish(mid)is true, try smaller; else try larger.
Pattern: Binary Search on Answer
Detailed Dry Run
Try Energy 14 for [[1,3],[2,4],[10,11]]
- Task [1,3]: 14 >= 3. Spend 1. Left 13.
- Task [2,4]: 13 >= 4. Spend 2. Left 11.
- Task [10,11]: 11 >= 11. Spend 10. Left 1. True. Try Energy 13...
Level III: Optimal Greedy (Sorting)
Intuition
To minimize initial energy, we want to perform tasks that 'save' the most energy requirement for later. The 'saved' energy after a task is . Sorting tasks by this difference in descending order ensures we handle the most restrictive tasks (relative to their cost) while we have the most energy.
Thought Process
- Sort tasks by
(minimum - actual)descending. - Work backwards: Start with
0energy. For each task, you must have at leastmenergy, soenergy = max(m, energy + a). - Alternatively, work forwards: Keep track of
totalEnergyneeded andcurrentEnergyavailable.
Pattern: Greedy Sorting
Detailed Dry Run
tasks = [[1,3],[2,4],[10,11]] Sorted by (m-a) desc: [1,3] (2), [2,4] (2), [10,11] (1)
| Task | Actual | Minimal | New Energy Needed |
|---|---|---|---|
| [10,11] | 10 | 11 | max(11, 0+10) = 11 |
| [2,4] | 2 | 4 | max(4, 11+2) = 13 |
| [1,3] | 1 | 3 | max(3, 13+1) = 14 |
Maximum Number of Events That Can Be Attended
You are given an array of events where events[i] = [startDay_i, endDay_i]. Every event i starts at startDay_i and ends at endDay_i. You can attend an event i at any day d where startDay_i <= d <= endDay_i.
You can only attend one event at any day d.
Return the maximum number of events you can attend.
Visual Representation
Events: [[1, 2], [1, 2], [3, 3]]
Day 1: Attend [1, 2] (Event 1)
Day 2: Attend [1, 2] (Event 2)
Day 3: Attend [3, 3] (Event 3)
Result: 3Examples
You can attend all 3 events.
Level I: Brute Force (Recursive Scheduling)
Intuition
Try every possible combination of events you could attend. For each subset of events, check if there exists a valid day for each event such that no two events are attended on the same day and each event is attended within its [start, end] range.
Thought Process
- Use recursion to try and attend or skip each event.
- When attending an event, try every possible day in its range that hasn't been used yet.
- Keep track of the maximum number of events attended.
- This approach is or worse if checking all day combinations.
Pattern: Backtracking / Search
Level II: Greedy Simulation (O(N^2))
Intuition
For each day, we scan all available events that have not been attended yet and pick the one that ends the earliest. This is a direct simulation of the optimal strategy without a Priority Queue.
Thought Process
- Sort events by
startDay. - Maintain a
usedboolean array. - For each day
dfromminStarttomaxEnd:- Find an unvisited event
isuch thatevents[i].start <= dandevents[i].end >= dand it has the minimumendtime among all such candidates. - If found, mark
used[i] = trueand incrementcount.
- Find an unvisited event
Pattern: Greedy Scanning
Detailed Dry Run
Events: [[1,2],[1,2],[3,3]] Day 1: Both [1,2] available. Pick one. Mark used. Day 2: Only other [1,2] available. Pick it. Mark used. Day 3: [3,3] available. Pick it. Mark used. Total: 3.
Level III: Optimal Greedy (Min-Heap + Sorting)
Intuition
On any given day, if multiple events are available, we should prioritize the one that ends the earliest. This is because an event with a later end day can potentially be attended on a future day.
Thought Process
- Sort events by
startDay. - Use a Min-Heap to store the end days of currently available events.
- Iterate through days:
- Add all events starting today to the heap.
- Remove events from the heap that have already ended.
- If the heap is not empty, attend the event that ends the earliest (pop from heap) and increment count.
Pattern: Greedy / Priority Queue
Detailed Dry Run
events = [[1,2], [1,2], [3,3]]
| Day | Add to Heap | Expired | Attend | Heap |
|---|---|---|---|---|
| 1 | [2, 2] | - | Attend(2) | [2] |
| 2 | - | - | Attend(2) | [] |
| 3 | [3] | - | Attend(3) | [] |
| Result: 3 |
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.