Skip to content

Commit

Permalink
Merge pull request #60 from bovem/drafts
Browse files Browse the repository at this point in the history
Article #33: Two-Pointer Approach
  • Loading branch information
bovem authored Jan 23, 2024
2 parents c9aea57 + cadbc79 commit 59b9658
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ For a glimpse into my journey and the projects I've been involved in, feel free
- <a target=_blank href="https://www.avni.sh/posts/dsa/encoding-and-decoding-strings/">Encoding and Decoding Functions for Strings</a>
- <a target=_blank href="https://www.avni.sh/posts/dsa/longest-consecutive/">Finding the Longest Consecutive Sequence in an Array</a>
- <a target=_blank href="https://www.avni.sh/posts/dsa/linked-lists/">Linked Lists</a>
- <a target=_blank href="https://www.avni.sh/posts/dsa/two-pointers-approach/">Two-Pointer Approach</a>
- <a target=_blank href="https://www.avni.sh/posts/developer-tools/">Developer Tools</a>
- <a target=_blank href="https://www.avni.sh/posts/developer-tools/my-development-environment/">My Development Environment: kitty, zsh, Neovim, tmux, and lazygit</a>

Expand Down
1 change: 1 addition & 0 deletions content/contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ summary: "Index of all content"
* <a target=_blank href="/posts/dsa/encoding-and-decoding-strings/">Encoding and Decoding Functions for Strings</a>
* <a target=_blank href="/posts/dsa/longest-consecutive/">Finding the Longest Consecutive Sequence in an Array</a>
- <a target=_blank href="/posts/dsa/linked-lists/">Linked Lists</a>
* <a target=_blank href="/posts/dsa/two-pointers-approach/">Two-Pointer Approach</a>
- <a target=_blank href="/posts/developer-tools/">Developer Tools</a>
- <a target=_blank href="/posts/developer-tools/my-development-environment/">My Development Environment: kitty, zsh, Neovim, tmux, and lazygit</a>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
281 changes: 281 additions & 0 deletions content/posts/dsa/two-pointers-approach/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
---
author: "Avnish"
title: "Two-Pointer Approach"
date: "2024-01-23"
description: "The two-pointer approach provides an efficient alternative for solving problems related to substrings, subarrays, and sequences."
tags: ["data-structures", "linked-lists", "fast-slow-pointers", "two-pointer", "sliding-window", "pointers"]
categories: ["Data Structures"]
series: ["Data Structures and Algorithms"]
aliases: ["two-pointer-approach", "sliding-window", "fast-slow-pointers"]
ShowToc: true
TocOpen: false
comments: false
cover:
image: "two-pointers-cover.png"
linkFullImages: true
alt: "We can improve the time complexity of some solutions from quadratic to linear using the two-pointer approach"
caption: ""
relative: false
responsiveImages: false
math: true
---

To solve problems like <a href="/posts/dsa/two-sums/" target="_blank">Two Sums</a>,
<a href="/posts/dsa/product-except-self/" target="_blank">Product Except Self</a>, or
<a href="/posts/dsa/contains-duplicate/" target="_blank">Contains Duplicate</a> we have to
access multiple values at the same time from a sequential data structure (for example, a
<a href="/posts/dsa/linked-lists/" target="_blank">Linked List</a> or an
<a href="/posts/dsa/arrays-strings-hashmaps/#arrays" target="_blank">Array</a>).

The initial instinct while solving these problems is to use nested loops where each layer
of the loop will maintain a different iterator on the data structure. But this
approach does not scale well with the size of input as a single
order of nested loops has $O(n^2)$ worst-case time complexity.

To implement a scalable solution we have to avoid nesting and access multiple values
in a single loop by maintaining two iterators. We can use this *two-pointer* approach
to solve the **Two Sums** problem (assuming the input is sorted).

<p align="center"><img src="two-pointers-two-sum.png" alt="Two Pointer Approach for solving the Two Sums Problem"></p>

```text
startPointer = 0
endPointer = length(array)-1
while(startPointer<endPointer){
sumValue = array
if(sumValue==target){
return (startPointer, endPointer)
}
else if (sumValue<target){
// Since the sumValue is small we have
// to increment the startPointer to a
// larger value
startPointer += 1
} else {
// Since the sumValue is larger we have
// to decrement the endPointer to a
// smaller value
endPointer -= 1
}
}
return none, none
```

Unlike the <a href="/posts/dsa/two-sums/#brute-force-solution" target="_blank">brute-force solution to two sums</a> problem which uses nested loops to
return results in quadratic time ($O(n^2)$), this approach will return results
in linear time ($O(1)$) while also maintaining a constant space complexity.

# Fast-Slow Pointers
The pointers can also iterate the data structure at an independent pace. For example,
a *fast* pointer can iterate over two or more values at a time whereas a *slow*
pointer can iterate over a single value at a time.

## Finding the middle node of a Linked List
Assume that we have to find the middle node of a Linked List. The Linked List
is a dynamically allocated data structure so its size could not be determined without
iterating it once completely. So a loop with $O(n)$ time complexity will be executed
on the linked list to determine its length $n$, then a second loop with $O(n/2)$ time
complexity will be executed to fetch the middle node. This will result in the total
time complexity of $O(n) + O(n/2)$ in the worst-case scenario.

```text
// Finding the length of the linked list
linked_list_length = 1
temp = linked_list.head
while(temp.next != nil){
temp = temp.next
linked_list_length += 1
}
// Iterating over the linked list until
// the middle node
middle_node_location = linked_list_length/2
temp = linked_list.head
i=0
while(i<middle_node_location){
temp=temp.next
i+=1
}
return temp
```

<p align="center"><img src="middle-node.png" alt="Finding the middle node of a linked list"></p>

But we can reduce the time complexity of the solution to just $O(n)$ if we use a fast and
a slow pointer to iterate over the linked list simultaneously. The slow pointer will
iterate over every node in the linked list whereas the fast pointer will skip over every other node.

```text
fast = linked_list.head
slow = linked_list.head
while(fast!=nil) or (fast.next!=nil){
// fast will skip over every other node
fast = fast.next.next
// slow will iterate the linked list
// one node at a time
slow = slow.next
}
return slow
```

<p align="center"><img src="two-pointers-middle-node.png" alt="Finding the middle node of a linked list using two-pointer approach"></p>

Since the fast pointer covers twice the distance as the slow pointer in the same number of
iterations when it reaches the end of the linked list the slow pointer would be at the middle node.

# Sliding Window
Some problems require analysis of sub-sequences of a data structure. Instead
of iterating the data structure one value at a time, we can define a *window* and move
it by one value on every iteration i.e. value/pattern identified from the previous window
would be reused with the new element to analyze the current window, eliminating redundancy.

<p align="center"><img src="sliding-window.png" alt="Sliding Window approach"></p>

Depending on the problem, the window size could be static (constant on every iteration) or
dynamic (variable on every iteration).

## Static Window Size
If the subsequence size defined in the problem is static then the sliding window size
will also be constant.

### Calculating Moving Average
The **Moving Average / Rolling Mean** is the mean calculated over a window of values with a constant size.

For example, 30 days moving average, 5 months moving average, etc.

$$Mean(a_1, a_2, a_3, a_4, a_5)={(a_1+a_2+a_3+a_4+a_5) \over 5}$$
$$Mean_{Rolling}(a_1, a_2, a_3, \dots , a_n, 3)=Mean(a_1, a_2, a_3),\ Mean(a_2, a_3, a_4),\newline Mean(a_3, a_4, a_5),\ \dots,\ Mean(a_{n-2}, a_{n-1}, a_{n})$$

If we have to calculate the moving average of values over the data provided in the
form of an array. We can implement a brute force solution that will use
a nested loop of window size $k$ and calculate the individual averages.

```text
func calculate_moving_average(array, k)
moving_averages = []
for index in array
moving_sum = 0
for index2 in range(0, k)
// Calculating the sum of the current window
moving_sum += array[index+index2]
// Appending the average from the current window
// to the moving average list
moving_averages.append(moving_sum/k)
return moving_averages
```

This approach will result in the total time complexity of $O(kn)$ where $k$ is the size
of the window and $n$ is the number of elements in the array.

By using a sliding window of size $k$ (the moving average window size) we can
eliminate the nested loop from the solution and decrease the total time
complexity to $O(n)$.

We know the formula for calculating the mean over the window of the first 3 values is
$$Mean(a_1, a_2, a_3)={(a_1+a_2+a_3) \over 3}$$

Instead of calculating the next rolling mean from scratch, we can use the value of
$Mean(a_1, a_2, a_3)$ by adding the value of the element introduced in this window
and subtracting the value of the element from the previous window (divided by the
window size).

$$Mean(a_2, a_3, a_4) = {{a_2 + a_3 + a_4} \over 3}$$
$$Mean(a_2, a_3, a_4) = {{a_2 + a_3 + a_4 + a_1 - a_1} \over 3}$$
$$Mean(a_2, a_3, a_4) = {{a_1 + a_2 + a_3} \over 3} + {a_4 \over 3} - {a_1 \over 3}$$
$$Mean(a_2, a_3, a_4) = {{a_1 + a_2 + a_3} \over 3} + {{a_4 - a_1} \over 3}$$
$$Mean(a_2, a_3, a_4) = Mean(a_1, a_2, a_3) + {{a_4 - a_1} \over 3}$$

```text
func average(values)
return sum(values)/length(values)
func calculate_moving_average(array, k)
moving_averages = []
// Calculating the average value of the
// first window
temp_average = average(array[:k])
moving_averages.append(temp_average)
for index in range(k, length(array))
// Using the average value from the
// previous window to calculate the average
// value of the current window
temp_average = temp_average + (array[index] + array[index-k])/k
moving_averages.append(temp_average)
return moving_averages
```

## Dynamic Window Size
Finding the largest/smallest subsequence satisfying a particular condition inside an array
or string requires us to work with an unknown window size. In such cases, to apply the sliding
window approach we have to maintain a start and an end pointer, updated dynamically while
iterating over the data structure.

### Finding the sub-array that sum up to a target value
Given a sorted array of positive integers as input, we have to find the first sub-array
that sums up to a target value.

Since the length of the sub-array is unknown
we have to create a dynamically sized window and calculate the sum of its elements.
If the sum is smaller than the target value then we will increase the window size from
the end and if the window sum is larger than the target value then we decrease its size
from the beginning. This cycle will be repeated until we have found the sub-array.

<p align="center"><img src="dynamic-sliding-window.png" alt="Dynamic Sliding Window approach"></p>

```text
func calculate_sum(array, start, end)
sum = 0
for index in [start, end]
sum += array[index]
return sum
func find_subarray(array, target)
start = 0
end = 0
while(end<len(array))
window_sum = calculate_sum(array, start, end)
if window_sum < target:
// If the sum of the current window
// is smaller than the target value
// then increase the window size from
// the end
end += 1
else if window_sum > target:
// If the sum of the current window
// is larger than the target value
// then decrease the window size from
// the start
start += 1
else:
return start, end
return nil, nil
```
<hr>
Thank you for taking the time to read this blog post! If you found this content valuable and would like to stay updated with my latest posts consider subscribing to my <a href="https://www.avni.sh/index.xml" target="_blank">RSS Feed</a>.

# Resources
<a href="https://usaco.guide/silver/two-pointers?lang=cpp" target="_blank">Two Pointers</a>
<a href="https://github.com/Chanda-Abdul/Several-Coding-Patterns-for-Solving-Data-Structures-and-Algorithms-Problems-during-Interviews/blob/main/%E2%9C%85%20%20Pattern%2001%20%3A%20Sliding%20Window.md" target="_blank">Sliding Window</a>
<a href="https://en.wikipedia.org/wiki/Moving_average" target="_blank">Moving Average</a>
<a href="https://www.youtube.com/watch?v=GcW4mgmgSbw" target="_blank">Solve subarray problems FASTER (using Sliding Windows)</a>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions content/posts/homelab/self-hosting-ollama/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,8 @@ used by our Ollama container.
Watch Ollama in action inside VSCode
<p align="center"><img src="ollama-vscode.gif" alt="Adding Comments to code using Ollama in VSCode"></p>
<p align="center"><small>Adding Comments to code using Ollama in VSCode</small></p>
<p align="center"><img src="ollama-vscode.gif" alt="Optimizing code using Ollama in VSCode"></p>
<p align="center"><small>Optimizing code using Ollama in VSCode</small></p>
<hr>
Thank you for taking the time to read this blog post! If you found this content valuable and would like to stay updated with my latest posts consider subscribing to my <a href="https://www.avni.sh/index.xml" target="_blank">RSS Feed</a>.
Expand Down
Binary file modified content/posts/homelab/self-hosting-ollama/ollama-nvim.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified content/posts/homelab/self-hosting-ollama/ollama-vscode.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 59b9658

Please sign in to comment.