Is Learning DSA Boring? Let's Use DSA View View πŸ‘€πŸ‘€ (Two Sum, Binary Search, and Bubble Sort)

Is Learning DSA Boring? Let's Use DSA View View πŸ‘€πŸ‘€ (Two Sum, Binary Search, and Bubble Sort)

●1 ●2 ●24
calendar_today ago β€’ schedule6 min read
β€” Originally published at dev.to

Hoi hoi!

I’m @nyaomaru, a frontend engineer who dislikes crowded places, so I'm planning to take a quiet vacation in September. 🏝️

Have you used DSA View View already? πŸ‘€πŸ‘€

https://coderlegion.com/22758/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view

DSA View View allows you to understand DSA by visualizing how your implementation actually runs.

But just introducing the tool is not enough.

Can it actually help us understand DSA?

In this article, we'll walk through three classic problems:

  • Two Sum
  • Binary Search
  • Bubble Sort

We'll first understand the algorithm, and then see what is actually happening with DSA View View.

Image description

Let's learn together! 😸


πŸ—ΊοΈ Two Sum

Let's start with a very famous problem.

Given an array of numbers and a target value, find the indices of two numbers whose sum equals the target.

For example,

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

The answer is

[0, 1];

Because

2 + 7 = 9

Simple!

So, how should we find them? πŸ€”

Brute Force

The easiest approach is probably checking every possible pair.

function twoSum(nums: number[], target: number): number[] {
  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 [];
}

This works.

But if the array becomes large, we may need to compare a lot of pairs, right?

The time complexity is O(nΒ²)

Can we avoid checking the same values again and again?

Yes.

Let's use a Map.

function twoSum(nums: number[], target: number): number[] {
  const seen = new Map<number, number>();

  for (let i = 0; i < nums.length; i++) {
    const current = nums[i];
    const need = target - current;

    if (seen.has(need)) {
      return [seen.get(need)!, i];
    }

    seen.set(current, i);
  }

  return [];
}

The important part is this πŸ‘‡

const need = target - current;

Instead of asking

Which two numbers should I combine?

we ask

What number do I need to complete the target?

Let's follow the example.

At first

current = 2
target = 9

need = 9 - 2
     = 7

Have we already seen 7?

No.

So we remember 2.

seen = {
  2 β†’ 0
}

Next

current = 7
target = 9

need = 9 - 7
     = 2

Have we already seen 2?

Yes! πŸ‘€πŸ‘€

seen = {
  2 β†’ 0
}

So

return [0, 1];

Done!

Because we only need to walk through the array once, the time complexity becomes.

Time:  O(n)
Space: O(n)

πŸ‘€ Let's View View It

The implementation is quite small.

But when I was first learning this pattern, this part still felt a little magical.

if (seen.has(need))
  • Where did need come from?
  • What is inside seen at this moment?
  • Why does checking the previous values solve the problem?

This is exactly where visualization helps.

Image description

{% embed https://dsa-view-view.vercel.app/#s=j.eyJlIjoidHdvLXN1bSIsImwiOiJ0eXBlc2NyaXB0IiwibSI6InZlcmlmaWNhdGlvbiIsInYiOjF9 %}

With DSA View View, we can move through the runtime one step at a time and inspect how the values change.

2
↓
Need 7
↓
Remember 2
↓
7
↓
Need 2
↓
Found 2!
πŸŽ‰

Now the Map is not just some mysterious trick.

We can actually follow the idea.

Remember what we have already seen, and check whether the value we need is there.

Nice! 😸


Next is Binary Search.

Suppose we have this sorted array

[1, 3, 5, 7, 9, 11, 13]

And we want to find

11

Of course, we could start from 1 and check every number.

1 β†’ 3 β†’ 5 β†’ 7 β†’ 9 β†’ 11

That works.

But Binary Search does something smarter.

Instead of checking from the beginning, it checks the middle.

[1, 3, 5, 7, 9, 11, 13]
          ↑
         mid

Our middle value is 7.

We are looking for 11.

11 > 7

Because the array is sorted, we already know something very useful.

Everything on the left side of 7 is also smaller than 11.

So, we don't need that half anymore. πŸ‘‹

[1, 3, 5, 7, 9, 11, 13]
             β””β”€β”€β”€β”€β”€β”€β”€β”˜
               search

Now we check the middle of the remaining range.

[9, 11, 13]
     ↑
    mid

And

11 === 11

Found it! πŸŽ‰

Here is the implementation.

function binarySearch(nums: number[], target: number): number {
  let left = 0;
  let right = nums.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (nums[mid] === target) {
      return mid;
    }

    if (nums[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }

  return -1;
}

There are three important variables.

left
right
mid

They represent the current search range.

For our example, they start like this

left = 0
right = 6
mid = 3

[1, 3, 5, 7, 9, 11, 13]
 ↑        ↑          ↑
left     mid       right

Because

nums[mid] < target;

we move left.

left = mid + 1;

Now

[1, 3, 5, 7, 9, 11, 13]
             ↑   ↑   ↑
           left mid right

And 11 is found.

Why Is Binary Search Fast?

This is the interesting part.

Each step removes about half of the remaining candidates.

If there are 1,000 values, we don't necessarily need 1,000 checks.

It becomes roughly

1000
↓
500
↓
250
↓
125
↓
...

That's why Binary Search has

Time:  O(log n)
Space: O(1)

But there is one very important condition.

The data must be sorted.

Without sorted data, we cannot safely throw away half of the search range.

πŸ‘€ Let's View View It

Binary Search is one of the algorithms that made me want a visualization tool in the first place.

The code itself is short

left = mid + 1;

or

right = mid - 1;

Easy.

But when learning it, I sometimes found myself thinking:

Wait... which part are we searching now? 😿

Image description

{% embed https://dsa-view-view.vercel.app/#s=j.eyJlIjoiYmluYXJ5LXNlYXJjaCIsImwiOiJ0eXBlc2NyaXB0IiwibSI6InZlcmlmaWNhdGlvbiIsInYiOjF9 %}

When we visualize left, mid, and right, the idea becomes much easier to follow.

We are not randomly changing three numbers.

We are continuously shrinking the search area.

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

       ↓

        β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

       ↓

          β–ˆβ–ˆβ–ˆ

       ↓

           β–ˆ

That's Binary Search!

Cut the unnecessary half.

Then cut it again. And again. And again.

Until we find the answer. βœ‚οΈπŸ˜Έ


Bubble Sort

Finally, let's sort something!

Consider this array.

[5, 1, 4, 2, 8];

We want.

[1, 2, 4, 5, 8];

Bubble Sort repeatedly compares two neighboring values.

If they are in the wrong order, it swaps them.

Let's look at the beginning.

[5, 1, 4, 2, 8]
 ↑  ↑

Compare

5 > 1

So swap them.

[1, 5, 4, 2, 8]

Next

[1, 5, 4, 2, 8]
    ↑  ↑

Again

5 > 4

Swap!

[1, 4, 5, 2, 8]

And continue.

[1, 4, 5, 2, 8]
       ↑  ↑

5 > 2

Swap!

[1, 4, 2, 5, 8]

Eventually, larger values move toward the end of the array.

They kind of...

bubble up.

That's why it is called Bubble Sort.

Here is a simple implementation:

function bubbleSort(nums: number[]): number[] {
  for (let i = 0; i < nums.length - 1; i++) {
    for (let j = 0; j < nums.length - i - 1; j++) {
      if (nums[j] > nums[j + 1]) {
        [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
      }
    }
  }

  return nums;
}

We repeatedly compare

nums[j];

And

nums[j + 1];

and swap them when necessary.

After one full pass, the largest remaining value reaches its correct position near the end.

So on the next pass, we don't need to check that position again.

That's why the inner loop contains.

nums.length - i - 1;

Complexity

Bubble Sort isn't very fast for large arrays.

Its time complexity is

Time:  O(nΒ²)
Space: O(1)

So I probably won't suddenly replace production sorting with Bubble Sort tomorrow. 😸

But as a learning example, I really like it.

Why?

Because you can see the algorithm working.

πŸ‘€ Let's View View It

This is probably the most visually satisfying one of the three.

Image description

{% embed https://dsa-view-view.vercel.app/#s=j.eyJlIjoiYnViYmxlLXNvcnQiLCJsIjoidHlwZXNjcmlwdCIsIm0iOiJ2ZXJpZmljYXRpb24iLCJ2IjoxfQ %}

Instead of only reading

[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];

we can follow the values moving through the array.

[5, 1, 4, 2, 8]

 ↓ swap

[1, 5, 4, 2, 8]

    ↓ swap

[1, 4, 5, 2, 8]

       ↓ swap

[1, 4, 2, 5, 8]

Then another pass begins.

The code contains nested loops, indexes, comparisons, and swaps.

But visually, the basic rule is extremely simple:

Compare neighbors. If the left one is bigger, swap them.

Repeat. Repeat. Repeat.

Sorted! πŸŽ‰


🧠 What Did We Actually Learn?

These three problems look quite different.

But each one introduces a useful way of thinking.

Two Sum

Remember information from previous steps.

Have I already seen what I need?

Use what we already know to remove impossible candidates.

Can I safely discard half of the search space?

Bubble Sort

Break a larger problem into many small comparisons.

Are these two values in the correct order?

This is one of the things I find interesting about learning DSA.

At first, the implementation can look like a collection of indexes, loops, conditions, and mysterious variables.

But behind the code, there is usually a much simpler idea.

And sometimes I don't fully understand that idea just by staring at the code.

I want to view it. πŸ‘€πŸ‘€


🎯 Conclusion

In this article, we looked at three classic algorithms:

  • Two Sum with a Map
  • Binary Search
  • Bubble Sort

And more importantly, we looked at how the data changes while they run.

I think this is where visualization can be especially useful.

  • Reading the final implementation tells us what the code is.
  • Stepping through it helps us understand why it works.

That's exactly why I built DSA View View.

https://dsa-view-view.vercel.app

You can write or load a TypeScript implementation, run it with your own inputs, and move backward and forward through the runtime.

If you are learning DSA too, try taking a problem you already solved and viewing it step by step.

You may notice something you didn't notice when only reading the code. πŸ‘€

And if there is a DSA problem you want me to cover next, please let me know in the comments!

I still have many algorithms to learn myself. 😸

Let's train our DSA muscles together! πŸ’ͺ

If you like DSA View View, please give it a star ⭐

https://github.com/nyaomaru/dsa-view-view

And my DSA View View has launched at TinyLaunch! πŸš€ Please take a loot πŸ‘‡

https://www.tinylaunch.com/launch/17804-dsa-view-view

See you in the next article!

2 Comments

1 vote
0
πŸ”₯ Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

The Sovereign Vault β€” A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

How I Made JavaScript Execution Visual and Rewindable (DSA View View πŸ‘€πŸ‘€)

nyaomaru - Jul 29

I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View πŸ‘€πŸ‘€)

nyaomaru - Jul 15

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13
chevron_left
401 Points β€’ 27 Badges
Heerlen, the Netherlands β€’ nyaomaru-portfolio.vercel.app
10Posts
7Comments
20Connections
Funny Frontend Engineer! ? Living in the Netherlands.

Related Jobs

View all jobs β†’

Commenters (This Week)

2 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!