Showing posts with label leetcode. Show all posts
Showing posts with label leetcode. Show all posts

Friday, September 4, 2026

Container With Most Water – Two Pointer Solution in JavaScript | LeetCode #11

What if the best answer is hiding at the two ends?

At first glance, Container With Most Water looks like a problem where we need to try every possible pair of lines.

There are potentially thousands or even millions of pairs. So the obvious solution quickly becomes too slow.

But there is a simple observation that lets us eliminate huge numbers of possibilities without checking them.

The trick is a classic Two Pointer technique.

1. The Problem

You are given an integer array height. Each element represents the height of a vertical line.

The line at index i and the line at index j can form a container. The goal is to find the pair of lines that can hold the maximum amount of water.

The amount of water between two lines is determined by two things:

  • The distance between the two lines.
  • The height of the shorter line.
Formula:

Area = min(height[i], height[j]) × (j - i)

For example, if two lines have heights 8 and 5, and they are 4 positions apart:

Area = min(8, 5) × 4 = 5 × 4 = 20

2. Naive Thinking: Try Every Pair

The first idea that naturally comes to mind is:

  1. Pick every possible left line.
  2. Pick every possible right line.
  3. Calculate the area.
  4. Keep track of the maximum.

That means checking every pair:

for (let i = 0; i < height.length; i++) {
    for (let j = i + 1; j < height.length; j++) {
        // calculate area
    }
}

If the array contains n elements, there can be roughly pairs.

Therefore, the brute-force approach has:

Time Complexity: O(n²)

That is the part we need to improve.

3. The Key Insight: The Shorter Line Controls the Water

This is the most important observation in the problem.

Consider two lines:

Left = 3
Right = 7

Even though the right line is height 7, the container can only hold water up to height 3.

So the area is:

min(3, 7) × width

Now suppose we move the taller line.

The width becomes smaller, but the limiting height is still potentially 3.

So moving the taller line cannot give us a better answer while the shorter line remains unchanged.

The Rule

Always move the pointer pointing to the shorter line.

This single observation reduces the problem from O(n²) to O(n).

4. Visual Walkthrough

Let's imagine the array:


Index:   0   1   2   3   4   5   6   7
Height:  1   8   6   2   5   4   8   3

We start with two pointers:


i = 0
j = 7

The two heights are:


height[i] = 1
height[j] = 3

width = 7

Therefore:

Area = min(1, 3) × 7 = 7

The shorter line is at i, so we move i forward.


i → 1
j → 7

Now:


height[i] = 8
height[j] = 3
width = 6

Area = min(8, 3) × 6
     = 18

The shorter line is now at j, so we move j backward.

We continue this process until the two pointers meet.

Why don't we miss the answer?

Suppose the left line is shorter. Keeping that same left line and moving the right pointer inward can only reduce the width.

Since the left line is already limiting the height, those possibilities cannot produce a better container than the current pair.

Therefore, we can safely discard that shorter line and move its pointer.

5. Your JavaScript Solution

Here is the two-pointer implementation:

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function (height) {
    let area = 0
    let i = 0; j = height.length - 1

    while (i < j) {
        if (height[i] > height[j]) {
            area = Math.max(area, height[j] * (j - i));
            j--
        } else {
            area = Math.max(area, height[i] * (j - i));
            i++
        }
    }

    return area
};

How the Code Works

We initialize two pointers:

let i = 0;
let j = height.length - 1;

One pointer starts at the beginning and the other starts at the end.

Then we repeatedly calculate the current container area:

height[shorterPointer] * (j - i)

If the left line is taller than the right line:

j--

Otherwise, we move the left pointer:

i++

Notice that your implementation handles equal heights by moving the left pointer. This is completely valid because either pointer can be moved when both heights are equal.

6. Edge Cases

Case 1: Two Elements

If there are only two lines, there is only one possible container.

[5, 8]

Area = min(5, 8) × 1
     = 5

Case 2: All Heights Are Equal

[5, 5, 5, 5]

The algorithm still works normally. When both heights are equal, the else branch moves the left pointer.

Case 3: Increasing Heights

[1, 2, 3, 4, 5]

The algorithm starts from both ends and keeps moving the shorter pointer. No nested loops are required.

Case 4: Very Large Input

This is where the two-pointer solution really matters. Instead of checking every pair, each pointer moves across the array only once.

7. Complexity Analysis

Metric Complexity
Time O(n)
Space O(1)

Why O(n)?

Both pointers only move toward each other. The left pointer never moves backward, and the right pointer never moves forward.

Therefore, across the entire algorithm, there are at most n pointer movements.

The algorithm also uses only a few variables, so the extra space is O(1).

8. How to Explain This in an Interview

If you get this problem in an interview, avoid immediately jumping into code. Explain the reasoning first.

Step 1: The area between two lines is determined by the shorter line and the distance between them.

Step 2: Start with pointers at both ends to maximize the initial width.

Step 3: Calculate the current area and update the maximum.

Step 4: Move the pointer pointing to the shorter line because keeping that line while reducing the width cannot produce a better result.

Step 5: Continue until the pointers meet.

A concise interview explanation could be:

I use two pointers starting from both ends. For every pair, the shorter height limits the amount of water, while the distance gives the width. After calculating the area, I move the pointer at the shorter height because moving the taller pointer would only decrease the width without increasing the limiting height. This allows us to eliminate impossible candidates and solve the problem in O(n) time and O(1) space.

9. The Reusable Pattern: Two Pointers

The most valuable thing about this problem isn't memorizing the solution. It's recognizing the pattern.

The Two Pointer technique is useful when:

  • You are working with an array or string.
  • You need to compare elements from opposite sides.
  • The search space can be reduced based on a condition.
  • Moving one pointer allows you to safely eliminate possibilities.

The general structure looks like this:

let left = 0;
let right = arr.length - 1;

while (left < right) {

    // calculate / evaluate current state

    if (some_condition) {
        left++;
    } else {
        right--;
    }
}

The important question to ask yourself when you see a similar problem is:

"Can I eliminate an entire set of possibilities by moving one pointer?"

If the answer is yes, there is a good chance a two-pointer solution can turn a quadratic approach into a linear one.

10. Conclusion

Container With Most Water is a great example of how an algorithmic insight can completely change the performance of a solution.

The brute-force approach checks every possible pair and takes O(n²). The two-pointer approach intelligently eliminates impossible candidates and brings the complexity down to O(n).

The key idea to remember is simple:

Find the shorter line → calculate area → move the shorter pointer.

Once this reasoning becomes familiar, you'll start seeing the same pattern in many other array and string problems.

Don't just remember the code. Remember why moving the shorter pointer is safe. That's the real solution.

LeetCode #1, Two Sum Explained: The HashMap Pattern Behind O(n) Solutions

Two Sum in C# & JavaScript: The HashMap Pattern Explained

LeetCode Problem #1 — Two Sum

The Two Sum problem is one of the most popular beginner problems on LeetCode. It looks simple at first, but it teaches one of the most important patterns in coding interviews: using a HashMap to reduce an O(n²) problem to O(n).

In this article, we will understand the problem, derive the efficient approach step by step, walk through the algorithm using an example, and implement it in both C# and JavaScript.

💡 The key idea:
Don't search for the second number. Calculate what the second number needs to be.

📌 Problem Statement

You are given an integer array nums and an integer target. Your task is to find the indices of two numbers whose sum is equal to the target.

You can assume that exactly one valid solution exists, and you cannot use the same element twice. The answer can be returned in any order.

For the official problem statement and constraints, see LeetCode — Two Sum .

Example

nums = [2, 7, 11, 15]
target = 9

We need to find two numbers whose sum is 9.

Here:

2 + 7 = 9

The indices are:

2 → index 0
7 → index 1

Therefore, the answer is:

[0, 1]

🧠 First Thought: The Brute Force Approach

The most straightforward solution is to compare every number with every other number.

For example:

for every i
    for every j
        check if nums[i] + nums[j] == target

This works, but there is a problem.

If the array contains 10,000 elements, checking every possible pair can require roughly comparisons.

⚠️ Problem:
Brute force has a time complexity of O(n²). The Two Sum problem specifically asks us to find an approach better than O(n²).

🚀 The Important Observation

Instead of asking:

"Which number should I pair with the current number?"

We can ask:

"What number do I need to reach the target?"

Suppose:

current number = 2
target = 9

The number we need is:

9 - 2 = 7

So instead of searching through the rest of the array for 7, we can simply check whether we have already seen 7.

🧩 The Formula

If the current number is x, we need another number y such that:

x + y = target

Rearranging the equation:

y = target - x

That gives us the key formula:

complement = target - current number

🗂️ Why Do We Need a Dictionary / HashMap?

We need a data structure that can quickly tell us whether a number has already appeared.

A HashMap is perfect for this.

In C#, we use:

Dictionary<int, int>

We store:

number → index

For example:

Number Index
2 0
7 1

This allows us to check whether a required number exists in approximately constant time.

🔄 How the Algorithm Works

For every element in the array, we perform three simple steps.

1️⃣ Calculate the complement
complement = target - nums[i]
2️⃣ Check the Dictionary
If the complement already exists, we have found the answer.
3️⃣ Store the current number
If the complement doesn't exist, store the current number and its index.

🔍 Step-by-Step Example

Let's use:

nums = [2, 7, 11, 15]
target = 9

Step 1 — Number 2

Current number:

2

Calculate the complement:

9 - 2 = 7

Does the dictionary contain 7?

No.

So we store:

2 → 0

Step 2 — Number 7

Current number:

7

Calculate the complement:

9 - 7 = 2

Now we check the dictionary.

We already have:

2 → 0

So we found the required pair.

The current index is 1, therefore:

[0, 1]

We can immediately return the result without processing the remaining elements.

📊 Visualizing the Process

Array

[ 2, 7, 11, 15 ]

Current = 2

Complement = 9 - 2 = 7

Is 7 already stored?

❌ No

Store 2 → index 0


Current = 7

Complement = 9 - 7 = 2

Is 2 already stored?

✅ Yes

Return [0, 1]

⚠️ Why Do We Check First and Store Later?

This is a very important detail in the implementation.

The order should be:

1. Calculate complement
2. Check dictionary
3. Store current number

Consider this example:

nums = [3, 3]
target = 6

For the first 3:

6 - 3 = 3

The dictionary doesn't contain 3, so we store the first occurrence.

For the second 3, the complement is again 3. This time, the first 3 already exists in the dictionary.

Therefore we return:

[0, 1]
✅ Important:
Checking before storing ensures that we never accidentally use the same array element twice. It also naturally handles duplicate values.

💻 C# Solution

Here is the C# implementation using Dictionary<int, int>:

public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        Dictionary<int, int> dic = new Dictionary<int, int>();

        for(int i = 0; i < nums.Length; i++){
            int val = target - nums[i];

            if(dic.ContainsKey(val)){
                return [dic.GetValueOrDefault(val), i];
            }
            else{
                dic[nums[i]] = i;
            }
        }

        return [];
    }
}

💻 JavaScript Solution

The same HashMap approach can also be implemented in JavaScript using the built-in Map.

JavaScript solution:

var twoSum = function(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 [];
};

⏱️ Time and Space Complexity

Approach Time Space
Brute Force O(n²) O(1)
HashMap / Dictionary O(n) average O(n)

Why is the optimized solution O(n)?

We iterate through the array only once. Each dictionary lookup and insertion takes O(1) average time.

Therefore:

n elements × O(1) lookup
= O(n)

🎯 The General Pattern Behind Two Sum

The most valuable part of this problem isn't just the solution. It's the pattern that you can reuse in many other problems.

Store what you've seen

Calculate what you need

Look it up

Return the answer

Whenever you see a problem involving:

  • Finding a pair
  • Finding a complement
  • Checking whether something appeared before
  • Counting occurrences
  • Finding duplicates
  • Matching values efficiently

you should consider whether a HashMap / Dictionary can help.

🎤 Interview Explanation

If an interviewer asks you to explain your solution, you can describe it simply:

"I iterate through the array and calculate the complement of the current number by subtracting it from the target. I store previously seen numbers along with their indices in a HashMap. Before storing the current number, I check whether its complement already exists in the map. If it does, I return the stored index and the current index. This allows the problem to be solved in O(n) average time instead of O(n²)."

🧠 What You Should Remember

  • Brute force checks every possible pair and takes O(n²).
  • The target tells us exactly what number we need.
  • Use target - current to calculate the complement.
  • Store numbers along with their indices.
  • Check the HashMap before storing the current number.
  • The HashMap approach takes O(n) average time.
  • The same pattern works across different programming languages.

🚀 Final Takeaway

Two Sum is often presented as a very easy problem, but the underlying idea is extremely useful. The important lesson isn't simply knowing how to solve Two Sum. It's recognizing when you can replace repeated searching with constant-time HashMap lookups.

Instead of repeatedly asking:

"Where is the number I need?"

calculate it first:

What I need = Target − What I currently have

That simple change in thinking turns the solution from a nested-loop O(n²) approach into an efficient O(n) average-time solution.


📌 Problem: LeetCode — Two Sum

Languages covered: C# and JavaScript

Container With Most Water – Two Pointer Solution in JavaScript | LeetCode #11

What if the best answer is hiding at the two ends? At first glance, Container With Most Water looks like a proble...

horizontal ads