Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@
* [Test Sort Colors](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/sort_colors/test_sort_colors.py)
* Three Sum
* [Test Three Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/three_sum/test_three_sum.py)
* Triangle Numbers
* [Test Triangle Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/triangle_numbers/test_triangle_numbers.py)
* Unique Bsts
* [Unique Bsts](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/unique_bsts/unique_bsts.py)
* Word Count
Expand Down
18 changes: 9 additions & 9 deletions algorithms/intervals/insert_interval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ Return the updated list of intervals.

## Solution

We first want to create a new list merged to store the merged intervals we will return at the end.
We first want to create a new list `merged` to store the merged intervals we will return at the end.

This solution operates in 3 phases:
1. Add all the intervals ending before newInterval starts to merged.
2. Merge all overlapping intervals with newInterval and add that merged interval to merged.
3. Add all the intervals starting after newInterval to merged.
1. Add all the intervals ending before `newInterval` starts to `merged`.
2. Merge all overlapping intervals with `newInterval` and add that merged interval to `merged`.
3. Add all the intervals starting after `newInterval` to `merged`.

### Phase 1

In this phase, we add all the intervals that end before newInterval starts to merged. This involves iterating through the
intervals list until the current interval no longer ends before newInterval starts (i.e. intervals[i][1] >= newInterval[0]).
In this phase, we add all the intervals that end before `newInterval` starts to `merged`. This involves iterating through the
`intervals` list until the current interval no longer ends before `newInterval` starts (i.e. `intervals[i][1] >= newInterval[0]`).

![Solution 1](./images/solutions/insert_interval_solution_1.png)
![Solution 2](./images/solutions/insert_interval_solution_2.png)

### Phase 2

In this phase, we merge all the intervals that overlap with newInterval together into a single interval by updating
newInterval to be the minimum start and maximum end of all the overlapping intervals. This involves iterating through
the intervals list until the current interval starts after newInterval ends (i.e. intervals[i][0] > newInterval[1]).
When that condition is met, we add newInterval to merged and move onto phase 3.
`newInterval` to be the minimum start and maximum end of all the overlapping intervals. This involves iterating through
the intervals list until the current interval starts after `newInterval` ends (i.e. `intervals[i][0] > newInterval[1]`).
When that condition is met, we add `newInterval` to merged and move onto phase 3.

![Solution 3](./images/solutions/insert_interval_solution_3.png)
![Solution 4](./images/solutions/insert_interval_solution_4.png)
Expand Down
58 changes: 58 additions & 0 deletions algorithms/two_pointers/three_sum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,61 @@ Input: nums = [0,0,0]
Output: [[0,0,0]]
Explanation: The only possible triplet sums up to 0.
```

## Solution

We can leverage the two-pointer technique to solve this problem by first sorting the array. We can then iterate through
each element in the array. The problem then reduces to finding two numbers in the rest of the array that sum to the
negative of the current element, which follows the same logic as the Two Sum (Sorted Array) problem.

![Solution 1](./images/solutions/three_sum_solution_1.png)

Since our first triplet sums to 0, we can add it to our result set.

![Solution 2](./images/solutions/three_sum_solution_2.png)
![Solution 3](./images/solutions/three_sum_solution_3.png)
![Solution 4](./images/solutions/three_sum_solution_4.png)
![Solution 5](./images/solutions/three_sum_solution_5.png)
![Solution 6](./images/solutions/three_sum_solution_6.png)

### Avoiding Duplicates

As soon as we find a triplet that sums to 0, we can add it to our result set. We then have to move our left and right
pointers to look for the next triplet while avoiding duplicate triplets. We can do this by moving the left and right
pointers until they point to different numbers than the ones they were pointing to before.
Here we move the left pointer once until it reaches the last -1 in the array. Then, we can move both the left and right
pointers so that they both point to new numbers.

![Solution 7](./images/solutions/three_sum_solution_7.png)
![Solution 8](./images/solutions/three_sum_solution_8.png)

Here we can do another iteration of the Two Sum problem using the new positions of the left and right pointers.

![Solution 9](./images/solutions/three_sum_solution_9.png)
![Solution 10](./images/solutions/three_sum_solution_10.png)
![Solution 11](./images/solutions/three_sum_solution_11.png)

At this point our left and right pointers have crossed, so we can move our iterator to the next number in the array.

### Avoiding Duplicates II

In this case, since the next number in the array is the same as the previous number, we can skip it. We can do this by
moving our iterator until it points to a new number.

![Solution 12](./images/solutions/three_sum_solution_12.png)
![Solution 13](./images/solutions/three_sum_solution_13.png)
![Solution 14](./images/solutions/three_sum_solution_14.png)

And we're ready to start the Two Sum algorithm again, so we reset our left and right pointers, and start the algorithm.

![Solution 15](./images/solutions/three_sum_solution_15.png)
![Solution 16](./images/solutions/three_sum_solution_16.png)
![Solution 17](./images/solutions/three_sum_solution_17.png)

### Termination

Our algorithm terminates when i reaches the 3rd to last element in the array (i.e., i < n - 2). This is because we need
at least 2 more elements after i for left and right to form a triplet.

![Solution 18](./images/solutions/three_sum_solution_18.png)
![Solution 19](./images/solutions/three_sum_solution_19.png)
30 changes: 22 additions & 8 deletions algorithms/two_pointers/three_sum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,48 @@
def three_sum(nums: List[int]) -> List[List[int]]:
"""
Complexity Analysis:
Ww assume that n is the length of the input array
Time Complexity: O(nlog(n)) + O(n^2) = O(n^2) the O(nlog(n)) is due to sorting
Space Complexity: O(1) as no extra space is taken up
Time Complexity: O(nlog(n)) + O(n^2) = O(n^2) the O(nlog(n)) is due to sorting, overall, the time complexity is O(n²).
This is due to the nested loops in the algorithm. We perform n iterations of the outer loop, and each iteration
takes O(n) time to use the two-pointer technique.
Space Complexity: O(n²) as no extra space is taken up. We need to store all distinct triplets that sum to 0, which
can be at most O(n²) triplets.
Args:
nums (list): input list of integers
Return:
list: list of lists of integers
"""
result = []
# Time Complexity: O(nlog(n))
# Time Complexity: O(nlog(n)) sorting in place. This may incur space complexity of O(n) due to Python's timesort
# using temporary storage to handle the in place sorting
nums.sort()

for idx, num in enumerate(nums):
# Increment to avoid duplicates
if idx > 0 and num == nums[idx - 1]:
continue

left, right = idx + 1, len(nums) - 1

while left < right:
sum_ = num + nums[left] + nums[right]
if sum_ > 0:
total = num + nums[left] + nums[right]
if total > 0:
right -= 1
elif sum_ < 0:
elif total < 0:
left += 1
else:
# add the triplet
result.append([num, nums[left], nums[right]])
left += 1
while nums[left] == nums[left - 1] and left < right:

# move the left pointer to avoid duplicates while it is still less than the right
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1

return result
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.
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.
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.
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.
34 changes: 15 additions & 19 deletions algorithms/two_pointers/three_sum/test_three_sum.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import unittest

from typing import List
from parameterized import parameterized
from algorithms.two_pointers.three_sum import three_sum

THREE_SUM_TEST_CASES = [
([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]),
([0, 1, 1], []),
([0, 0, 0], [[0, 0, 0]]),
([-1, 0, 1, 2, -1, -1], [[-1, -1, 2], [-1, 0, 1]]),
([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]),
([-1, 0, 1, 2, -1, -4, 2], [[-1, -1, 2], [-1, 0, 1], [-4, 2, 2]]),
([-1, -1, 0, 1, 1, 1, 2], [[-1, -1, 2], [-1, 0, 1]]),
([-1, 0, 1, 2, -1, -4, -1, 2, 1], [[-1, -1, 2], [-1, 0, 1], [-4, 2, 2]]),
]

class ThreeSumTestCases(unittest.TestCase):
def test_one(self):
"""Should return [[-1, -1, 2], [-1, 0, 1]] for nums = [-1, 0, 1, 2, -1, -4]"""
nums = [-1, 0, 1, 2, -1, -4]
expected = [[-1, -1, 2], [-1, 0, 1]]
actual = three_sum(nums)
self.assertEqual(expected, actual)

def test_two(self):
"""Should return [] for nums = [0, 1, 1]"""
nums = [0, 1, 1]
expected = []
actual = three_sum(nums)
self.assertEqual(expected, actual)

def test_three(self):
"""Should return [[0,0,0]] for nums = [0,0,0]"""
nums = [0, 0, 0]
expected = [[0, 0, 0]]
class ThreeSumTestCases(unittest.TestCase):
@parameterized.expand(THREE_SUM_TEST_CASES)
def test_three_sum(self, nums: List[int], expected: List[List[int]]):
actual = three_sum(nums)
self.assertEqual(expected, actual)

Expand Down
83 changes: 83 additions & 0 deletions algorithms/two_pointers/triangle_numbers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Triangle Numbers

Write a function to count the number of triplets in an integer array nums that could form the sides of a triangle. For
three sides to form a valid triangle, the sum of any two sides must be greater than the third side. The triplets do not
need to be unique.

## Examples

```text
Input:
nums = [11,4,9,6,15,18]

Output:
10

Explanation: Valid combinations are...

4, 15, 18
6, 15, 18
9, 15, 18
11, 15, 18
9, 11, 18
6, 11, 15
9, 11, 15
4, 6, 9
```

## Solution

In order for a triplet to be valid lengths of a triangle, the sum of any two sides must be greater than the third side.
By sorting the array, we can leverage the two-pointer technique to count all valid triplets in O(n2) time and O(1) space.
The key to this question is realizing that if we sort three numbers from smallest to largest (say a ≤ b ≤ c), we only
need to check if a + b > c. If this condition holds, the other two conditions (a + c > b and b + c > a) are automatically
satisfied because c ≥ b and b ≥ a. For example, with 4, 8, 9, if 4 + 8 > 9 is true, then we have a valid triplet.

![Solution 1](./images/solutions/triangle_numbers_solution_1.png)

But not only that, triplets where the smallest number is between 4 and 8 are also valid triplets.

![Solution 2](./images/solutions/triangle_numbers_solution_2.png)

This means that if we sort the input array, and then iterate from the end of the array to the beginning, we can use the
two-pointer technique to efficiently count all valid triplets.

![Solution 3](./images/solutions/triangle_numbers_solution_3.png)

The pointers i, left, and right represent the current triplet we are considering. If nums[left] + nums[right] > nums[i]
then we know there are a total of right - left valid triplets, since all triplets between left and right are also valid
triplets. We can then decrement right to check for the valid triplets that can be made by decreasing the middle value.

![Solution 4](./images/solutions/triangle_numbers_solution_4.png)
![Solution 5](./images/solutions/triangle_numbers_solution_5.png)
![Solution 6](./images/solutions/triangle_numbers_solution_6.png)
![Solution 7](./images/solutions/triangle_numbers_solution_7.png)

When nums[left] + nums[right] < nums[i], we know that all triplets between left and right are also invalid, so we
increment left to look for a larger smallest value.

![Solution 8](./images/solutions/triangle_numbers_solution_8.png)

Each time left and right cross, we decrement i and reset left and right to their positions at opposite ends of the array.
This happens until i is less than 2, at which point we have counted all valid triplets.

![Solution 9](./images/solutions/triangle_numbers_solution_9.png)
![Solution 10](./images/solutions/triangle_numbers_solution_10.png)
![Solution 11](./images/solutions/triangle_numbers_solution_11.png)
![Solution 12](./images/solutions/triangle_numbers_solution_12.png)
![Solution 13](./images/solutions/triangle_numbers_solution_13.png)
![Solution 14](./images/solutions/triangle_numbers_solution_14.png)
![Solution 15](./images/solutions/triangle_numbers_solution_15.png)
![Solution 16](./images/solutions/triangle_numbers_solution_16.png)
![Solution 17](./images/solutions/triangle_numbers_solution_17.png)
![Solution 18](./images/solutions/triangle_numbers_solution_18.png)
![Solution 19](./images/solutions/triangle_numbers_solution_19.png)
![Solution 20](./images/solutions/triangle_numbers_solution_20.png)
![Solution 21](./images/solutions/triangle_numbers_solution_21.png)
![Solution 22](./images/solutions/triangle_numbers_solution_22.png)
![Solution 23](./images/solutions/triangle_numbers_solution_23.png)
![Solution 24](./images/solutions/triangle_numbers_solution_24.png)
![Solution 25](./images/solutions/triangle_numbers_solution_25.png)
![Solution 26](./images/solutions/triangle_numbers_solution_26.png)
![Solution 27](./images/solutions/triangle_numbers_solution_27.png)
![Solution 28](./images/solutions/triangle_numbers_solution_28.png)
51 changes: 51 additions & 0 deletions algorithms/two_pointers/triangle_numbers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List


def triangle_number(heights: List[int]) -> int:
"""
Finds the count of valid triangles that can be formed from the given input of numbers. A valid triangle is a triangle
which has any two sides whose sum is greater than the third side. This assumes that it is okay to manipulate the
input list. Therefore callers of this function should be aware that the input list is manipulated in place.

Args:
heights (list): list of integers that represent sides of a triangle
Returns:
int: number of valid triangles that can be formed
"""
# If there are no heights, or we have an empty list, return 0 early as no valid triangles can be formed here.
if not heights:
return 0

# Sorts the heights in place. This incurs a time complexity cost of O(n log(n)) and space cost of O(n) as this sorting
# requires temporary storage using Python's timsort
heights.sort()

# Keeps track of number of valid triangles that can be formed
count = 0

# Iterate through the list starting from the back, idx will be at the last position, this will be the third pointer
for idx in range(len(heights) - 1, 1, -1):
# Initialize the two pointers to keep track of the other two indices that will point to the two other numbers that
# can form a valid triangle.
left = 0
right = idx - 1

# This is a micro-optimization to get the largest side and use it in the loop below
largest_side = heights[idx]

while left < right:
# A valid triplet is found by satisfying the condition a + b > c. If this condition holds, then the other
# two conditions hold as well, a + c > b and b + c > a.
is_valid_triplet = heights[left] + heights[right] > largest_side
if is_valid_triplet:
# The numbers between the right and left pointers form valid triplets with the number at the idx position
# we find all the possible triplets(triangles) that can be formed by finding the difference.
count += right - left
# we decrement right to check if there are valid triplets that can be formed by decreasing the middle valid
right -= 1
else:
# Increase the left to find the next maximum minimum number that can form a valid triplet
left += 1

# return the count of the triangles that can be formed
return count
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.two_pointers.triangle_numbers import triangle_number

TRIANGLE_NUMBER_TEST_CASES = [
([11, 4, 9, 6, 15, 18], 10),
([2,2,3,4], 3),
([4,2,3,4], 4),
]


class TriangleNumberTestCases(unittest.TestCase):
@parameterized.expand(TRIANGLE_NUMBER_TEST_CASES)
def test_triangle_number(self, heights: List[int], expected: int):
actual = triangle_number(heights)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Loading