DSA Partner Challenge | Week 3
Backtracking — 3 classic FAANG problems. One pattern. Three applications.
| Video | Link |
|---|---|
| Backtracking Tutorial 1 | youtu.be/sTdiMLom00U |
| Backtracking Tutorial 2 | youtu.be/ymgnIIclCF0 |
void backtrack(state, choices):
if isComplete(state):
record(state)
return
for choice in choices:
if isValid(state, choice):
makeChoice(state, choice) ← CHOOSE
backtrack(updatedState) ← EXPLORE
undoChoice(state, choice) ← UNCHOOSE
Three key conditions:
- Goal: current state is a complete valid solution → record it
- Pruning: current state cannot lead to any solution → stop early
- Extension: try each valid next choice, recurse, then undo
Q1. Combination Sum — LC #39 Medium
Find all combinations of candidates that sum to target. Each candidate can be used unlimited times.
// Java
void backtrack(int[] cands, int remaining, int start, List<Integer> cur, List<List<Integer>> res) {
if (remaining == 0) { res.add(new ArrayList<>(cur)); return; }
for (int i=start; i<cands.length; i++) {
if (cands[i] > remaining) break; // PRUNE (sorted array)
cur.add(cands[i]); // CHOOSE
backtrack(cands, remaining-cands[i], i, cur, res); // i: reuse allowed
cur.remove(cur.size()-1); // UNCHOOSE
}
}
// Sort first, call backtrack(candidates, target, 0, [], result)def combinationSum(self, candidates, target):
candidates.sort()
result = []
def backtrack(start, current, remaining):
if remaining == 0: result.append(current[:]); return
for i in range(start, len(candidates)):
if candidates[i] > remaining: break # prune
current.append(candidates[i])
backtrack(i, current, remaining-candidates[i]) # i: reuse
current.pop()
backtrack(0, [], target)
return result💡 Pass
i(noti+1) to allow reuse of the same element. Passi+1for Combination Sum II where each element can only be used once.
Trace: candidates=[2,3,6,7], target=7
pick 2 → remaining=5
pick 2 → remaining=3
pick 3 → remaining=0 ✓ [2,2,3]
pick 3 → remaining=4
pick 3 → remaining=1
pick 3 → remaining=-2 PRUNE
pick 7 → remaining=0 ✓ [7]
Q2. Word Search — LC #79 Medium
Find if a word exists in a grid using horizontally/vertically connected letters.
// Java
boolean dfs(char[][] board, String word, int r, int c, int idx) {
if (idx == word.length()) return true; // all chars matched
if (r<0 || r>=board.length || c<0 || c>=board[0].length) return false;
if (board[r][c] != word.charAt(idx)) return false;
char tmp = board[r][c];
board[r][c] = '#'; // MARK visited (in-place, O(1) space)
boolean found = dfs(board,word,r+1,c,idx+1) || dfs(board,word,r-1,c,idx+1)
|| dfs(board,word,r,c+1,idx+1) || dfs(board,word,r,c-1,idx+1);
board[r][c] = tmp; // RESTORE (backtrack)
return found;
}
// Call dfs(board, word, i, j, 0) for each cell (i,j)def exist(self, board, word):
m, n = len(board), len(board[0])
def dfs(r, c, idx):
if idx==len(word): return True
if r<0 or r>=m or c<0 or c>=n or board[r][c]!=word[idx]: return False
tmp=board[r][c]; board[r][c]='#' # mark visited
found=dfs(r+1,c,idx+1) or dfs(r-1,c,idx+1) or dfs(r,c+1,idx+1) or dfs(r,c-1,idx+1)
board[r][c]=tmp # restore
return found
return any(dfs(i,j,0) for i in range(m) for j in range(n))4-directional movement template:
int[] dr = {0, 0, 1, -1};
int[] dc = {1, -1, 0, 0};
for (int d=0; d<4; d++) dfs(r+dr[d], c+dc[d], idx+1);In-place marking (
board[r][c] = '#') avoids a separatevisited[][]array — O(1) extra space.
Q3. Target Sum — LC #494 Medium
Assign + or - to each number. Count assignments that make the expression equal target.
Backtracking O(2^n):
int backtrack(int[] nums, int target, int idx, int currentSum) {
if (idx == nums.length) return currentSum == target ? 1 : 0;
return backtrack(nums, target, idx+1, currentSum+nums[idx])
+ backtrack(nums, target, idx+1, currentSum-nums[idx]);
}def findTargetSumWays(self, nums, target):
def bt(idx, s):
if idx==len(nums): return 1 if s==target else 0
return bt(idx+1, s+nums[idx]) + bt(idx+1, s-nums[idx])
return bt(0, 0)With Memoisation — O(n × sum):
from functools import lru_cache
def findTargetSumWays(self, nums, target):
@lru_cache(maxsize=None)
def dp(idx, remaining):
if idx==len(nums): return 1 if remaining==0 else 0
return dp(idx+1, remaining-nums[idx]) + dp(idx+1, remaining+nums[idx])
return dp(0, target)Memoisation caches
(idx, remaining)— each unique state computed once. Goes from exponential to polynomial.
| Problem | Pattern | Key State | Pruning |
|---|---|---|---|
| Combination Sum | Include/repeat | remaining sum | sum > target → break |
| Word Search | DFS on grid | visited cells | OOB or wrong char |
| Target Sum | Include/exclude (+/-) | current sum | memoize states |
| Subsets | Include/exclude | current subset | none |
| Permutations | Swap & restore | current position | duplicates |
- I know the 3-step backtracking pattern: choose → explore → unchoose
- Combination Sum: why sort? why
breaknotcontinue? why passinoti+1? - Word Search: in-place marking with restore — no extra visited array
- I know the 4-directional movement pattern for grid DFS
- Target Sum: backtracking gives all paths, memoisation caches overlapping subproblems
- I solved all 3 problems
These three problems are interview staples. Which one took you the longest to understand? Drop it in the community.
Solved all 3? Drop a 🔥 in the community.
Next up → Day 22: Hashing — HashMap, HashSet, frequency maps