Mastering Problem-Solving: Techniques That Helped Me Solve 200+ LeetCode Problems
Mastering Problem-Solving: Techniques That Helped Me Solve 200+ LeetCode Problems
As a software engineer who's solved 200+ algorithmic problems on LeetCode and participated in numerous competitive programming contests, I've learned that problem-solving is a skill that can be systematically developed. This post shares the techniques and mental frameworks that transformed me from someone who struggled with medium problems to someone who can tackle most hard problems with confidence.
Why Problem-Solving Skills Matter
Before diving into techniques, let's address the elephant in the room: Do you really need to solve hundreds of LeetCode problems to be a good developer?
The answer is nuanced. While daily work rarely requires implementing complex algorithms, the problem-solving skills you develop through practice translate directly to:
- System Design: Breaking down complex systems into manageable components
- Debugging: Systematically narrowing down root causes
- Performance Optimization: Recognizing bottlenecks and applying appropriate solutions
- Code Reviews: Spotting edge cases and potential bugs
- Technical Interviews: Getting past the hiring process to land your dream job
The Four-Step Problem-Solving Framework
Every problem I solve follows this framework:
1. Understand (5-10 minutes)
2. Plan (10-15 minutes)
3. Execute (20-30 minutes)
4. Reflect (5-10 minutes)
Let's explore each step in detail.
Step 1: Understand the Problem
This seems obvious, but it's where most people fail. I used to jump straight to coding, only to realize halfway through that I misunderstood the requirements.
My Process:
A. Read Twice, Code Once
- First read: Get the gist
- Second read: Note constraints, edge cases, and examples
B. Restate in Your Own Words Example problem: "Given an array, find two numbers that add up to a target."
My restatement: "I need to find indices i and j where arr[i] + arr[j] = target, and i ≠ j. Return these indices."
C. Identify Constraints
- Input size? (n ≤ 10^4 vs n ≤ 10^9 completely changes the approach)
- Memory limits?
- Negative numbers allowed?
- Duplicates allowed?
- Can I modify the input?
D. Work Through Examples ``` Input: nums = [2, 7, 11, 15], target = 9 Output: [0, 1] ← Why? Because nums[0] + nums[1] = 2 + 7 = 9
Edge cases to consider:
- What if no solution exists? Return [-1, -1]?
- What if multiple solutions exist? Return any one?
- What if array has duplicates? [3, 3], target = 6? ```
E. The Stupid Questions Technique Ask deliberately naive questions:
- "Can the array be empty?"
- "Can the target be negative?"
- "Do both numbers have to be different?"
In interviews, this demonstrates thoroughness. In competitions, it prevents wrong submissions.
Step 2: Plan Your Approach
This is where algorithmic patterns come in. Over 200 problems, I've identified these recurring patterns:
Pattern 1: Two Pointers
When to use: Sorted arrays, finding pairs, removing duplicates
Example: Two Sum II (sorted array) ```javascript function twoSum(nums, target) { let left = 0, right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return [left, right];
else if (sum < target) left++; // Need larger sum
else right--; // Need smaller sum
}
return [-1, -1];
} ```
Time: O(n), Space: O(1)
Pattern 2: Hash Map (Trading Space for Time)
When to use: Looking up values, counting frequencies, caching results
Example: Two Sum (unsorted array) ```javascript function twoSum(nums, target) { const map = new Map(); // value → index
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 [-1, -1];
} ```
Time: O(n), Space: O(n)
Pattern 3: Sliding Window
When to use: Contiguous subarrays, substrings, "maximum/minimum subarray" problems
Example: Longest Substring Without Repeating Characters ```javascript function lengthOfLongestSubstring(s) { const seen = new Set(); let left = 0, maxLen = 0;
for (let right = 0; right < s.length; right++) {
// Shrink window until no duplicates
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}
seen.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
} ```
Time: O(n), Space: O(min(n, m)) where m = character set size
Pattern 4: Fast & Slow Pointers
When to use: Cycle detection, finding middle element, linked list problems
Example: Detect Cycle in Linked List ```javascript function hasCycle(head) { let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next; // Move 1 step
fast = fast.next.next; // Move 2 steps
if (slow === fast) return true; // Cycle detected
}
return false; // Fast reached end, no cycle
} ```
Pattern 5: Binary Search (Beyond Sorted Arrays)
When to use: Search space has "monotonic" property, optimization problems
Example: Find Minimum in Rotated Sorted Array ```javascript function findMin(nums) { let left = 0, right = nums.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
// Right half is sorted, min is in left half
if (nums[mid] > nums[right]) {
left = mid + 1;
}
// Left half is sorted, min is in left half or mid
else {
right = mid;
}
}
return nums[left];
} ```
Pattern 6: Dynamic Programming
When to use: Overlapping subproblems, optimal substructure
Example: Coin Change ```javascript function coinChange(coins, amount) { const dp = Array(amount + 1).fill(Infinity); dp[0] = 0; // Base case: 0 coins needed for amount 0
for (let i = 1; i <= amount; i++) {
for (const coin of coins) {
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
} ```
Pattern 7: Backtracking
When to use: Generate all combinations, permutations, subsets
Example: Generate Parentheses ```javascript function generateParenthesis(n) { const result = [];
function backtrack(current, open, close) {
// Base case: used all parentheses
if (current.length === 2 * n) {
result.push(current);
return;
}
// Can add opening parenthesis if not exceeded
if (open < n) {
backtrack(current + '(', open + 1, close);
}
// Can add closing parenthesis if it doesn't exceed opening
if (close < open) {
backtrack(current + ')', open, close + 1);
}
}
backtrack('', 0, 0);
return result;
} ```
My Pattern Recognition Checklist
When facing a new problem, I ask:
- Is the input sorted? → Binary search, two pointers
- Do I need to find a pair/triplet? → Two pointers, hash map
- Is it about subarrays/substrings? → Sliding window, prefix sums
- Does it involve making choices? → Backtracking, DP
- Can I break it into smaller subproblems? → Recursion, DP
- Is there a greedy property? → Greedy algorithm
- Does it involve a graph/tree? → DFS, BFS
Step 3: Execute (Code with Confidence)
Best Practices:
1. Start with Brute Force Even if you know it's not optimal, write the O(n²) solution first. It:
- Verifies your understanding
- Gives you something to optimize
- Often passes initial test cases
2. Code in Layers ```javascript // Layer 1: Structure function twoSum(nums, target) { // TODO: implement }
// Layer 2: Core logic function twoSum(nums, target) { const map = new Map(); for (let i = 0; i < nums.length; i++) { // TODO: check complement } }
// Layer 3: Complete implementation 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 [-1, -1]; } ```
3. Use Descriptive Variable Names ```javascript // Bad let l = 0, r = n - 1;
// Good let leftPointer = 0, rightPointer = nums.length - 1; ```
4. Test As You Code After writing the loop, mentally walk through:
- First iteration
- Middle iteration
- Last iteration
- Edge cases (empty, single element)
Step 4: Reflect (The Secret Sauce)
This step separates good problem-solvers from great ones. After solving (or even failing to solve) a problem:
What I Do:
A. Analyze Time/Space Complexity ``` Brute force: O(n²) time, O(1) space Optimized: O(n) time, O(n) space
Trade-off: Used extra space to improve time complexity ```
B. Ask "Can I Do Better?"
- Can I reduce time complexity? (Better algorithm or data structure)
- Can I reduce space complexity? (In-place modification, constant space)
- Did I handle all edge cases?
C. Read Other Solutions On LeetCode, after solving, I read the top 3 solutions. I've learned:
- Pythonic tricks
- Built-in functions I didn't know existed
- Alternative approaches I hadn't considered
D. Add to My Pattern Library I maintain a notion document with:
- Problem name
- Pattern used
- Key insight
- Mistakes I made
Common Mistakes and How to Avoid Them
Mistake 1: Jumping to Code Too Quickly
Fix: Force yourself to write pseudocode first
Mistake 2: Not Testing Edge Cases
Fix: Always test:
- Empty input
- Single element
- All elements same
- Maximum constraints
Mistake 3: Ignoring Time Limits
Fix: Quick estimation:
- 10^8 operations ≈ 1 second
- If n ≤ 100: O(n³) is fine
- If n ≤ 10^4: O(n²) is fine
- If n ≤ 10^6: O(n log n) is fine
- If n ≤ 10^8: O(n) or O(log n) only
Mistake 4: Giving Up Too Early
Fix: The 20-minute rule:
- Struggle for 20 minutes
- If stuck, look at hints
- If still stuck, read solution
- Re-implement from memory the next day
My Practice Routine
Consistency beats intensity. Here's my weekly routine:
| Day | Activity | Duration | |-----|----------|----------| | Mon | 2 Easy problems | 30 min | | Tue | 1 Medium problem | 45 min | | Wed | Review past mistakes | 30 min | | Thu | 1 Medium problem | 45 min | | Fri | 1 Hard problem (or attempt) | 60 min | | Sat | Contest simulation | 90 min | | Sun | Rest / Read editorials | 30 min |
Total: ~5 hours/week
Resources That Helped Me
-
Books:
- "Cracking the Coding Interview" by Gayle Laakmann McDowell
- "Competitive Programming 3" by Steven Halim
-
Websites:
- LeetCode (best for interviews)
- Codeforces (best for competitive programming)
- AlgoExpert (great explanations)
-
YouTube Channels:
- NeetCode (clear explanations)
- Abdul Bari (algorithms fundamentals)
- William Fiset (advanced topics)
Real-World Application
These skills directly improved my work:
Example 1: Debugging Production Issue
- Problem: API timeout processing 10k user records
- Pattern Recognition: O(n²) nested loop detected
- Solution: Hash map optimization → 45s down to 0.8s
Example 2: Feature Development
- Problem: Find all users who haven't logged in for 30 days
- Pattern Recognition: Sliding window on time series data
- Solution: Efficient one-pass algorithm vs naive date comparisons
Conclusion
Problem-solving is not about memorizing solutions—it's about developing mental models and recognizing patterns. With deliberate practice:
- Month 1: You'll recognize basic patterns
- Month 3: You'll solve most mediums confidently
- Month 6: You'll attempt hards without fear
- Month 12: You'll develop intuition for optimal solutions
The journey from 0 to 200 problems taught me that consistency matters more than intensity. Solve one problem daily, reflect on it, and watch your problem-solving intuition grow.
Want to discuss problem-solving strategies or compare solutions? Connect with me on LeetCode or LinkedIn.