# 🚀 From Brute Force to Optimal: Solving LeetCode Problems Step-by-Step (with JavaScript Examples)

When tackling coding problems, the smartest approach isn’t always the fastest—it’s knowing how to evolve your solution over time. Whether you're a beginner or brushing up for interviews, this post will walk you through **8 techniques**, each one solving an example problem in progressively smarter ways.

---

## 🧱 Step 1: Brute Force with Loops

### 📌 Problem: **Two Sum**

Given an array `nums` and a target value, return the indices of two numbers such that they add up to the target.

### 🧠 Brute Force Solution

```js
function twoSum(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];
      }
    }
  }
}
```

### 🔍 Analysis

* ✅ Easy to understand
    
* ❌ Time: `O(n²)` → slow for large arrays
    

---

## 🧮 Step 2: Hash Map Optimization

### 💡 Use hashing to store previously seen elements

```js
function twoSum(nums, target) {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    const diff = target - nums[i];
    if (map.has(diff)) return [map.get(diff), i];
    map.set(nums[i], i);
  }
}
```

### 🔍 Analysis

* ✅ Time reduced to `O(n)`
    
* ✅ More scalable
    

---

## 🔁 Step 3: Sliding Window

### 📌 Problem: **Longest Substring Without Repeating Characters**

### 🧠 Brute Force (Nested Loops)

```js
function lengthOfLongestSubstring(s) {
  let max = 0;
  for (let i = 0; i < s.length; i++) {
    let set = new Set();
    for (let j = i; j < s.length; j++) {
      if (set.has(s[j])) break;
      set.add(s[j]);
    }
    max = Math.max(max, set.size);
  }
  return max;
}
```

### 🧠 Optimized with Sliding Window

```js
function lengthOfLongestSubstring(s) {
  let set = new Set();
  let left = 0, maxLen = 0;

  for (let right = 0; right < s.length; right++) {
    while (set.has(s[right])) {
      set.delete(s[left]);
      left++;
    }
    set.add(s[right]);
    maxLen = Math.max(maxLen, right - left + 1);
  }
  return maxLen;
}
```

### 🔍 Analysis

* ❌ Brute force: `O(n²)`
    
* ✅ Sliding window: `O(n)`
    

---

## 📦 Step 4: Prefix Sum

### 📌 Problem: **Subarray Sum Equals K**

### 🧠 Optimized with Prefix Sum + Map

```js
function subarraySum(nums, k) {
  const map = new Map();
  map.set(0, 1);
  let count = 0, sum = 0;

  for (const num of nums) {
    sum += num;
    if (map.has(sum - k)) count += map.get(sum - k);
    map.set(sum, (map.get(sum) || 0) + 1);
  }
  return count;
}
```

### 🔍 Analysis

* ✅ Handles multiple subarrays
    
* ✅ Time: `O(n)`
    
* ✅ Space: `O(n)`
    

---

## 🔍 Step 5: Sorting + Binary Search

### 📌 Problem: **Find Insert Position**

### 🧠 Binary Search

```js
function searchInsert(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (nums[mid] === target) return mid;
    else if (nums[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return left;
}
```

### 🔍 Analysis

* ✅ Efficient for sorted arrays
    
* ✅ Time: `O(log n)`
    

---

## 🧭 Step 6: Heap / Greedy

### 📌 Problem: **Top K Frequent Elements**

### 🧠 Using Map + Sort (Greedy-style)

```js
function topKFrequent(nums, k) {
  const count = new Map();
  for (const num of nums) {
    count.set(num, (count.get(num) || 0) + 1);
  }
  const sorted = [...count.entries()].sort((a, b) => b[1] - a[1]);
  return sorted.slice(0, k).map(([num]) => num);
}
```

### 🔍 Analysis

* ✅ Time: `O(n log n)`
    
* 🛠️ Can optimize with heap for `O(n log k)`
    

---

## 🧵 Step 7: Backtracking

### 📌 Problem: **Subsets**

### 🧠 Recursive Backtracking

```js
function subsets(nums) {
  const res = [];
  function backtrack(start, path) {
    res.push([...path]);
    for (let i = start; i < nums.length; i++) {
      path.push(nums[i]);
      backtrack(i + 1, path);
      path.pop();
    }
  }
  backtrack(0, []);
  return res;
}
```

### 🔍 Analysis

* ✅ Generates all combinations
    
* ❌ Time: `O(2ⁿ)`
    

---

## 🏗️ Step 8: Dynamic Programming

### 📌 Problem: **Climbing Stairs**

### 🧠 Bottom-Up DP

```js
function climbStairs(n) {
  if (n <= 2) return n;
  let dp = [1, 1];
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}
```

### 🔍 Analysis

* ✅ Time: `O(n)`
    
* ✅ Space: `O(n)` (can optimize to `O(1)`)
    

---

## 🔄 Final Recap Table

| Step | Technique | Example Problem | Time Complexity |
| --- | --- | --- | --- |
| 1 | Brute Force | Two Sum | `O(n²)` |
| 2 | Hash Map / Set | Two Sum (Optimized) | `O(n)` |
| 3 | Sliding Window | Longest Substring Without Repeat | `O(n)` |
| 4 | Prefix Sum | Subarray Sum Equals K | `O(n)` |
| 5 | Binary Search | Search Insert Position | `O(log n)` |
| 6 | Greedy / Sort / Heap | Top K Frequent Elements | `O(n log k)` |
| 7 | Backtracking | Subsets | `O(2ⁿ)` |
| 8 | Dynamic Programming | Climbing Stairs | `O(n)` |

---

## 🧠 Conclusion

When approaching a problem:

1. **Start with brute force** to understand the core logic.
    
2. **Climb the ladder**—optimize with hash maps, two pointers, prefix sums, and beyond.
    
3. **Always analyze complexity** and think: *Can I do better?*
    

Whether you're prepping for interviews or improving daily, this technique ladder gives you a structured way to level up. 🚀
