Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 19 additions & 13 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,24 @@
* Spread Stones
* [Test Minimum Moves To Spread Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py)
* Heap
* Longest Happy String
* [Test Longest Happy String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/longest_happy_string/test_longest_happy_string.py)
* Maximal Score After K Operations
* [Test Maximal Score](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/maximal_score_after_k_operations/test_maximal_score.py)
* Maximum Subsequence Score
* [Test Max Subsequence Score](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/maximum_subsequence_score/test_max_subsequence_score.py)
* Min Cost Hire K Workers
* [Test Min Cost To Hire Workers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/min_cost_hire_k_workers/test_min_cost_to_hire_workers.py)
* Min Cost To Connect Sticks
* [Test Min Cost Connect Sticks](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/min_cost_to_connect_sticks/test_min_cost_connect_sticks.py)
* Schedule Tasks
* [Test Schedule Tasks On Minimum Machines](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/schedule_tasks/test_schedule_tasks_on_minimum_machines.py)
* Topkclosesttoorigin
* [Test Top K Closest To Origin](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/topkclosesttoorigin/test_top_k_closest_to_origin.py)
* Topklargest
* [Test Top K Largest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/topklargest/test_top_k_largest_elements.py)
* Total Cost Hire K Workers
* [Test Total Cost Hire K Workers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/total_cost_hire_k_workers/test_total_cost_hire_k_workers.py)
* Huffman
* [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py)
* [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py)
Expand Down Expand Up @@ -173,6 +189,9 @@
* [Test Task Scheduler](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/task_scheduler/test_task_scheduler.py)
* Josephus Circle
* [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py)
* Matrix
* Isvalidsudoku
* [Test Is Valid Sudoku](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/isvalidsudoku/test_is_valid_sudoku.py)
* Memoization
* [Fibonacci](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/memoization/fibonacci.py)
* [Petethebaker](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/petethebaker.py)
Expand Down Expand Up @@ -670,17 +689,6 @@
* [Test Find Difference](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/hashmap/find_difference_of_two_arrays/test_find_difference.py)
* Unique Number Of Occurrences
* [Test Unique Occurrences](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/hashmap/unique_number_of_occurrences/test_unique_occurrences.py)
* Heap
* Maximal Score After K Operations
* [Test Maximal Score](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/heap/maximal_score_after_k_operations/test_maximal_score.py)
* Maximum Subsequence Score
* [Test Max Subsequence Score](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/heap/maximum_subsequence_score/test_max_subsequence_score.py)
* Min Cost Hire K Workers
* [Test Min Cost To Hire Workers](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/heap/min_cost_hire_k_workers/test_min_cost_to_hire_workers.py)
* Min Cost To Connect Sticks
* [Test Min Cost Connect Sticks](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/heap/min_cost_to_connect_sticks/test_min_cost_connect_sticks.py)
* Total Cost Hire K Workers
* [Test Total Cost Hire K Workers](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/heap/total_cost_hire_k_workers/test_total_cost_hire_k_workers.py)
* Hidden Cubic Numbers
* [Hidden Cubic Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/hidden_cubic_numbers/hidden_cubic_numbers.py)
* Matrix In Spiral Form
Expand Down Expand Up @@ -861,8 +869,6 @@
* [Test Longest Common Prefix](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_prefix/test_longest_common_prefix.py)
* Longest Common Suffix Queries
* [Test Longest Common Suffix Queries](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_suffix_queries/test_longest_common_suffix_queries.py)
* Longest Happy String
* [Test Longest Happy String](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_happy_string/test_longest_happy_string.py)
* Longest Self Contained Substring
* [Test Longest Self Contained Substring](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py)
* Look And Say Sequence
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ strings, return any one of them. If no such string can be formed, return an empt

## Examples

![Example 1](./images/examples/longest_happy_string_example_1.png)
![Example 2](./images/examples/longest_happy_string_example_2.png)
![Example 3](./images/examples/longest_happy_string_example_3.png)
![Example 1](images/examples/longest_happy_string_example_1.png)
![Example 2](images/examples/longest_happy_string_example_2.png)
![Example 3](images/examples/longest_happy_string_example_3.png)


## Solution
Expand Down Expand Up @@ -55,21 +55,21 @@ Note: In Python, strings are immutable, so we construct the result as a list and

Let’s look at the following illustration to get a better understanding of the solution:

![Longest Happy String Solution 1](./images/solutions/longest_happy_string_solution_1.png)
![Longest Happy String Solution 2](./images/solutions/longest_happy_string_solution_2.png)
![Longest Happy String Solution 3](./images/solutions/longest_happy_string_solution_3.png)
![Longest Happy String Solution 4](./images/solutions/longest_happy_string_solution_4.png)
![Longest Happy String Solution 5](./images/solutions/longest_happy_string_solution_5.png)
![Longest Happy String Solution 6](./images/solutions/longest_happy_string_solution_6.png)
![Longest Happy String Solution 7](./images/solutions/longest_happy_string_solution_7.png)
![Longest Happy String Solution 8](./images/solutions/longest_happy_string_solution_8.png)
![Longest Happy String Solution 9](./images/solutions/longest_happy_string_solution_9.png)
![Longest Happy String Solution 10](./images/solutions/longest_happy_string_solution_10.png)
![Longest Happy String Solution 11](./images/solutions/longest_happy_string_solution_11.png)
![Longest Happy String Solution 12](./images/solutions/longest_happy_string_solution_12.png)
![Longest Happy String Solution 13](./images/solutions/longest_happy_string_solution_13.png)
![Longest Happy String Solution 14](./images/solutions/longest_happy_string_solution_14.png)
![Longest Happy String Solution 15](./images/solutions/longest_happy_string_solution_15.png)
![Longest Happy String Solution 1](images/solutions/longest_happy_string_solution_1.png)
![Longest Happy String Solution 2](images/solutions/longest_happy_string_solution_2.png)
![Longest Happy String Solution 3](images/solutions/longest_happy_string_solution_3.png)
![Longest Happy String Solution 4](images/solutions/longest_happy_string_solution_4.png)
![Longest Happy String Solution 5](images/solutions/longest_happy_string_solution_5.png)
![Longest Happy String Solution 6](images/solutions/longest_happy_string_solution_6.png)
![Longest Happy String Solution 7](images/solutions/longest_happy_string_solution_7.png)
![Longest Happy String Solution 8](images/solutions/longest_happy_string_solution_8.png)
![Longest Happy String Solution 9](images/solutions/longest_happy_string_solution_9.png)
![Longest Happy String Solution 10](images/solutions/longest_happy_string_solution_10.png)
![Longest Happy String Solution 11](images/solutions/longest_happy_string_solution_11.png)
![Longest Happy String Solution 12](images/solutions/longest_happy_string_solution_12.png)
![Longest Happy String Solution 13](images/solutions/longest_happy_string_solution_13.png)
![Longest Happy String Solution 14](images/solutions/longest_happy_string_solution_14.png)
![Longest Happy String Solution 15](images/solutions/longest_happy_string_solution_15.png)

### Time complexity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def longest_diverse_string(a: int, b: int, c: int) -> str:
# Process the heap until it's empty or no valid character can be added
while max_heap:
# Pop the character with the highest remaining frequency
count, character = heappop(max_heap)
count = -count # Convert back to positive
negative_count, character = heappop(max_heap)
count = -negative_count # Convert back to positive

# Check if adding this character violates the "no three consecutive" rule
if len(result) >= 2 and result[-1] == character and result[-2] == character:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from parameterized import parameterized
from pystrings.longest_happy_string import longest_diverse_string
from algorithms.heap.longest_happy_string import longest_diverse_string


class LongestHappyStringTestCase(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Constraints:

## Examples

![Example 1](./images/examples/min_cost_to_connect_sticks_1.png)
![Example 2](./images/examples/min_cost_to_connect_sticks_2.png)
![Example 3](./images/examples/min_cost_to_connect_sticks_3.png)
![Example 4](./images/examples/min_cost_to_connect_sticks_4.png)
![Example 1](images/examples/min_cost_to_connect_sticks_1.png)
![Example 2](images/examples/min_cost_to_connect_sticks_2.png)
![Example 3](images/examples/min_cost_to_connect_sticks_3.png)
![Example 4](images/examples/min_cost_to_connect_sticks_4.png)
104 changes: 104 additions & 0 deletions algorithms/heap/topkclosesttoorigin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# K Closest Points to Origin

Given a list of points in the form [[x1, y1], [x2, y2], ... [xn, yn]] and an integer k, find the k closest points to the
origin (0, 0) on the 2D plane.

The distance between two points (x, y) and (a, b) is calculated using the formula:

√(x1 - a2)2 + (y1 - b2)2

Return the k closest points in any order.

## Examples

```text
Input:

points = [[3,4],[2,2],[1,1],[0,0],[5,5]]
k = 3

Output:
[[2,2],[1,1],[0,0]]

Also Valid:

[[2,2],[0,0],[1,1]]
[[1,1],[0,0],[2,2]]
[[1,1],[2,2],[0,0]]
...
[[0,0],[1,1],[2,2]]
```

![Example 1](./images/examples/top_k_closest_to_origin_example_1.png)

```text
Input: points = [[3,3],[5,-1],[-2,4]], k = 2
Output: [[3,3],[-2,4]]
Explanation: The answer [[-2,4],[3,3]] would also be accepted.
```

```text
Input: points = [[1,3],[-2,2]], k = 1
Output: [[-2,2]]

Explanation:
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]].
```

## Solution

- [Approach 1](#approach-1-sorting)
- [Approach 2](#approach-2-max-heap)

### Approach 1: Sorting

The simplest approach is to sort calculate the distance of each point from the origin and sort the points based on their
distance. This approach has a time complexity of O(n log n) where n is the number of points in the array, and a space
complexity of O(n) (to store the sorted array of distances).

### Approach 2: Max Heap

This problem can be solved using a similar approach to the one used to solve [Kth Largest Element in an Array](../topklargest/README.md). The key
difference is that we need to store the k closest points to the origin, rather than the k largest elements. Since we are
looking for the k smallest elements, we need a max-heap, rather than a min-heap.

By default, python's heapq module implements a min-heap, but we can make it behave like a max-heap by negating the values
of everything we push onto it.

We add the first k points to the heap by pushing a tuple containing the negative of the distance from the origin, and the
index of the point. After that is finished, our heap contains the k closest points to the origin that we've seen so far,
with the point furthest from the origin at the root of the heap.

For each point after the first k, we calculate the distance from the origin and compare it with the root of the heap. If
the current point is closer to the origin than the root of the heap, we pop the root and push the current point into the
heap. This way, the heap will always contain the k closest points to the origin we've seen so far.

At the end of the iteration, the heap will contain the k closest points to the origin. We can iterate over each point in
the heap and return the point associated with each tuple.

![Solution 0](./images/solutions/top_k_closest_to_origin_solution_0.png)
![Solution 1](./images/solutions/top_k_closest_to_origin_solution_1.png)
![Solution 2](./images/solutions/top_k_closest_to_origin_solution_2.png)
![Solution 3](./images/solutions/top_k_closest_to_origin_solution_3.png)
![Solution 4](./images/solutions/top_k_closest_to_origin_solution_4.png)
![Solution 5](./images/solutions/top_k_closest_to_origin_solution_5.png)
![Solution 6](./images/solutions/top_k_closest_to_origin_solution_6.png)
![Solution 7](./images/solutions/top_k_closest_to_origin_solution_7.png)
![Solution 8](./images/solutions/top_k_closest_to_origin_solution_8.png)
![Solution 9](./images/solutions/top_k_closest_to_origin_solution_9.png)
![Solution 10](./images/solutions/top_k_closest_to_origin_solution_10.png)
![Solution 11](./images/solutions/top_k_closest_to_origin_solution_11.png)

#### Complexity Analysis

##### Time Complexity: O(n log k)

Where n is the number of points in the array and k is the input parameter. We iterate over
all points, and in the worst case, we both push and pop each point from the heap, which takes O(log k) time per point.

##### Space Complexity: O(k)

Where k is the input parameter. The space is used by the heap to store the k closest points to the origin.
36 changes: 36 additions & 0 deletions algorithms/heap/topkclosesttoorigin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import List, Tuple
import heapq


def k_closest_to_origin(points: List[List[int]], k: int) -> List[List[int]]:
# Max heap will store the top k points closest to the origin in the form (-distance, idx). The distance is negated
# because in Python, the heapq module uses min-heap by default. Negating values gives us a maximum heap.
# We store the idx of the point for later retrieval from the points array passed in
max_heap: List[Tuple[int, int]] = []

for idx, point in enumerate(points):
x, y = point
# calculate the distance for this point from the origin
distance = x * x + y * y

# If the contents of the heap are less than the desired top k, then we add the current point's distance and idx
if len(max_heap) < k:
heapq.heappush(max_heap, (-distance, idx))
# We check if the calculated distance of this point is less than the top element in the heap. If this point
# is closer to the origin that what is at the root of the heap, we pop the root of the heap and add this
# new distance and index.
# Note the negation here again to get the actual distance
elif distance < -max_heap[0][0]:
heapq.heappushpop(max_heap, (-distance, idx))

# Return the top k points closest to origin. We use 1 to get the index of the point from the original points list
# as that is what is stored in the max heap
return [points[point[1]] for point in max_heap]


def k_closest_to_origin_sorting(points: List[List[int]], k: int) -> List[List[int]]:
# Sort the points by the distance from the origin. This incurs a cost of O(n log(n)) and space cost of O(n) due to
# timsort
sorted_points = sorted(points, key=lambda p: p[0] ** 2 + p[1] ** 2)
# Retrieve the top k points closest to the origin
return sorted_points[:k]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.heap.topkclosesttoorigin import (
k_closest_to_origin,
k_closest_to_origin_sorting,
)

TOP_K_CLOSEST_TO_ORIGIN = [
([[3, 4], [2, 2], [1, 1], [0, 0], [5, 5]], 3, [[2, 2], [1, 1], [0, 0]]),
([[1, 3], [-2, 2]], 1, [[-2, 2]]),
([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]),
]


class TopKClosestToOriginTestCase(unittest.TestCase):
@parameterized.expand(TOP_K_CLOSEST_TO_ORIGIN)
def test_top_k_closest_to_origin(
self, points: List[List[int]], k: int, expected: List[List[int]]
):
actual = k_closest_to_origin(points, k)

sorted_expected = sorted(expected, key=lambda x: (x[0], x[1]))
sorted_actual = sorted(actual, key=lambda x: (x[0], x[1]))
self.assertEqual(sorted_expected, sorted_actual)

@parameterized.expand(TOP_K_CLOSEST_TO_ORIGIN)
def test_top_k_closest_to_origin_sorting(
self, points: List[List[int]], k: int, expected: List[List[int]]
):
actual = k_closest_to_origin_sorting(points, k)

sorted_expected = sorted(expected, key=lambda x: x[0])
sorted_actual = sorted(actual, key=lambda x: x[0])
self.assertEqual(sorted_expected, sorted_actual)


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