LeetCode has become the gold standard for technical interview preparation. This guide will teach you systematic approaches to tackle any problem, recognize common patterns, and develop effective practice habits that actually work.
The Problem-Solving Framework
Step 1: Understand the Problem (5-10 minutes)
Before writing any code, ensure you fully understand what's being asked.
// Problem: Two Sum
// Given an array of integers nums and an integer target,
// return indices of the two numbers such that they add up to target.
// Questions to ask yourself:
// 1. What is the input size?
// 2. Are there duplicates?
// 3. What if no solution exists?
// 4. Can we use the same element twice?
// 5. What about negative numbers?Clarification Checklist:
- Input constraints and edge cases
- Output format (return value vs modify in-place)
- Time and space complexity requirements
- Can we assume valid input?
Step 2: Think of Examples (2-3 minutes)
Create test cases that cover different scenarios.
// Example cases for Two Sum:
// Case 1: Normal case
Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1]
Explanation: nums[0] + nums[1] = 2 + 7 = 9
// Case 2: Negative numbers
Input: nums = [-1, -2, -3, -4, -5], target = -8
Output: [2, 4]
Explanation: nums[2] + nums[4] = -3 + -5 = -8
// Case 3: Same element twice
Input: nums = [3, 3], target = 6
Output: [0, 1]
// Case 4: No solution
Input: nums = [1, 2, 3], target = 10
Output: [] or some indicatorStep 3: Brute Force Solution (5-10 minutes)
Start with the simplest approach, even if inefficient.
// Brute Force Two Sum - O(n²) time, O(1) space
function twoSumBrute(nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return [];
}Step 4: Optimize (10-15 minutes)
Identify inefficiencies and improve.
// Optimized Two Sum - O(n) time, O(n) space
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}Step 5: Implement Clean Code (5-10 minutes)
Write production-quality code with proper variable names and comments.
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
function twoSum(nums, target) {
const numToIndex = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (numToIndex.has(complement)) {
return [numToIndex.get(complement), i];
}
numToIndex.set(nums[i], i);
}
return []; // No solution found
}Common Problem Patterns
Pattern 1: Sliding Window
Used for problems involving subarrays or substrings.
Fixed Size Window
// Maximum average subarray I
function findMaxAverage(nums, k) {
let sum = 0;
// Calculate first window
for (let i = 0; i < k; i++) {
sum += nums[i];
}
let maxSum = sum;
// Slide window
for (let i = k; i < nums.length; i++) {
sum += nums[i] - nums[i - k];
maxSum = Math.max(maxSum, sum);
}
return maxSum / k;
}Variable Size Window
// Longest substring without repeating characters
function lengthOfLongestSubstring(s) {
const charSet = new Set();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.length; right++) {
while (charSet.has(s[right])) {
charSet.delete(s[left]);
left++;
}
charSet.add(s[right]);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
// Minimum size subarray sum
function minSubArrayLen(target, nums) {
let left = 0;
let sum = 0;
let minLength = Infinity;
for (let right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLength = Math.min(minLength, right - left + 1);
sum -= nums[left];
left++;
}
}
return minLength === Infinity ? 0 : minLength;
}Pattern 2: Two Pointers
Used for sorted arrays or linked lists.
// Container with most water
function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxArea = 0;
while (left < right) {
const width = right - left;
const h = Math.min(height[left], height[right]);
maxArea = Math.max(maxArea, width * h);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
// Three sum
function threeSum(nums) {
nums.sort((a, b) => a - b);
const result = [];
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}Pattern 3: Fast & Slow Pointers
Used for cycle detection in linked lists.
// Linked list cycle detection
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
}
// Find cycle start node
function detectCycle(head) {
let slow = head;
let fast = head;
// Detect cycle
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
// Find start of cycle
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}Pattern 4: Merge Intervals
// Merge intervals
function merge(intervals) {
if (intervals.length <= 1) return intervals;
// Sort by start time
intervals.sort((a, b) => a[0] - b[0]);
const result = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const last = result[result.length - 1];
const current = intervals[i];
if (current[0] <= last[1]) {
// Overlapping intervals, merge them
last[1] = Math.max(last[1], current[1]);
} else {
result.push(current);
}
}
return result;
}
// Insert interval
function insert(intervals, newInterval) {
const result = [];
let i = 0;
const n = intervals.length;
// Add all intervals before newInterval
while (i < n && intervals[i][1] < newInterval[0]) {
result.push(intervals[i]);
i++;
}
// Merge overlapping intervals
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.push(newInterval);
// Add remaining intervals
while (i < n) {
result.push(intervals[i]);
i++;
}
return result;
}Pattern 5: Cyclic Sort
// Find missing number
function missingNumber(nums) {
let i = 0;
while (i < nums.length) {
const correct = nums[i];
if (correct < nums.length && nums[i] !== nums[correct]) {
[nums[i], nums[correct]] = [nums[correct], nums[i]];
} else {
i++;
}
}
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== i) {
return i;
}
}
return nums.length;
}
// Find all missing numbers
function findDisappearedNumbers(nums) {
const result = [];
for (let i = 0; i < nums.length; i++) {
const index = Math.abs(nums[i]) - 1;
if (nums[index] > 0) {
nums[index] = -nums[index];
}
}
for (let i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
result.push(i + 1);
}
}
return result;
}Pattern 6: Modified Binary Search
// Search in rotated sorted array
function search(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
return mid;
}
// Left half is sorted
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// Right half is sorted
else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
// Find minimum in rotated sorted array
function findMin(nums) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}Pattern 7: Top K Elements
// Kth largest element in an array
function findKthLargest(nums, k) {
// Using min-heap approach
const minHeap = [];
for (const num of nums) {
minHeap.push(num);
minHeap.sort((a, b) => a - b);
if (minHeap.length > k) {
minHeap.shift();
}
}
return minHeap[0];
}
// Top K frequent elements
function topKFrequent(nums, k) {
const frequency = {};
for (const num of nums) {
frequency[num] = (frequency[num] || 0) + 1;
}
const buckets = Array(nums.length + 1).fill(null).map(() => []);
for (const [num, freq] of Object.entries(frequency)) {
buckets[freq].push(parseInt(num));
}
const result = [];
for (let i = buckets.length - 1; i >= 0 && result.length < k; i--) {
result.push(...buckets[i]);
}
return result.slice(0, k);
}Pattern 8: K-way Merge
// Merge k sorted lists
function mergeKLists(lists) {
const minHeap = [];
// Add first node of each list to heap
for (const list of lists) {
if (list) {
minHeap.push(list);
}
}
// Create min-heap based on node values
minHeap.sort((a, b) => a.val - b.val);
const dummy = new ListNode(0);
let current = dummy;
while (minHeap.length > 0) {
// Extract minimum
const node = minHeap.shift();
current.next = node;
current = current.next;
// Add next node from same list
if (node.next) {
minHeap.push(node.next);
minHeap.sort((a, b) => a.val - b.val);
}
}
return dummy.next;
}Problem Categories and Strategies
Array Problems
Key Techniques:
- Hash maps for frequency counting
- Two pointers for sorted arrays
- Sliding window for subarray problems
- Prefix sums for range queries
// Product of array except self
function productExceptSelf(nums) {
const n = nums.length;
const result = new Array(n).fill(1);
// Calculate left products
let leftProduct = 1;
for (let i = 0; i < n; i++) {
result[i] = leftProduct;
leftProduct *= nums[i];
}
// Calculate right products and final result
let rightProduct = 1;
for (let i = n - 1; i >= 0; i--) {
result[i] *= rightProduct;
rightProduct *= nums[i];
}
return result;
}String Problems
Key Techniques:
- Two pointers for palindrome/checking
- Sliding window for substring problems
- Hash maps for character counting
- Regular expressions for pattern matching
// Longest palindromic substring
function longestPalindrome(s) {
let start = 0;
let maxLength = 1;
function expandAroundCenter(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
left--;
right++;
}
return right - left - 1;
}
for (let i = 0; i < s.length; i++) {
const len1 = expandAroundCenter(i, i); // Odd length
const len2 = expandAroundCenter(i, i + 1); // Even length
const len = Math.max(len1, len2);
if (len > maxLength) {
maxLength = len;
start = i - Math.floor((len - 1) / 2);
}
}
return s.substring(start, start + maxLength);
}Tree Problems
Key Techniques:
- Recursive DFS for most problems
- BFS for level-order traversal
- Divide and conquer for BST problems
- Morris traversal for O(1) space
// Binary tree level order traversal
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const level = [];
const size = queue.length;
for (let i = 0; i < size; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}
// Lowest common ancestor in BST
function lowestCommonAncestor(root, p, q) {
if (!root) return null;
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q);
}
if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
}Graph Problems
Key Techniques:
- BFS for shortest path
- DFS for connectivity and cycles
- Topological sort for dependencies
- Union-Find for connected components
// Course schedule II (topological sort)
function findOrder(numCourses, prerequisites) {
const graph = new Map();
const inDegree = new Array(numCourses).fill(0);
// Build graph
for (let i = 0; i < numCourses; i++) {
graph.set(i, []);
}
for (const [course, prereq] of prerequisites) {
graph.get(course).push(prereq);
inDegree[prereq]++;
}
// Start with courses having no prerequisites
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) {
queue.push(i);
}
}
const result = [];
while (queue.length > 0) {
const course = queue.shift();
result.push(course);
for (const prereq of graph.get(course)) {
inDegree[prereq]--;
if (inDegree[prereq] === 0) {
queue.push(prereq);
}
}
}
return result.length === numCourses ? result : [];
}Practice Strategy
Phase 1: Fundamentals (Weeks 1-4)
Focus: Basic patterns and data structures
Easy Problems:
- Array: Two Sum, Contains Duplicate, Valid Anagram
- String: Valid Palindrome, Reverse String, First Unique Character
- Tree: Maximum Depth, Invert Binary Tree, Same Tree
- Graph: Number of Islands, Clone Graph
Goal: Solve 50-75 easy problems
Phase 2: Intermediate Patterns (Weeks 5-8)
Focus: Recognizing and applying patterns
Medium Problems:
- Array: 3Sum, Container With Most Water, Product of Array Except Self
- String: Longest Substring Without Repeating Characters, Valid Parentheses
- Tree: Binary Tree Level Order Traversal, Validate BST, LCA
- Graph: Course Schedule, Clone Graph, Reconstruct Itinerary
Goal: Solve 75-100 medium problems
Phase 3: Advanced Topics (Weeks 9-12)
Focus: Complex patterns and optimization
Hard Problems:
- DP: Median of Two Sorted Arrays, Trapping Rain Water
- Tree: Binary Tree Maximum Path Sum, Serialize and Deserialize BST
- Graph: Word Ladder, Alien Dictionary
Goal: Solve 25-50 hard problems
Daily Practice Schedule
Weekdays (1-2 hours):
- 3-5 new problems (mix of easy/medium)
- Review previous day's problems
- Document patterns learned
Weekends (2-3 hours):
- Mock interviews (time yourself)
- Revise weak areas
- Participate in weekly contests
Time Management During Practice
First Attempt
- Easy: 10-15 minutes
- Medium: 20-30 minutes
- Hard: 30-45 minutes
If Stuck
- 15 minutes: Re-read problem, check edge cases
- 210 minutes: Try different approach
- 315 minutes: Look at hint or discussion
- 420 minutes: Check solution approach (not code)
- 530+ minutes: Study solution thoroughly
After Solving
- 1Analyze: Time and space complexity
- 2Optimize: Can you improve it?
- 3Document: Note the pattern
- 4Revisit: Solve again in 1 week
Common Mistakes to Avoid
1. Jumping to Coding Too Fast
Wrong: Start coding immediately after reading problem Right: Understand, plan, then code
2. Ignoring Edge Cases
Wrong: Only testing with given examples Right: Test with empty arrays, single elements, duplicates
3. Not Optimizing After Brute Force
Wrong: Stopping at first solution Right: Always think about optimization
4. Memorizing Solutions
Wrong: Memorizing code without understanding Right: Understanding patterns and approaches
5. Not Revisiting Problems
Wrong: Solving once and moving on Right: Revisit after 1 week, 1 month, 3 months
Interview Simulation
Mock Interview Format
// Problem: Implement LRU Cache
// Step 1: Clarify requirements
// - What's the capacity? Assume positive integer
// - What operations needed? get and put
// - Time complexity? O(1) for both operations
// Step 2: Design approach
// - Use hash map for O(1) access
// - Use doubly linked list for O(1) insertion/deletion
// - Maintain order of usage
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return -1;
}
// Move to end (most recently used)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
// Update if key exists
if (this.cache.has(key)) {
this.cache.delete(key);
}
// Remove least recently used if at capacity
if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}Tracking Progress
Problem Log Template
{
date: "2025-02-20",
problem: "Two Sum",
difficulty: "Easy",
category: "Array",
pattern: "Hash Map",
timeToSolve: "8 minutes",
attempts: 1,
notes: "Remember to check complement before adding current element",
revisitDate: "2025-02-27"
}Metrics to Track
- 1Problems solved per week
- 2Average time to solution
- 3Pattern recognition rate
- 4First-try success rate
- 5Topics mastered vs. learning
Final Tips
- 1Consistency over intensity: 30 minutes daily > 5 hours weekly
- 2Quality over quantity: Understand deeply, don't rush
- 3Pattern recognition: Focus on recognizing when to use which approach
- 4Teach others: Explaining reinforces understanding
- 5Stay positive: Struggling is part of learning
Remember: The goal isn't just to solve problems, but to develop problem-solving thinking that transfers to any technical challenge. Good luck with your preparation!