Interview Setbook · Part 6 of 6 · Q176–Q210
C++ Mastery Series · Interview Prep

The Interview
Problem Setbook

Part 6 of 6. The grand finale covering Greedy algorithms, Backtracking, Bit Manipulation, and a Mixed Bag of advanced concepts. Every problem follows the full structure: Problem → Decode → Approach Comparison → Dry Run Trace → Annotated C++ Code → Complexity Footer.

Part 6 · Q176–Q210 Greedy Backtracking Bit Manipulation Mixed Bag
Chapter 18 · Q176–Q185 · Greedy Algorithms
Greedy Algorithms — Making the Best Local Choice
The Greedy Strategy

The Core Idea: Make the optimal choice at the current step and never look back.
When to use: When a problem exhibits "Optimal Substructure" and the "Greedy Choice Property." If you sort the data (often by end time, profit, or cost), can you just pick the best valid option sequentially? If yes, it's greedy.

Q176 · MEDIUM Non-overlapping Intervals
GreedyArray
LeetCode #435
Must KnowSort by End Time

Given an array of intervals 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.

Input[[1,2],[2,3],[3,4],[1,3]]
Output1
WhyRemove [1,3]
Decode — Keep the maximum number of non-overlapping intervals

Instead of thinking about what to remove, think about what to keep. We want to pack as many intervals as possible. An interval that ends earlier leaves more room for subsequent intervals. This is the classic Activity Selection Problem.

Sort by Start

Hard to track

Sorting by start time requires you to constantly update the end time when overlaps occur.

Time: O(n log n)
Sort by End ✓

Greedy Choice

Sort by end time. Pick the first interval, then greedily pick the next one that starts after the current one ends.

Time: O(n log n) · Space: O(1)
Approach — Sort by End Time

1. Sort intervals by their end times.
2. Track the end of the last added interval (initially the end of the first interval).
3. Iterate through the rest. If an interval starts before end, it overlaps! Increment the removal count. Otherwise, update end to this new interval's end time.

intervals = [[1,2], [2,3], [3,4], [1,3]] Sort by end time: [[1,2], [2,3], [1,3], [3,4]]
Init: count = 0, end = 2 (from [1,2])
i=1: [2,3] -> start=2. 2 >= 2 (no overlap). Update end = 3.
i=2: [1,3] -> start=1. 1 < 3 (overlap!). Increment count = 1.
i=3: [3,4] -> start=3. 3 >= 3 (no overlap). Update end = 4.
Answer = 1
C++ Solution — O(n log n) time · O(1) space int eraseOverlapIntervals(vector<vector<int>>& intervals) { if(intervals.empty()) return 0; // Custom comparator to sort by end time sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) { return a[1] < b[1]; }); int count = 0; int currentEnd = intervals[0][1]; for(int i = 1; i < intervals.size(); i++) { if(intervals[i][0] < currentEnd) { // Overlap found, we "remove" it count++; } else { // No overlap, update the boundary currentEnd = intervals[i][1]; } } return count; }
TimeO(n log n)
SpaceO(1)
Q177 · MEDIUM Gas Station
GreedyArray
LeetCode #134
Must KnowTrack Total and Current Tank

There are n gas stations along a circular route. You have a car with an unlimited gas tank. It costs cost[i] of gas to travel from station i to i+1. You begin with an empty tank. Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.

Gas[1,2,3,4,5]
Cost[3,4,5,1,2]
Output3
Decode — If total gas >= total cost, a solution exists

This problem relies on a mathematical proof: if the sum of all gas is greater than or equal to the sum of all costs, there is guaranteed to be a valid starting point. We just need to find it.

Brute Force

Simulate All Starts

Try starting at every station and looping around. Very slow for large inputs.

Time: O(n²)
Optimal ✓

Single Pass Greedy

Track current fuel. If it drops below 0, the starting point (and any point before it) is invalid. Reset start to next station.

Time: O(n) · Space: O(1)
Approach — Single Pass Greedy

Iterate through the stations. Keep a running tally of your current_gas. If at any point your current_gas drops below 0, it means you can't reach the current station from your chosen starting point.

Crucially, it also means you can't reach it from any station between your start and the current one! Therefore, reset your starting point to the next station (i + 1) and reset current_gas to 0.

gas = [1,2,3,4,5], cost = [3,4,5,1,2] i=0: net = 1-3 = -2. curr = -2. curr < 0! Reset start = 1, curr = 0.
i=1: net = 2-4 = -2. curr = -2. curr < 0! Reset start = 2, curr = 0.
i=2: net = 3-5 = -2. curr = -2. curr < 0! Reset start = 3, curr = 0.
i=3: net = 4-1 = 3. curr = 3.
i=4: net = 5-2 = 3. curr = 6.
Total Gas (15) > Total Cost (15). Start is 3.
C++ Solution — O(n) time · O(1) space int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { int total_gas = 0, total_cost = 0; int current_gas = 0; int starting_station = 0; for(int i = 0; i < gas.size(); i++) { total_gas += gas[i]; total_cost += cost[i]; current_gas += gas[i] - cost[i]; // If we run out of gas, this starting point (and all previous) is invalid if(current_gas < 0) { starting_station = i + 1; current_gas = 0; } } if(total_gas < total_cost) return -1; return starting_station; }
TimeO(n)
SpaceO(1)
Q178 · EASY Assign Cookies
GreedyArray
LeetCode #455
Must KnowSort and Two Pointers

You have g children with a greed factor and s cookies with a size. A child will be content if the cookie size is ≥ their greed. Maximize the number of content children. Each child gets at most one cookie.

Children (g)[1,2,3]
Cookies (s)[1,1]
Output1
Decode — Feed the least greedy children with the smallest possible cookies

To maximize the number of content children, always try to satisfy the least greedy child first. And to save your big cookies for greedier children, satisfy them using the smallest cookie that meets their requirement.

Brute Force

Nested Loops

For each child, scan the entire cookie array to find a valid cookie. O(N*M) time.

Time: O(n * m)
Sort + Pointers ✓

Greedy Match

Sort both arrays. Use two pointers to iterate. If cookie size ≥ child's greed, move both. Otherwise, just move the cookie pointer.

Time: O(n log n) · Space: O(1)
Approach — Sort & Two Pointers

Sort both the children and cookie arrays in ascending order. Initialize two pointers at 0. If the current cookie is large enough for the current child (s[cookie] >= g[child]), the child gets the cookie (increment child). Regardless of whether it fits, increment the cookie pointer to look at the next cookie.

g = [1,2,3], s = [1,1] Sort g: [1,2,3], Sort s: [1,1]
child=0 (greed 1), cookie=0 (size 1). 1 >= 1. Match! child=1, cookie=1.
child=1 (greed 2), cookie=1 (size 1). 1 < 2. Too small. cookie=2.
cookie=2 is out of bounds. Loop ends.
Content children = 1.
C++ Solution — O(n log n) time int findContentChildren(vector<int>& g, vector<int>& s) { sort(g.begin(), g.end()); sort(s.begin(), s.end()); int child = 0, cookie = 0; while(child < g.size() && cookie < s.size()) { if(s[cookie] >= g[child]) { // Cookie is big enough, satisfy the child child++; } // Always move to the next cookie cookie++; } return child; }
TimeO(n log n + m log m)
SpaceO(1)
Q179 · EASY Lemonade Change
GreedyArray
LeetCode #860
Must KnowPrioritize giving $10s as change

Customers buy a $5 lemonade and pay with $5, $10, or $20 bills. You must provide the correct change. Return true if you can provide every customer with correct change.

Input[5,5,5,10,20]
Outputtrue
Decode — Keep your $5 bills as long as possible

When a customer hands you a $20, you have two ways to make change: one $10 and one $5, or three $5s. A $5 bill is more versatile because it's strictly required to make change for a $10. Therefore, greedily part with your $10s first to save your $5s.

Hash Map Counting

Track all bills

Use a map to store count of 5, 10, 20. Overkill and uses extra space unnecessarily.

Time: O(n) · Space: O(n)
Variables ✓

Greedy O(1) Memory

Just track `fives` and `tens` counters. For a 20, subtract a 10 and a 5 if possible.

Time: O(n) · Space: O(1)
Approach — Counter Variables

Initialize fives = 0 and tens = 0. Loop through bills. If 5, fives++. If 10, check if fives > 0, decrement it, and tens++. If 20, greedily check if we have a 10 and a 5. If not, check if we have three 5s. If neither, return false.

bills = [5,5,10,20] i=0 (bill=5): fives=1, tens=0
i=1 (bill=5): fives=2, tens=0
i=2 (bill=10): need $5 change. fives=1, tens=1
i=3 (bill=20): need $15 change. Use $10 + $5 (greedy!). fives=0, tens=0.
Result = true
C++ Solution — O(n) time · O(1) space bool lemonadeChange(vector<int>& bills) { int fives = 0, tens = 0; for(int bill : bills) { if(bill == 5) { fives++; } else if(bill == 10) { if(fives == 0) return false; fives--; tens++; } else { // bill == 20 // Greedily use a $10 bill first if(tens > 0 && fives > 0) { tens--; fives--; } else if(fives >= 3) { fives -= 3; } else { return false; } } } return true; }
TimeO(n)
SpaceO(1)
Q180 · MEDIUM Min Arrows to Burst Balloons
GreedyIntervals
LeetCode #452
Must KnowInterval Overlap Variant

Balloons are represented as 2D intervals [x_start, x_end]. An arrow shot up at exactly x bursts all balloons where x_start <= x <= x_end. Find the minimum arrows needed to burst all balloons.

Input[[10,16],[2,8],[1,6],[7,12]]
Output2
Decode — Find the maximum number of mutually exclusive overlapping clusters

This is structurally identical to Non-overlapping Intervals (Q176). If you sort the balloons by their end coordinate, you can shoot an arrow at the very end of the first balloon, guaranteeing you pop it and any others overlapping that coordinate.

Sort by Start

Constantly update min_end

If sorted by start, you have to keep track of the minimum end coordinate of overlapping balloons to know when an arrow MUST be shot.

Sort by End ✓

Shoot at the End

Sort by end. The greedy choice is to shoot at the end of the first balloon, popping everything that starts before this point.

Time: O(n log n) · Space: O(1)
Approach — Sort by End Coordinate

Sort the array based on the end coordinate. Initialize arrows = 1 and currentEnd = points[0][1]. Loop through the balloons. If a balloon's start point is greater than currentEnd, it means it doesn't overlap with our current cluster, so we must shoot another arrow (arrows++) and update currentEnd to this new balloon's end point.

points = [[10,16], [2,8], [1,6], [7,12]] Sort by end: [[1,6], [2,8], [7,12], [10,16]]
Init: arrows = 1, end = 6 (from [1,6])
i=1: [2,8]. start 2 <= 6. Overlaps! Same arrow pops it.
i=2: [7,12]. start 7 > 6. No overlap. Need new arrow. arrows = 2, end = 12.
i=3: [10,16]. start 10 <= 12. Overlaps! Same arrow pops it.
Answer = 2
C++ Solution — O(n log n) time int findMinArrowShots(vector<vector<int>>& points) { if(points.empty()) return 0; // Sort by end coordinate sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b) { return a[1] < b[1]; }); int arrows = 1; int currentEnd = points[0][1]; for(int i = 1; i < points.size(); i++) { if(points[i][0] > currentEnd) { // This balloon starts after our last arrow, need a new one arrows++; currentEnd = points[i][1]; } } return arrows; }
TimeO(n log n)
SpaceO(1) excluding sorting
Q181 · MEDIUM Merge Intervals
GreedyIntervals
LeetCode #56
Must KnowAsked 10,000+Sort by Start Time

Given an array of intervals where intervals[i] = [start_i, end_i], merge all overlapping intervals, and return an array of the non-overlapping intervals.

Input[[1,3],[2,6],[8,10]]
Output[[1,6],[8,10]]
Decode — If sorting by start time, overlapping intervals will be adjacent

Unlike Q176 or Q180 where we sort by end time to remove/skip, here we sort by start time because we want to combine. If the next interval starts before or exactly when the current one ends, they overlap and their new end is the max of both ends.

Brute Force

Compare all pairs

Check every interval against every other interval and merge if they overlap. Very inefficient.

Time: O(N²)
Optimal ✓

Sort and Merge

Sort by start time. Iterate once, comparing the current interval with the last merged interval.

Time: O(N log N) · Space: O(N)
Approach — Sort and Extend

Sort the array. Add the first interval to a merged list. Iterate through the rest. For each interval, check if its start is <= the end of the last interval in the merged list. If yes, update the end of the last interval to be the max of both ends. If no, push it to the list as a new non-overlapping interval.

intervals = [[1,3], [8,10], [2,6]] Sort by start: [[1,3], [2,6], [8,10]]
Init: merged = [[1,3]]
i=1: [2,6]. start 2 <= last end 3. Overlap! Update last end = max(3, 6) = 6. merged = [[1,6]].
i=2: [8,10]. start 8 > last end 6. No overlap. Push to merged.
Answer = [[1,6], [8,10]]
C++ Solution — O(n log n) time vector<vector<int>> merge(vector<vector<int>>& intervals) { if(intervals.empty()) return {}; // Sort by start times sort(intervals.begin(), intervals.end()); vector<vector<int>> merged; merged.push_back(intervals[0]); for(int i = 1; i < intervals.size(); i++) { // Get the last inserted interval vector<int>& last = merged.back(); if(intervals[i][0] <= last[1]) { // Overlapping intervals, update the end last[1] = max(last[1], intervals[i][1]); } else { // Disjoint interval, add it to the list merged.push_back(intervals[i]); } } return merged; }
TimeO(n log n)
SpaceO(n) for output array
Q182 · HARD Candy
GreedyArray
LeetCode #135
Must KnowTwo Passes: Left-to-Right, then Right-to-Left

There are n children in a line with ratings. You must give candies so that: 1. Every child gets at least 1 candy. 2. Children with higher ratings get more candies than their neighbors. Return the minimum candies needed.

Input[1,0,2]
Output5
Why2, 1, 2 = 5
Decode — Resolve conflicts by fulfilling rules in one direction at a time

Trying to check both left and right neighbors simultaneously is chaotic. Break it down: first, sweep left-to-right to ensure kids with higher ratings than their left neighbor get more candy. Then, sweep right-to-left to ensure kids with higher ratings than their right neighbor get more, without breaking the first rule (by taking the max).

Two Arrays

LeftArr and RightArr

Do L-to-R pass into one array, R-to-L into another. Final candy is max(L[i], R[i]).

Time: O(N) · Space: O(N)
One Array ✓

In-Place Update

Use one array. L-to-R pass. Then R-to-L pass, updating the same array using max().

Time: O(N) · Space: O(N)
Approach — The 2-Pass Greedy Technique

Create a candies array initialized to 1 for all children.
1. Left-to-Right: Loop from index 1. If rating[i] > rating[i-1], set candies[i] = candies[i-1] + 1.
2. Right-to-Left: Loop backwards from n-2. If rating[i] > rating[i+1], set candies[i] = max(candies[i], candies[i+1] + 1). Finally, sum the array.

ratings = [1, 0, 2] Init: candies = [1, 1, 1]

Left-to-Right Pass:
i=1: 0 < 1. Skip. candies = [1, 1, 1]
i=2: 2 > 0. Update candies[2] = candies[1]+1 = 2. candies = [1, 1, 2]

Right-to-Left Pass:
i=1: 0 < 2. Skip. candies = [1, 1, 2]
i=0: 1 > 0. Update candies[0] = max(1, 1+1) = 2. candies = [2, 1, 2]
Total = 2 + 1 + 2 = 5
C++ Solution — O(n) time · O(n) space int candy(vector<int>& ratings) { int n = ratings.size(); vector<int> candies(n, 1); // Left-to-right pass for(int i = 1; i < n; i++) { if(ratings[i] > ratings[i-1]) { candies[i] = candies[i-1] + 1; } } // Right-to-left pass for(int i = n - 2; i >= 0; i--) { if(ratings[i] > ratings[i+1]) { candies[i] = max(candies[i], candies[i+1] + 1); } } int total = 0; for(int c : candies) total += c; return total; }
TimeO(n)
SpaceO(n)
Q183 · MEDIUM Task Scheduler
GreedyMath
LeetCode #621
Must KnowDesign around the most frequent task

You are given an array of CPU tasks (A-Z) and a cooling time n. Identical tasks must be separated by at least n intervals. Find the minimum intervals needed to complete all tasks.

Tasks["A","A","A","B","B","B"]
n2
Output8
Decode — Find the bottleneck (the task with the highest frequency)

The most frequent task dictates the minimum length. If task 'A' appears 3 times and cooldown is 2, the frame looks like: A _ _ A _ _ A. The minimum length is (max_freq - 1) * (n + 1) + tasks_with_max_freq. If there are enough other tasks to fill all the gaps, the answer is just the total number of tasks!

Max Heap Simulation

Simulate Time

Use a priority queue and a cooldown queue to simulate every tick of the CPU. Good to know, but overkill here.

Time: O(N log 26)
Math Formula ✓

Greedy Gap Calculation

Calculate idle slots based purely on the highest frequency task. Fill slots with remaining tasks.

Time: O(N) · Space: O(1)
Approach — Math-based Greedy

1. Count frequencies of all tasks.
2. Find the highest frequency (maxFreq).
3. Count how many tasks have this exact maxFreq (maxFreqCount).
4. Calculate the base time: ans = (maxFreq - 1) * (n + 1) + maxFreqCount.
5. The answer is the max(ans, tasks.size()). If tasks.size() is larger, it means we had enough tasks to fill all idle slots naturally without needing extra cooldowns.

tasks = [A,A,A,B,B,B], n = 2 Frequencies: A:3, B:3
maxFreq = 3 (A or B)
maxFreqCount = 2 (Both A and B appear 3 times)
Math: (3 - 1) * (2 + 1) + 2 = 2 * 3 + 2 = 8.
Max(8, 6) = 8. (Execution: A, B, idle, A, B, idle, A, B)
C++ Solution — O(n) time · O(1) space int leastInterval(vector<char>& tasks, int n) { vector<int> freq(26, 0); int maxFreq = 0; for(char c : tasks) { freq[c - 'A']++; maxFreq = max(maxFreq, freq[c - 'A']); } int maxFreqCount = 0; for(int f : freq) { if(f == maxFreq) maxFreqCount++; } int ans = (maxFreq - 1) * (n + 1) + maxFreqCount; return max((int)tasks.size(), ans); }
TimeO(N)
SpaceO(1) (Array of size 26)
Q184 · MEDIUM Fractional Knapsack
GreedyMath
Classic Interview Problem
Must KnowSort by Value-to-Weight Ratio

Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value. Unlike 0/1 Knapsack, you can break items into fractions.

W = 50Vals:[60,100,120], Wts:[10,20,30]
Output240.0
Decode — Since fractions are allowed, Greedy > DP

Because we can slice an item, we simply want the "best bang for our buck." Calculate the value / weight ratio for each item. Sort items descending by this ratio. Take as much as you can of the highest ratio item, then move to the next.

Dynamic Programming

0/1 Knapsack Logic

Fails or is incredibly inefficient here because the weights can be fractional. DP is for discrete choices.

Greedy Ratio ✓

Sort by Val/Wt

The optimal choice is always to take the item with the highest value per unit of weight.

Time: O(N log N) · Space: O(1)
Approach — Value/Weight Sort

Sort the items using a custom comparator that calculates (double)value / weight and sorts descending. Loop through the sorted items. If the item's weight is <= the remaining capacity W, take the whole thing. If it's greater, take a fraction: (remaining W / item's weight) * item's value, and break the loop since the knapsack is full.

W = 50, Items = { (60,10), (100,20), (120,30) } Ratios: 60/10 = 6, 100/20 = 5, 120/30 = 4. (Already sorted)
i=0: wt 10 <= W(50). Take all. Val = 60, W = 40.
i=1: wt 20 <= W(40). Take all. Val = 60+100 = 160, W = 20.
i=2: wt 30 > W(20). Take fraction! Fraction = 20/30. Val += 120 * (2/3) = 80.
Total Val = 160 + 80 = 240.0
C++ Solution — O(n log n) time // Assume Item struct exists: struct Item { int value, weight; }; double fractionalKnapsack(int W, Item arr[], int n) { // Custom sort descending by ratio sort(arr, arr + n, [](Item a, Item b) { double r1 = (double)a.value / a.weight; double r2 = (double)b.value / b.weight; return r1 > r2; }); double finalValue = 0.0; for (int i = 0; i < n; i++) { if (arr[i].weight <= W) { W -= arr[i].weight; finalValue += arr[i].value; } else { // Take a fraction of the remaining item finalValue += arr[i].value * ((double)W / arr[i].weight); break; } } return finalValue; }
TimeO(n log n)
SpaceO(1)
Q185 · MEDIUM Minimum Platforms
GreedyTwo Pointers
GFG Classic
Must KnowMerge Sort style two-pointer sweep

Given arrival and departure times of all trains that reach a railway station, find the minimum number of platforms required so that no train is kept waiting.

Arrivals[900, 940, 950]
Departures[910, 1200, 1120]
Output2
Decode — Track active overlapping intervals as a timeline

Instead of tying arrivals and departures to specific trains, think of them as independent events on a timeline. Sorting both arrays independently and walking through them like the "merge" step of merge sort lets you track how many trains are currently stationed. Arrival = +1 platform, Departure = -1 platform.

Brute Force

Nested Loops

Count how many intervals overlap with every single interval. Max overlap is the answer. O(N²) time.

Two Pointers ✓

Timeline Sweep

Sort both independently. Move pointers based on which event (arrival or departure) happens next.

Time: O(N log N) · Space: O(1)
Approach — Timeline Simulation

Sort arr and dep arrays. Use pointers i=1 (for arr) and j=0 (for dep). Initialize platforms = 1 and max_platforms = 1. While pointers are valid: if arr[i] <= dep[j], a train arrived before the previous one left, so increment platforms and i. Otherwise, a train departed, so decrement platforms and increment j. Track the max.

arr=[900,940,950], dep=[910,1120,1200] (Sorted) Init: platforms = 1, i=1 (940), j=0 (910)
940 > 910: Departure happens first! platforms--, j++. (platforms=0, j=1(1120))
940 <= 1120: Arrival! platforms++, i++. (platforms=1, i=2(950))
950 <= 1120: Arrival! platforms++, i++. (platforms=2, i=3)
i is out of bounds. Max platforms seen = 2.
C++ Solution — O(n log n) time int findPlatform(int arr[], int dep[], int n) { // Sort arrivals and departures independently sort(arr, arr + n); sort(dep, dep + n); int platforms = 1, maxPlatforms = 1; int i = 1; // Pointer for arrivals int j = 0; // Pointer for departures while (i < n && j < n) { if (arr[i] <= dep[j]) { // Train arrived before or right as the previous departed platforms++; i++; } else { // A train departed platforms--; j++; } maxPlatforms = max(maxPlatforms, platforms); } return maxPlatforms; }
TimeO(N log N)
SpaceO(1)
↑ back to top
Chapter 19 · Q186–Q195 · Backtracking
Backtracking — Explore All Options, Undo When Wrong
Q186 · MEDIUM Permutations
BacktrackingArray
LeetCode #46
Must KnowChoose, Explore, Un-choose

Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

Input[1,2,3]
Output[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Decode — Generate all possible orderings by swapping

A permutation is just a rearrangement of elements. Instead of creating new arrays and tracking visited elements, we can build permutations in-place by swapping elements. At each position, swap the current element with every other available element, explore that path, and then swap them back (backtrack).

Visited Array

Extra Space

Use a boolean array to track used elements and build a new path array. Easy to write, but uses O(N) extra space.

Time: O(N * N!) · Space: O(N)
In-Place Swaps ✓

Optimal Backtracking

Swap elements in the original array to generate branches. Revert swaps when returning.

Time: O(N * N!) · Space: O(N) stack
Approach — The Classic Backtracking Template

Use a recursive function backtrack(start). Base case: if start == nums.size(), add the current nums array to the result. Recursive step: iterate i from start to the end. Choose: swap(nums[start], nums[i]). Explore: call backtrack(start + 1). Un-choose: swap(nums[start], nums[i]) to restore the array for the next iteration.

nums = [1, 2, 3] start=0: i=0, swap(0,0) → [1,2,3], recurse(start=1)
  start=1: i=1, swap(1,1) → [1,2,3], recurse(start=2)
    start=2: i=2, swap(2,2) → [1,2,3], recurse(3) → Save [1,2,3]
  start=1: i=2, swap(1,2) → [1,3,2], recurse(start=2)
    start=2: i=2, swap(2,2) → [1,3,2], recurse(3) → Save [1,3,2]
start=0: i=1, swap(0,1) → [2,1,3], recurse(start=1) ... (continues)
C++ Solution — O(n * n!) time class Solution { private: void backtrack(vector<int>& nums, int start, vector<vector<int>>& result) { if(start == nums.size()) { result.push_back(nums); return; } for(int i = start; i < nums.size(); i++) { swap(nums[start], nums[i]); // Choose backtrack(nums, start + 1, result); // Explore swap(nums[start], nums[i]); // Un-choose (Backtrack) } } public: vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> result; backtrack(nums, 0, result); return result; } };
TimeO(n * n!)
SpaceO(n)
Q187 · HARD N-Queens
BacktrackingMatrix
LeetCode #51
Must KnowO(1) Attack Validation via Arrays

The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other. Return all distinct solutions.

Inputn = 4
Output[[".Q..","...Q","Q...","..Q."], ["..Q.","Q...","...Q",".Q.."]]
Decode — Place row by row, tracking attacked columns and diagonals

We only place one queen per row, so row attacks are impossible. For column and diagonal attacks, instead of scanning the board (which takes O(n) time per check), we can use boolean arrays. Main diagonal ID: row - col + (n - 1). Anti-diagonal ID: row + col.

Naive Backtracking

Scan Board

For every placement, iterate vertically and diagonally to check if safe. Slower validation.

Time: O(N!)
Optimal ✓

Boolean Hash Arrays

Use arrays to track occupied columns and diagonals in O(1) time.

Time: O(N!) · Space: O(N)
Approach — Fast O(1) Validation Lookups

Create a boolean array for cols (size n), diag1 (size 2n), and diag2 (size 2n). Iterate through columns for the current row. If col, d1, and d2 are all false, place the Queen, set the arrays to true, and recurse to row + 1. On backtrack, set the arrays back to false and remove the Queen.

n = 4 row=0: Place Q at (0,0). cols[0]=T, diag1[3]=T, diag2[0]=T. Recurse row=1.
row=1: col=0 (attacked). col=1 (attacked by diag). col=2 (safe!). Place Q at (1,2). Recurse row=2.
row=2: All cols attacked! Return → Backtrack to row=1.
row=1: Remove Q from (1,2). Place at (1,3). Recurse row=2...
Finds valid configuration: (0,1), (1,3), (2,0), (3,2)
C++ Solution — Fast Array Lookups class Solution { void solve(int row, int n, vector<string>& board, vector<vector<string>>& ans, vector<bool>& cols, vector<bool>& diag1, vector<bool>& diag2) { if (row == n) { ans.push_back(board); return; } for (int col = 0; col < n; col++) { int d1 = row - col + n - 1; // Main diagonal trick int d2 = row + col; // Anti-diagonal trick if (!cols[col] && !diag1[d1] && !diag2[d2]) { board[row][col] = 'Q'; cols[col] = diag1[d1] = diag2[d2] = true; solve(row + 1, n, board, ans, cols, diag1, diag2); board[row][col] = '.'; // Backtrack cols[col] = diag1[d1] = diag2[d2] = false; } } } public: vector<vector<string>> solveNQueens(int n) { vector<vector<string>> ans; vector<string> board(n, string(n, '.')); vector<bool> cols(n, false), diag1(2*n, false), diag2(2*n, false); solve(0, n, board, ans, cols, diag1, diag2); return ans; } };
TimeO(N!)
SpaceO(N)
Q188 · MEDIUM Subsets
BacktrackingArray
LeetCode #78
Must KnowPick or Don't Pick

Given an integer array nums of unique elements, return all possible subsets (the power set). The solution set must not contain duplicate subsets.

Input[1,2,3]
Output[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
Decode — Every node in the recursion tree is a valid subset

Unlike permutations where we only record the answer at the end of the recursion tree (when length == n), for subsets, every path we take is a valid subset. We simply add the current path to our results at the very beginning of every recursive call.

Cascading / Iterative

Copy and Add

Start with [[]]. For each num, take existing subsets, copy them, and add num. Very fast but consumes more memory.

Time: O(N * 2^N)
Backtracking ✓

DFS Search

Explore all paths via DFS. Add node to subset, recurse, remove node.

Time: O(N * 2^N) · Space: O(N)
Approach — DFS Backtracking

Create a backtrack(start, path) function. First thing inside: result.push_back(path). Then loop i from start to nums.size() - 1. Inside loop: push nums[i] to path, recurse with i + 1, pop from path.

nums = [1, 2] start=0, path=[] → Save []
  i=0: push 1. recurse(1, [1]) → Save [1]
    i=1: push 2. recurse(2, [1,2]) → Save [1,2]
    pop 2. path=[1]
  pop 1. path=[]
  i=1: push 2. recurse(2, [2]) → Save [2]
  pop 2. path=[]
C++ Solution — O(N * 2^N) time class Solution { void backtrack(int start, vector<int>& nums, vector<int>& path, vector<vector<int>>& result) { // Every path generated is a valid subset result.push_back(path); for(int i = start; i < nums.size(); i++) { path.push_back(nums[i]); // Choose backtrack(i + 1, nums, path, result); // Explore path.pop_back(); // Un-choose } } public: vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> result; vector<int> path; backtrack(0, nums, path, result); return result; } };
TimeO(N * 2^N)
SpaceO(N)
Q189 · MEDIUM Subsets II (With Duplicates)
BacktrackingArray
LeetCode #90
Must KnowSort and Skip Duplicates

Given an integer array nums that may contain duplicates, return all possible subsets. The solution set must not contain duplicate subsets.

Input[1,2,2]
Output[[],[1],[1,2],[1,2,2],[2],[2,2]]
Decode — If you've seen the number at this level of the tree, skip it

To avoid duplicate subsets like generating [1, 2a] and [1, 2b], we must sort the array first. Then, during the loop inside our backtracking function, if i > start and nums[i] == nums[i-1], we continue to skip it.

HashSet

Store and Filter

Generate all subsets normally, sort each subset, and store in a HashSet to filter duplicates. Very slow.

Sort + Prune ✓

Tree Pruning

Sort the array. If the current element is the same as the previous (at the same tree level), skip it.

Time: O(N * 2^N) · Space: O(N)
Approach — Sort and Skip

Sort the nums array first. Inside the backtrack loop, add a condition: if(i > start && nums[i] == nums[i-1]) continue;. This ensures that if we have `[1, 2, 2]`, we explore the first `2`, but skip the second `2` at that specific level, preventing duplicate branches.

nums = [1, 2, 2] (Sorted) Save []
i=0: push 1. Save [1]
  i=1: push 2. Save [1,2]
    i=2: push 2. Save [1,2,2]
  i=2: i(2) > start(1) && 2 == 2. SKIP! (Prevents duplicate [1,2])
i=1: push 2. Save [2]
  i=2: push 2. Save [2,2]
i=2: i(2) > start(0) && 2 == 2. SKIP! (Prevents duplicate [2])
C++ Solution class Solution { void backtrack(int start, vector<int>& nums, vector<int>& path, vector<vector<int>>& result) { result.push_back(path); for(int i = start; i < nums.size(); i++) { // Skip duplicates to avoid duplicate subsets if(i > start && nums[i] == nums[i-1]) continue; path.push_back(nums[i]); backtrack(i + 1, nums, path, result); path.pop_back(); } } public: vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<vector<int>> result; vector<int> path; sort(nums.begin(), nums.end()); // Crucial step for duplicate logic backtrack(0, nums, path, result); return result; } };
TimeO(N * 2^N)
SpaceO(N)
Q190 · MEDIUM Combination Sum
BacktrackingArray
LeetCode #39
Must KnowYou can reuse the same element

Given an array of distinct integers and a target integer, return a list of all unique combinations where the chosen numbers sum to target. You may choose the same number an unlimited number of times.

Inputc = [2,3,6,7], target = 7
Output[[2,2,3],[7]]
Decode — Pass 'i' instead of 'i + 1' to the recursive call

Because we can reuse elements, when we make the recursive backtrack call after choosing nums[i], we don't move to the next index. We pass i again. We stop when the target becomes 0 (success) or less than 0 (failure).

Take or Not Take

Binary Choice

At each step, either take the number and stay, or don't take and move to `i+1`. Valid, but loop-based is cleaner.

Loop + Reuse ✓

DFS with target tracking

Loop from `start`. Subtract from target. Recurse passing `i` instead of `i+1`.

Time: O(2^T) · Space: O(T)
Approach — Subtraction Backtracking

Pass the remaining target into the function. Base case 1: target < 0 (return). Base case 2: target == 0 (save path and return). Inside the loop, recurse via backtrack(i, target - nums[i]). Passing i allows infinite reuse. The loop naturally moves to i+1 when we backtrack.

c = [2, 3], target = 4 target=4: i=0 (2). path=[2], recurse(target=2, start=0)
  target=2: i=0 (2). path=[2,2], recurse(target=0, start=0)
    target=0. Save [2,2]. Return.
  target=2: i=1 (3). path=[2,3], recurse(target=-1) → Fail.
target=4: i=1 (3). path=[3], recurse(target=1, start=1)
  target=1: i=1 (3). path=[3,3], recurse(target=-2) → Fail.
C++ Solution class Solution { void backtrack(int start, int target, vector<int>& nums, vector<int>& path, vector<vector<int>>& result) { if(target < 0) return; if(target == 0) { result.push_back(path); return; } for(int i = start; i < nums.size(); i++) { path.push_back(nums[i]); // Pass 'i' instead of 'i + 1' to allow reuse backtrack(i, target - nums[i], nums, path, result); path.pop_back(); } } public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<int>> result; vector<int> path; backtrack(0, target, candidates, path, result); return result; } };
TimeO(2^target)
SpaceO(target) for stack
Q191 · MEDIUM Combination Sum II
BacktrackingArray
LeetCode #40
Must KnowCombine Q189 (Skip Dups) and Q190 (Summing)

Given a collection of candidate numbers (with duplicates) and a target, find all unique combinations that sum to target. Each number may only be used once.

Inputc = [10,1,2,7,6,1,5], t = 8
Output[[1,1,6],[1,2,5],[1,7],[2,6]]
Decode — Sort, skip duplicates, and pass i + 1

Since we can't reuse elements, we pass i + 1 to the recursive call. Since there are duplicates in the input array, we must sort the array and use the if (i > start && candidates[i] == candidates[i-1]) continue; trick from Subsets II to avoid duplicate combinations.

Naive Hashset

Filter at End

Find all combinations, sort each one, store in a Set. Will TLE (Time Limit Exceeded).

Sort + Prune ✓

Early Stopping

Sort the array. Skip duplicates. Stop loop early if `nums[i] > target`.

Time: O(2^N) · Space: O(N)
Approach — Sorted Backtracking

Sort the array. Base cases: target == 0 (save) and target < 0 (return). In the loop, add duplicate skipping. Add an optimization: if(nums[i] > target) break; since the array is sorted, no subsequent numbers will be valid either! Pass i + 1 to recurse.

c = [1, 1, 2], target = 3 (Sorted) target=3: i=0 (1). path=[1], recurse(target=2, start=1)
  target=2: i=1 (1). path=[1,1], recurse(target=1, start=2)
    target=1: i=2 (2). 2 > 1. Break early!
  target=2: i=2 (2). path=[1,2], recurse(target=0) → Save [1,2]
target=3: i=1 (1). i(1) > start(0) && 1 == 1. Skip Duplicate!
target=3: i=2 (2). path=[2], recurse(target=1).
C++ Solution class Solution { void backtrack(int start, int target, vector<int>& nums, vector<int>& path, vector<vector<int>>& result) { if(target < 0) return; if(target == 0) { result.push_back(path); return; } for(int i = start; i < nums.size(); i++) { // Skip duplicates if(i > start && nums[i] == nums[i-1]) continue; // Optimization: if the number exceeds target, break early since array is sorted if(nums[i] > target) break; path.push_back(nums[i]); backtrack(i + 1, target - nums[i], nums, path, result); path.pop_back(); } } public: vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { vector<vector<int>> result; vector<int> path; sort(candidates.begin(), candidates.end()); backtrack(0, target, candidates, path, result); return result; } };
TimeO(2^N)
SpaceO(N)
Q192 · MEDIUM Combinations
BacktrackingMath
LeetCode #77
Must KnowSubset logic, but constrained by size k

Given two integers n and k, return all possible combinations of k numbers chosen from the range [1, n].

Inputn = 4, k = 2
Output[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
Decode — Just like subsets, but stop when path length == k

Instead of iterating over an array, we are generating numbers from 1 to n using a loop. We push the path to our results only when its size reaches k. We can also optimize: if we need 2 more numbers but only 1 is left in the range, we can stop early!

Standard DFS

No Pruning

Explore all subset branches and save only those with length k. Wastes time exploring impossible paths.

Pruned DFS ✓

Math Optimization

Stop loop if remaining elements are fewer than needed to reach length k.

Time: O(C(n,k)) · Space: O(k)
Approach — Pruned Backtracking

Base case: path.size() == k. Save and return. Loop i from start. The maximum value i can be is n - needed + 1, where needed = k - path.size(). Push i, recurse with i + 1, pop.

n = 3, k = 2 start=1, path=[] (need 2). limit = 3 - 2 + 1 = 2.
  i=1: path=[1], recurse(2)
    start=2, path=[1] (need 1). limit = 3 - 1 + 1 = 3.
    i=2: path=[1,2] → Save [1,2]
    i=3: path=[1,3] → Save [1,3]
  i=2: path=[2], recurse(3)
    start=3, path=[2] (need 1). limit = 3.
    i=3: path=[2,3] → Save [2,3]
C++ Solution class Solution { void backtrack(int start, int n, int k, vector<int>& path, vector<vector<int>>& result) { if(path.size() == k) { result.push_back(path); return; } // Optimization: if remaining elements aren't enough to fill to size k, break loop int needed = k - path.size(); for(int i = start; i <= n - needed + 1; i++) { path.push_back(i); backtrack(i + 1, n, k, path, result); path.pop_back(); } } public: vector<vector<int>> combine(int n, int k) { vector<vector<int>> result; vector<int> path; backtrack(1, n, k, path, result); return result; } };
TimeO(C(n,k))
SpaceO(k)
Q193 · MEDIUM Palindrome Partitioning
BacktrackingString
LeetCode #131
Must KnowCut string into valid pieces

Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.

Input"aab"
Output[["a","a","b"],["aa","b"]]
Decode — If the prefix is a palindrome, recurse on the suffix

Loop through the string to make a "cut". Check if the substring from start to i is a palindrome. If it is, add it to your path and recursively ask the function to partition the rest of the string starting from i + 1.

DP + Backtracking

Precompute Palindromes

Use O(N²) DP to precompute all palindromes. Then backtrack. Slightly faster, but complex.

Pure Backtrack ✓

On-the-fly Check

Use a helper function to check palindrome status during the DFS. Clean and standard.

Time: O(N * 2^N) · Space: O(N)
Approach — Prefix Cut Recursion

Base case: start == s.length(). Save path. Loop i from start to end. Check isPalindrome(s, start, i). If true, extract the substring s.substr(start, i - start + 1), push it, recurse with i + 1, and pop.

s = "aab" start=0: i=0, "a" is Pal. path=["a"]. Recurse(1)
  start=1: i=1, "a" is Pal. path=["a","a"]. Recurse(2)
    start=2: i=2, "b" is Pal. path=["a","a","b"]. Recurse(3) → Save
  start=1: i=2, "ab" Not Pal!
start=0: i=1, "aa" is Pal. path=["aa"]. Recurse(2)
  start=2: i=2, "b" is Pal. path=["aa","b"]. Recurse(3) → Save
start=0: i=2, "aab" Not Pal!
C++ Solution class Solution { bool isPalindrome(const string& s, int left, int right) { while(left < right) { if(s[left++] != s[right--]) return false; } return true; } void backtrack(int start, const string& s, vector<string>& path, vector<vector<string>>& result) { if(start == s.length()) { result.push_back(path); return; } for(int i = start; i < s.length(); i++) { if(isPalindrome(s, start, i)) { path.push_back(s.substr(start, i - start + 1)); backtrack(i + 1, s, path, result); path.pop_back(); } } } public: vector<vector<string>> partition(string s) { vector<vector<string>> result; vector<string> path; backtrack(0, s, path, result); return result; } };
TimeO(N * 2^N)
SpaceO(N)
Q194 · MEDIUM Word Search
BacktrackingMatrix
LeetCode #79
Must KnowDFS + Backtracking on Grid

Given an m x n grid of characters board and a string word, return true if word exists in the grid. The word can be constructed from letters of sequentially adjacent cells (horizontal/vertical). The same letter cell may not be used more than once.

Decode — Temporarily mark cells as visited to avoid reusing them

We run a DFS starting from every cell that matches the first letter. To ensure we don't visit the same cell twice in a single path, we temporarily change the character on the board (e.g., to *). After exploring all 4 directions, we backtrack by restoring the original character.

Visited Matrix

Extra Memory

Create a `vector> visited` to track cells. Uses extra O(M*N) space.

In-Place Marking ✓

Space Optimized

Modify board directly. Save char, set to `*`, recurse, restore char.

Time: O(M*N*4^L) · Space: O(L)
Approach — Grid DFS

1. Double loop to find `board[r][c] == word[0]`. Start DFS.
2. DFS Base cases: index == word.length() return true. Bounds check & char match check.
3. Save temp = board[r][c]. Set board[r][c] = '*'.
4. Recurse 4 directions. If any return true, return true.
5. Restore board[r][c] = temp.

Grid: [['A','B'],['C','D']], Word="AB" r=0, c=0: 'A' matches word[0]. Start DFS.
dfs(0,0, idx=0): Set [0][0] = '*'. Recurse 4 ways.
→ Down (1,0): 'C' != 'B'. Fail.
→ Right (0,1): 'B' == 'B'. Match! dfs(0,1, idx=1).
dfs(0,1, idx=1): Set [0][1] = '*'. Recurse.
→ idx=2 == length. Return TRUE!
Unwinds stack, restores board, returns true.
C++ Solution class Solution { bool dfs(vector<vector<char>>& board, string& word, int r, int c, int index) { if(index == word.length()) return true; // Found the word if(r < 0 || r >= board.size() || c < 0 || c >= board[0].size() || board[r][c] != word[index]) { return false; // Out of bounds or character mismatch } char temp = board[r][c]; board[r][c] = '*'; // Mark as visited // Explore 4 directions bool found = dfs(board, word, r + 1, c, index + 1) || dfs(board, word, r - 1, c, index + 1) || dfs(board, word, r, c + 1, index + 1) || dfs(board, word, r, c - 1, index + 1); board[r][c] = temp; // Backtrack: unmark return found; } public: bool exist(vector<vector<char>>& board, string word) { for(int r = 0; r < board.size(); r++) { for(int c = 0; c < board[0].size(); c++) { if(board[r][c] == word[0] && dfs(board, word, r, c, 0)) { return true; } } } return false; } };
TimeO(M * N * 4^L)
SpaceO(L) stack (L=word len)
Q195 · HARD Sudoku Solver
BacktrackingMatrix
LeetCode #37
Must KnowTry 1-9, Validate, Recurse

Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by the character '.'.

Decode — The ultimate backtracking test

Iterate through the board. Find an empty cell. Try placing digits '1' through '9'. For each digit, check if it's valid in the row, column, and 3x3 subgrid. If valid, place it, and recursively try to solve the rest of the board. If the recursive call fails, undo the placement (backtrack to '.') and try the next digit.

Full Board Check

Slow Validation

Re-scanning the entire 9x9 board every time to validate a move. Works, but inefficient.

O(1) Math Check ✓

Smart Math Lookups

Use `3 * (r/3) + i/3` logic to check row, col, and 3x3 box in a single loop of 9.

Time: O(9^Empty) · Space: O(81)
Approach — Recursive Search & Check

Our `solve` function iterates the grid. When it hits `'.'`, it loops 1-9. It calls `isValid(r, c, char)`. `isValid` loops 0-8 checking `board[r][i]`, `board[i][c]`, and the 3x3 grid using the math formula `board[3*(row/3) + i/3][3*(col/3) + i%3]`. If valid, place char, if(solve(board)) return true. If false, backtrack. If all 1-9 fail, return false. If loop finishes, return true (solved).

Abstract Trace Find empty cell (0,2).
Try '1': Valid! board[0][2]='1'. recurse().
→ Find next empty (0,3).
→ Try '1'-'9'. All Invalid! Return false.
Backtrack to (0,2). Un-choose '1' (set to '.').
Try '2': Valid! board[0][2]='2'. recurse()...
C++ Solution class Solution { bool isValid(vector<vector<char>>& board, int row, int col, char c) { for(int i = 0; i < 9; i++) { if(board[i][col] == c) return false; // Check column if(board[row][i] == c) return false; // Check row // Check 3x3 block if(board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c) return false; } return true; } bool solve(vector<vector<char>>& board) { for(int r = 0; r < board.size(); r++) { for(int c = 0; c < board[0].size(); c++) { if(board[r][c] == '.') { for(char num = '1'; num <= '9'; num++) { if(isValid(board, r, c, num)) { board[r][c] = num; // Choose if(solve(board)) return true; // Explore board[r][c] = '.'; // Un-choose (Backtrack) } } return false; // No valid number found, triggers backtracking above } } } return true; // Board is full } public: void solveSudoku(vector<vector<char>>& board) { solve(board); } };
TimeO(9^(empty cells))
SpaceO(81)
↑ back to top
Chapter 20 · Q196–Q205 · Bit Manipulation
Bit Manipulation — Thinking in 0s and 1s
Bitwise Operator Cheatsheet

AND (&): 1 if both are 1. Used for masking.
OR (|): 1 if either is 1. Used to set bits.
XOR (^): 1 if different, 0 if same. Perfect for toggling and finding odd-occurrences.
Shifts (<<, >>): Multiply or divide by 2.

Q196 · EASY Single Number
BitsArray
LeetCode #136
Must KnowXOR cancels duplicates

Given a non-empty array of integers, every element appears twice except for one. Find that single one. You must implement a solution with a linear runtime complexity and use only constant extra space.

Input[4,1,2,1,2]
Output4
Decode — XOR identical numbers to erase them

The XOR operator (^) has two magical properties:
1. a ^ a = 0 (Any number XORed with itself is 0)
2. a ^ 0 = a (Any number XORed with 0 remains unchanged).
If we XOR every number in the array together, all pairs will cancel out into 0s, leaving only the unique number.

Hash Map

Count Frequencies

Store counts in a map. Loop map to find the element with count == 1. Violates the O(1) space constraint.

Space: O(N)
Bitwise XOR ✓

In-Place Cancellation

Keep a running XOR total. Fast, elegant, and uses zero extra memory.

Time: O(N) · Space: O(1)
Approach — Running XOR

Initialize a variable result = 0. Loop through every number in the array, applying result ^= num. The order doesn't matter because XOR is commutative and associative. Return the result.

nums = [4, 1, 2, 1, 2] Init: res = 0
i=0: res = 0 ^ 4 = 4
i=1: res = 4 ^ 1 = 5 (Binary: 100 ^ 001 = 101)
i=2: res = 5 ^ 2 = 7 (Binary: 101 ^ 010 = 111)
i=3: res = 7 ^ 1 = 6 (Binary: 111 ^ 001 = 110) → Notice the '1' was canceled!
i=4: res = 6 ^ 2 = 4 (Binary: 110 ^ 010 = 100) → The '2' was canceled!
Final Answer = 4
C++ Solution — O(n) time · O(1) space int singleNumber(vector<int>& nums) { int result = 0; for (int num : nums) { result ^= num; } return result; }
TimeO(N)
SpaceO(1)
Q197 · EASY Number of 1 Bits
BitsMath
LeetCode #191
Must KnowBrian Kernighan's Algorithm

Write a function that takes the binary representation of an unsigned integer and returns the number of '1' bits it has (also known as the Hamming weight).

Inputn = 11 (1011 in binary)
Output3
Decode — Drop the lowest set bit

If you subtract 1 from a number (n - 1), all the trailing 0s become 1s, and the lowest 1 becomes a 0. If you then bitwise AND it with the original number (n & (n - 1)), it perfectly erases that lowest 1 bit. We can just count how many times we can do this until n is 0!

Loop 32 Times

Bit Masking

Loop exactly 32 times, shifting a '1' mask left, and checking `(n & mask) != 0`. Works, but always takes 32 operations.

n & (n - 1) ✓

Smart Bit Drop

Only loop exactly as many times as there are '1's in the number.

Time: O(1s) · Space: O(1)
Approach — Brian Kernighan's Algorithm

Initialize a count = 0. Create a while(n > 0) loop. Inside the loop, perform n = n & (n - 1). This drops the lowest 1 bit. Increment count++. Return the count.

n = 11 (Binary: 1011) Init: count = 0
Iter 1: n = 1011 & 1010 = 1010. count = 1. (Dropped the 1 at the end)
Iter 2: n = 1010 & 1001 = 1000. count = 2. (Dropped the 1 in the middle)
Iter 3: n = 1000 & 0111 = 0000. count = 3. (Dropped the final 1)
n is now 0. Loop ends.
Answer = 3
C++ Solution — O(k) time where k = number of 1s int hammingWeight(uint32_t n) { int count = 0; while (n > 0) { // Drops the lowest set bit n = n & (n - 1); count++; } return count; }
TimeO(1) (Max 32 ops)
SpaceO(1)
Q198 · EASY Counting Bits
BitsDP
LeetCode #338
Must KnowRelate 'i' to 'i/2'

Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1's in the binary representation of i.

Inputn = 5
Output[0,1,1,2,1,2]
Decode — Shifting right by 1 divides by 2

If you bit-shift a number right (i >> 1), you are essentially dividing it by 2. The number of 1s in i is exactly equal to the number of 1s in i / 2, plus 1 if i itself was odd (because it had a 1 in the least significant bit position). This is perfect for Dynamic Programming.

Popcount Loop

Recompute Everything

Run `hammingWeight()` on every number from 0 to n. Works, but duplicates effort.

Time: O(N log N)
Bitwise DP ✓

Reuse Previous Answers

ans[i] = ans[i >> 1] + (i & 1). Calculates in pure O(N) time.

Time: O(N) · Space: O(N)
Approach — Dynamic Programming via Shifts

Create an array ans of size n + 1, initialized with 0s. Loop i from 1 to n. Calculate ans[i] by taking the pre-computed answer for i >> 1 (which is ans[i/2]), and add i & 1 (which evaluates to 1 if odd, 0 if even).

n = 5 ans = [0, 0, 0, 0, 0, 0]
i=1: ans[1>>1] + (1&1) = ans[0] + 1 = 0 + 1 = 1.
i=2: ans[2>>1] + (2&1) = ans[1] + 0 = 1 + 0 = 1.
i=3: ans[3>>1] + (3&1) = ans[1] + 1 = 1 + 1 = 2.
i=4: ans[4>>1] + (4&1) = ans[2] + 0 = 1 + 0 = 1.
i=5: ans[5>>1] + (5&1) = ans[2] + 1 = 1 + 1 = 2.
Result = [0, 1, 1, 2, 1, 2]
C++ Solution — O(N) time vector<int> countBits(int n) { vector<int> ans(n + 1, 0); for(int i = 1; i <= n; i++) { // i >> 1 is i/2. i & 1 is i%2 (checks if odd) ans[i] = ans[i >> 1] + (i & 1); } return ans; }
TimeO(N)
SpaceO(N) for output
Q199 · EASY Reverse Bits
BitsMath
LeetCode #190
Must KnowExtract Right, Push Left

Reverse bits of a given 32 bits unsigned integer.

Input (Binary)00000010100101000001111010011100
Output (Binary)00111001011110000010100101000000
Decode — Build a new number bit by bit

Instead of swapping bits in place, it is much easier to extract the bits from n one by one starting from the right (Least Significant Bit), and append them to a new result variable, shifting result to the left each time to make room.

String Conversion

To String and Back

Convert number to a 32-char binary string, reverse the string, convert back to int. Terribly slow.

Bit Shift ✓

Extract and Accumulate

Iterate 32 times. Extract rightmost bit with `n & 1`. Shift `result` left, add bit. Shift `n` right.

Time: O(1) · Space: O(1)
Approach — The Shift-and-OR Pattern

Initialize res = 0. Loop exactly 32 times. Shift res left by 1 to make room (res << 1). Extract the rightmost bit of n using n & 1. Add it to res using the OR operator (res | bit). Finally, shift n right by 1 (n >> 1) to process the next bit.

Example: n = 1101 (Only showing 4 bits for clarity) Init: res = 0000
Iter 1: bit = 1101 & 1 = 1. res = (0000 << 1) | 1 = 0001. n = 110.
Iter 2: bit = 110 & 1 = 0. res = (0001 << 1) | 0 = 0010. n = 11.
Iter 3: bit = 11 & 1 = 1. res = (0010 << 1) | 1 = 0101. n = 1.
Iter 4: bit = 1 & 1 = 1. res = (0101 << 1) | 1 = 1011. n = 0.
Result = 1011 (Reversed!)
C++ Solution — O(1) time · O(1) space uint32_t reverseBits(uint32_t n) { uint32_t res = 0; for(int i = 0; i < 32; i++) { // Make room in res for the new bit res <<= 1; // Extract rightmost bit and add it to res res |= (n & 1); // Shift n to process the next bit n >>= 1; } return res; }
TimeO(1) (Exact 32 ops)
SpaceO(1)
Q200 · EASY Missing Number
BitsMath
LeetCode #268
Must KnowIndex XOR Value

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.

Input[3,0,1]
Output2
Whyn=3. Range is 0,1,2,3.
Decode — Treat array indices and values as a Single Number problem

If the array wasn't missing a number, the elements would perfectly match the indices 0 through n. If we XOR every index together, and XOR every array value together, everything will come in pairs and cancel out to 0... except the missing number, which has an index but no matching value!

Gauss Formula

Sum(0...n) - Sum(array)

Calculate `n * (n + 1) / 2`. Subtract sum of elements. Great, but strictly speaking, risks integer overflow on massive arrays.

XOR Indexes ✓

No Overflow Danger

XOR avoids large sums entirely. Initialize `res = n`, then XOR `i` and `nums[i]`.

Time: O(N) · Space: O(1)
Approach — The Double XOR

Initialize res = nums.size() (because the array indices only go up to n-1, but the numbers go up to n). Loop i from 0 to n-1. Update res ^= i ^ nums[i]. The result will hold the missing number.

nums = [3, 0, 1] (n = 3) Init: res = 3
i=0: res = 3 ^ 0(idx) ^ 3(val) = 0
i=1: res = 0 ^ 1(idx) ^ 0(val) = 1
i=2: res = 1 ^ 2(idx) ^ 1(val) = 2
Loop ends. Answer = 2 (All numbers paired except 2!)
C++ Solution — O(n) time · O(1) space int missingNumber(vector<int>& nums) { int result = nums.size(); for(int i = 0; i < nums.size(); i++) { // XOR the index, and XOR the value result ^= i ^ nums[i]; } return result; }
TimeO(N)
SpaceO(1)
↑ back to top
Chapter 21 · Q206–Q210 · Mixed Bag
Mixed Bag & Advanced — The Grand Finale
The Ultimate Test

These final problems represent the culmination of your prep. They rarely fit into a single easy category. They require you to combine multiple data structures (like a Map and a Linked List) or use highly specialized structures (like a Trie) to achieve optimal performance.

Q206 · MEDIUM Implement Trie (Prefix Tree)
DesignStringTree
LeetCode #208
Must KnowArray of 26 Pointers

A trie (pronounced as "try") or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. Implement the Trie class with insert, search, and startsWith methods.

Commands["Trie","insert","search","startsWith"]
Inputs[[],["apple"],["apple"],["app"]]
Output[null,null,true,true]
Decode — Build a character-by-character tree path

Instead of storing whole words in a node, each node represents a single character. A node contains an array of 26 pointers (one for each letter of the alphabet) to its children, and a boolean flag isEndOfWord. Words sharing a prefix share the same path down the tree.

Hash Set

Fast Search, Slow Prefix

O(1) exact word search, but `startsWith` requires checking every word in the set. O(N*M) prefix search.

Trie Nodes ✓

O(L) Operations

Every operation takes time proportional to the length of the word (L). Extremely fast for autocomplete.

Time: O(L) · Space: O(L) per word
Approach — The 26-ary Tree

Create a TrieNode struct with TrieNode* children[26] and bool isEnd.
Insert: Iterate letters. If children[char - 'a'] is null, create a new node. Move pointer down. Set isEnd = true at the final node.
Search/Prefix: Iterate letters. If the child is null, return false. If loop finishes, return isEnd (for search) or true (for startsWith).

Insert "app", Search "app", Prefix "ap" insert("app"): Root → 'a' (new) → 'p' (new) → 'p' (new, isEnd=true).
search("app"): Root → 'a' (exists) → 'p' (exists) → 'p' (exists). isEnd is true! → true
startsWith("ap"): Root → 'a' (exists) → 'p' (exists). Path exists! → true
C++ Solution — Object Oriented Design class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { isEnd = false; for(int i = 0; i < 26; i++) { children[i] = nullptr; } } }; class Trie { TrieNode* root; public: Trie() { root = new TrieNode(); } void insert(string word) { TrieNode* node = root; for(char c : word) { int idx = c - 'a'; if(node->children[idx] == nullptr) { node->children[idx] = new TrieNode(); } node = node->children[idx]; } node->isEnd = true; } bool search(string word) { TrieNode* node = root; for(char c : word) { int idx = c - 'a'; if(node->children[idx] == nullptr) return false; node = node->children[idx]; } return node->isEnd; } bool startsWith(string prefix) { TrieNode* node = root; for(char c : prefix) { int idx = c - 'a'; if(node->children[idx] == nullptr) return false; node = node->children[idx]; } return true; } };
TimeO(L) per operation
SpaceO(N * L) total
Q207 · HARD Word Ladder
GraphBFSString
LeetCode #127
Must KnowShortest Path = BFS

A transformation sequence from beginWord to endWord using a wordList requires changing exactly one letter at a time. Every intermediate word must exist in the wordList. Return the length of the shortest transformation sequence, or 0 if none exists.

Words"hit" -> "cog"
List["hot","dot","dog","lot","log","cog"]
Output5
Decode — Find the shortest path in an unweighted string graph

Anytime a problem asks for the "shortest path" or "minimum steps" and the costs are uniform (1 change = 1 step), it is a screaming siren for Breadth-First Search (BFS). The "nodes" are words, and "edges" connect words that differ by exactly one letter.

DFS Backtracking

Too Slow

Exploring every possible valid path recursively to find the minimum length will result in Time Limit Exceeded.

Queue BFS ✓

Level-by-Level

Use a Queue to explore radially. Use a Hash Set for O(1) dictionary lookups and to mark words as visited.

Time: O(M² * N) · Space: O(M * N)
Approach — Character Replacement BFS

1. Put wordList into an unordered_set. If endWord isn't in it, return 0.
2. Push beginWord into a queue. Initialize steps = 1.
3. While queue is not empty, process level by level (using size = q.size()).
4. For each word, try changing each character from 'a' to 'z'. If the mutated word equals endWord, return steps + 1. If it exists in the set, erase it from the set (to mark as visited) and push to queue.
5. Increment steps after each level.

begin="hit", end="cog" Level 1: Q=["hit"]. steps=1.
Pop "hit". Try "ait", "bit"... "hot" is in set! Push "hot". Remove "hot" from set.
Level 2: Q=["hot"]. steps=2.
Pop "hot". Try changing 'h'. "dot", "lot" in set! Push both. Remove from set.
Level 3: Q=["dot", "lot"]. steps=3.
... continues radially until "cog" is generated. Return 5. (hit->hot->dot->dog->cog)
C++ Solution int ladderLength(string beginWord, string endWord, vector<string>& wordList) { unordered_set<string> dict(wordList.begin(), wordList.end()); if(dict.find(endWord) == dict.end()) return 0; queue<string> q; q.push(beginWord); int steps = 1; while(!q.empty()) { int size = q.size(); // Process current level fully for(int i = 0; i < size; i++) { string word = q.front(); q.pop(); // Try replacing every character for(int j = 0; j < word.size(); j++) { char original = word[j]; for(char c = 'a'; c <= 'z'; c++) { word[j] = c; if(word == endWord) return steps + 1; if(dict.find(word) != dict.end()) { q.push(word); dict.erase(word); // Mark visited by removing } } word[j] = original; // Restore for next position } } steps++; } return 0; }
TimeO(M² * N) (M=word len, N=list size)
SpaceO(M * N)
Q208 · MEDIUM LRU Cache
DesignLinked ListHash Map
LeetCode #146
Must KnowAsked EverywhereMap + Doubly Linked List

Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. Implement the LRUCache class with get(key) and put(key, value). Both operations must run in O(1) average time complexity.

Decode — We need fast lookups AND fast reordering

To get O(1) lookup, we MUST use a Hash Map. To get O(1) deletion and insertion (for reordering usage), we MUST use a Doubly Linked List. The trick is to store the Linked List Node pointer inside the Hash Map as the value!

Array / Vector

Slow Reorder

O(1) access with an array, but moving an accessed item to the "front/back" of the array takes O(N) shift time.

DLL + Map ✓

Perfect O(1) Synergy

Map gives O(1) node lookup. DLL allows O(1) severing and reconnecting of nodes.

Time: O(1) · Space: O(Capacity)
Approach — Map + DLL with Dummy Nodes

1. Create a Node struct with key, val, prev, next.
2. Use two dummy nodes: head and tail. head->next is the Most Recently Used. tail->prev is the Least Recently Used.
3. Get: If key exists in map, extract the node, delete it from its current DLL position, insert it right after head (marking it recently used), return val.
4. Put: If key exists, update value and move to front. If new, create node, insert after head, add to map. If map size > capacity, remove node before tail, delete from map, delete node from memory.

C++ Solution — The Standard Interview Implementation class Node { public: int key, val; Node* prev; Node* next; Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {} }; class LRUCache { int capacity; unordered_map<int, Node*> cache; Node* head; Node* tail; void removeNode(Node* node) { node->prev->next = node->next; node->next->prev = node->prev; } void insertAfterHead(Node* node) { node->next = head->next; node->prev = head; head->next->prev = node; head->next = node; } public: LRUCache(int capacity) { this->capacity = capacity; head = new Node(-1, -1); tail = new Node(-1, -1); head->next = tail; tail->prev = head; } int get(int key) { if(cache.find(key) == cache.end()) return -1; Node* node = cache[key]; removeNode(node); insertAfterHead(node); // Mark as Most Recently Used return node->val; } void put(int key, int value) { if(cache.find(key) != cache.end()) { Node* node = cache[key]; node->val = value; removeNode(node); insertAfterHead(node); } else { if(cache.size() == capacity) { Node* lru = tail->prev; cache.erase(lru->key); removeNode(lru); delete lru; // Prevent memory leak } Node* newNode = new Node(key, value); cache[key] = newNode; insertAfterHead(newNode); } } };
TimeO(1)
SpaceO(Capacity)
Q209 · MEDIUM Number of Islands
GraphDFS
LeetCode #200
Must KnowSink the Island

Given an m x n 2D binary grid which represents a map of '1's (land) and '0's (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.

Decode — Find a '1', count it, then erase the entire connected body

This is the classic connected-components problem. Every time we encounter an unvisited '1' in our loop, we have discovered a brand new island. To ensure we don't count its connected pieces as separate islands, we launch a DFS that visits all connected '1's and changes them to '0's ("sinking" the island).

Visited Matrix

Extra Array

Use a boolean matrix to track visited land. Works well but uses O(M*N) extra memory.

In-Place Sinking ✓

DFS Mutate

Mutate the input grid by changing '1's to '0's. Saves space if modification is allowed.

Time: O(M*N) · Space: O(M*N) recursive
Approach — Grid DFS

Iterate through every cell [r][c]. If the cell is '1', increment the islandCount and call dfs(grid, r, c). The DFS function checks if bounds are valid and if the cell is '1'. If so, it sets the cell to '0' and makes four recursive calls for Up, Down, Left, and Right.

C++ Solution class Solution { void dfs(vector<vector<char>>& grid, int r, int c) { int m = grid.size(); int n = grid[0].size(); // Bounds check and water check if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == '0') return; // Sink the land to mark it as visited grid[r][c] = '0'; // Explore radially dfs(grid, r + 1, c); dfs(grid, r - 1, c); dfs(grid, r, c + 1); dfs(grid, r, c - 1); } public: int numIslands(vector<vector<char>>& grid) { int count = 0; for(int r = 0; r < grid.size(); r++) { for(int c = 0; c < grid[0].size(); c++) { if(grid[r][c] == '1') { count++; dfs(grid, r, c); } } } return count; } };
TimeO(M * N)
SpaceO(M * N) worst-case call stack
Q210 · HARD Trapping Rain Water
ArrayTwo PointersDynamic Prog.
LeetCode #42
Must KnowWater depends on Min(MaxLeft, MaxRight)

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

Input[0,1,0,2,1,0,1,3,2,1,2,1]
Output6
Decode — A column traps water if it sits in a "bowl"

For any given column i, the amount of water resting exactly on top of it is min(highest_wall_to_the_left, highest_wall_to_the_right) - height[i]. If this math yields a negative number, it holds 0 water.

Precompute Arrays

Prefix Max Arrays

Make a leftMax array and a rightMax array. Then loop and apply the formula. Easy to write, takes O(N) space.

Two Pointers ✓

O(1) Space Magic

Keep pointers at ends. Move the smaller pointer inward, updating leftMax or rightMax dynamically.

Time: O(N) · Space: O(1)
Approach — The Pincer Method

Initialize left = 0, right = n - 1. Keep track of leftMax = 0 and rightMax = 0. While left < right, compare the heights at the two pointers. The smaller boundary is the bottleneck! So, if height[left] < height[right], we process the left side: update leftMax, add leftMax - height[left] to total water, and move left++. Else, process the right side.

C++ Solution — Optimal Two Pointer int trap(vector<int>& height) { if(height.empty()) return 0; int left = 0, right = height.size() - 1; int leftMax = 0, rightMax = 0; int water = 0; while(left < right) { // The smaller boundary restricts the water level if(height[left] < height[right]) { if(height[left] >= leftMax) { leftMax = height[left]; // New wall, no water traps here } else { water += leftMax - height[left]; // Inside a bowl } left++; } else { if(height[right] >= rightMax) { rightMax = height[right]; } else { water += rightMax - height[right]; } right--; } } return water; }
TimeO(N)
SpaceO(1)
↑ back to top