forked from gitgik/algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
1,667 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
## Problem | ||
Find the first duplicate value in an array of positive integers. Each integer value in the array is less or equal to the length of the array. | ||
|
||
Sample input: [1, 2, 1, 3] (Notice the length of array is 3, so the largest value is 3) | ||
|
||
Sample output: 1 | ||
|
||
Try finding a solution that runs in constant space. | ||
|
||
|
||
```python | ||
## solution (O(n) time | O(n) space) | ||
def first_dup(array): | ||
unique = set() | ||
for i in array: | ||
if i in unique: | ||
return i | ||
unique.add(i) | ||
return -1 | ||
|
||
``` | ||
|
||
|
||
```python | ||
first_dup([1, 2, 1, 3, 2]) | ||
``` | ||
|
||
|
||
|
||
|
||
1 | ||
|
||
|
||
|
||
### Optimal Approach | ||
We can take each element's value minus 1 and use it as an index. Where the index will fall in the array, we'll set that value to -ve. As we loop through the array, | ||
if we end up at an element that is already set to -ve, we know we've found a duplicate. | ||
|
||
|
||
|
||
```python | ||
def first_duplicate(array): | ||
"""Complexity: O(n) time | O(1) space""" | ||
|
||
i = 0 | ||
while i < len(array): | ||
val = abs(array[i]) - 1 | ||
# if we find a negative number, it's our first duplicate | ||
if array[val] < 0: | ||
return abs(array[val]) | ||
else: | ||
# make the current value negative | ||
array[val] = -array[val] | ||
# increment i | ||
i += 1 | ||
return -1 | ||
``` | ||
|
||
|
||
```python | ||
first_duplicate([ 2, 1, 3, 4, 7, 6, 7]) | ||
``` | ||
|
||
|
||
|
||
|
||
7 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
# Finding The Smallest Interval of K-sorted Lists. | ||
|
||
Given K sorted lists of integers, return the **smallest interval** (inclusive) that contains at least one element from each least. | ||
|
||
For example, given: | ||
|
||
```python | ||
[[0, 1, 4, 17, 20, 25, 31], | ||
[5, 6, 10], | ||
[0, 3, 7, 8, 12]] | ||
``` | ||
The smallest range here is [3,5], since it contains: | ||
* 4 from first list, | ||
* 5 from the second list (inclusive), | ||
* 3 from the third list (inclusive) | ||
|
||
|
||
|
||
## Naive Solution | ||
The brute force solution would be to compare every pair of elements in the lists and consider their intervals. After finding this interval, traverse every list to make sure that there is at least one element from each list in the interval. | ||
|
||
In order to find the smallest such interval, we need to store the smallest seen so far, and update if we see a smaller interval. | ||
|
||
This would be an expensive O(N^3), where N is the total number of elements in all K lists. On the bright side, this solution uses O(1) memory, since it only needs to store the current smallest interval. | ||
|
||
|
||
## Solution 1: K-pointers | ||
Since the K lists are all sorted, this suggest iterating from the beginning (smallest elements) to end (largest elements). | ||
|
||
| ||
|
||
Imagine comparing the minimum values of all the arrays: In our example above, the first values will be **[0, 5, 0]**. And the interval would be the minimum and maximum of these values: **[0, 5]**. | ||
|
||
| ||
|
||
This is one such interval but we aren't sure if it's the smallest. So we must keep looking. We must step along by increasing the minimum. In this case, the next interval we should consider is **[1, 5]** | ||
|
||
Let's translate this process into an alogirithm: | ||
|
||
1. Initialize K pointers, one for each list, pointing the minimum element in the list. (index 0 -- because they are sorted). | ||
2. Initialize variables to track the right and left boundaries of the interval. | ||
3. Find the pointer pointing to the **minimum** and the pointer pointing to the **maximum** of all the values pointed to. This becomes your **tracked interval**. | ||
4. If the new interval is smaller than the previously tracked interval, update the tracked interval to be this new interval. | ||
5. Increment the minimum value pointer. After incrementing this pointer, it may not point to a minimum value anymore. | ||
6. Repeat steps 3 -5 until we get to the end of one of the lists. | ||
7. Return the tracked interval (by now, it's the smallest) | ||
|
||
|
||
```python | ||
def smallest_interval(k_arrays): | ||
# step 1: init K pointers | ||
k_pointers = [0] * len(k_arrays) # evaluates to [0, 0, 0] | ||
# step 2: init the left, right boundaries of the interval | ||
interval = float('-inf'), float('inf') | ||
|
||
while True: | ||
# initialize local max and min, for updating tracked interval. | ||
local_max = float('-inf') | ||
local_min = float('inf') | ||
min_index = -1 | ||
reached_end = False | ||
|
||
# step 3: find out which is the max, min to make the interval | ||
for i in range(len(k_pointers)): | ||
|
||
# first, check if we've reached the end of one of the K-lists | ||
if k_pointers[i] >= len(k_arrays[i]): | ||
reached_end = True | ||
break | ||
|
||
if k_arrays[i][k_pointers[i]] > local_max: | ||
local_max = k_arrays[i][k_pointers[i]] | ||
|
||
if k_arrays[i][k_pointers[i]] < local_min: | ||
local_min = k_arrays[i][k_pointers[i]] | ||
# save the index of minimum value, to be used later for incrementing | ||
min_index = i | ||
|
||
# if we've reached the end of any list, | ||
# we've already found the smallest interval | ||
if reached_end: | ||
break | ||
|
||
# step 4: update, if the new interval is < previous interval | ||
if local_max - local_min < interval[1] - interval[0]: | ||
interval = local_min, local_max | ||
|
||
# step 5: increment the minimum value pointer | ||
k_pointers[min_index] = k_pointers[min_index] + 1 | ||
|
||
return interval | ||
|
||
``` | ||
|
||
|
||
```python | ||
# let's test it out | ||
k_arrays = [[0, 1, 4, 17, 20, 25, 31], | ||
[5, 6, 10, 13], | ||
[0, 3, 7, 8, 12]] | ||
|
||
smallest_interval(k_arrays) | ||
``` | ||
|
||
|
||
|
||
|
||
(3, 5) | ||
|
||
|
||
|
||
This code runs in O(K * N) time where: | ||
|
||
K = number of lists, | ||
|
||
and N = total number of elements in all the lists. | ||
|
||
The space compexity is O(K), since we are storing a K length array of pointers. | ||
|
||
## Solution 2: Min-Heap | ||
We can use a heap to simplify much of the work in the for loop. | ||
|
||
If we used a heap instead of an array of pointers to track the values we are currently looking at, we would be able to find the local minimum in O(1) time. | ||
|
||
However, we still need to know which list the local minimum is from: for this, we can make use of Python's tuple. | ||
|
||
| ||
|
||
> The min-heap is a heap where the first element is guaranteed to be minimum of all elements in the heap. | ||
Consider a min-heap consisting of tuples holding the following info: | ||
``` | ||
( value, which list it is from, index of the value in that list ) | ||
``` | ||
|
||
We can adapt the algorithm to use the heap as follows: | ||
|
||
1. Initialize the heap of size K, with all the tuples being: (first value of list, the list it is from, index 0). | ||
2. Initialize variables to track the right and left boundaries of the interval | ||
3. Initialize the `local_max` variable to be the max of the first set of values. Since we are using a min-heap, there is no easy way to retrieve the maximum value, so we will need to track it. | ||
4. Pop an element from the top of the heap. The element contains the `local_min`, the list it is from, and index within that list. | ||
5. Compare the new range `(local maximum - local minimum)` and update the current tracked interval if necessary. | ||
6. Increment the `local min`s index, and read the value. | ||
7. If the value is larger than the `local_max`, update the `local_max`. This sets ut up so that the next iteration has an updated version of `local_max`. | ||
8. Create a heap element using the new value, and insert it into the heap. | ||
9. Repeat steps 4-8 until we've exhaused the list. | ||
|
||
|
||
|
||
```python | ||
import heapq | ||
|
||
def smallest_interval(k_arrays): | ||
|
||
# initialize heap, | ||
# each tuple contains (value, the list it is from, value's index) | ||
heap = [(row[0], i, 0) for i, row in enumerate(k_arrays)] | ||
heapq.heapify(heap) | ||
|
||
# initialize local maximum and interval | ||
local_max = max(row[0] for row in k_arrays) | ||
interval = [float('-inf'), float('inf')] | ||
|
||
while heap: | ||
# pop local minimum from the heap | ||
local_min, k_list, min_index = heapq.heappop(heap) | ||
# if the new interval is smaller that tracked interval, update it | ||
if local_max - local_min < interval[1] - interval[0]: | ||
interval = [local_min, local_max] | ||
|
||
# if we've reached the end of the list, break | ||
if min_index == len(k_arrays[k_list]) - 1: | ||
return interval | ||
|
||
# increment the min index and recalculate local max | ||
min_index += 1 | ||
next_val = (k_arrays[k_list][min_index]) | ||
local_max = max(next_val, local_max) | ||
|
||
# push new values into heap | ||
# (next value, list it is from, the value's index in its list) | ||
heapq.heappush(heap, (next_val, k_list, min_index)) | ||
|
||
``` | ||
|
||
|
||
```python | ||
smallest_interval(k_arrays) | ||
``` | ||
|
||
|
||
|
||
|
||
[3, 5] | ||
|
||
|
||
|
||
Popping and pushing an element from the heap takes O(log(N)) time, where N is the number of elements in the heap. | ||
|
||
Since our heap will be maximum size K, and in the worst case, we'll need to iterate for every value in the lists, our total time complexity is O(N log K), where N is the total amount of elements in the lists. | ||
|
||
Our space complexity is O(K), as we are storing at most one element per list in the array. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
### Longest Peak | ||
Write a function that takes in an array and returns the length of the longest peak in the array. | ||
|
||
A peak is defined as adjacent elements that are strictly increasing towards a peak tip, then scrictly decreasing. | ||
|
||
For instance, the array values `1, 2, 5, 3, 0` have a peak, while `10, 0, 4`. Similarly, `1, 2, 3` isn't a peak beacuase there aren't any integers decreasing after 3. | ||
|
||
Note: There can exist more than one peak in the array. Return the longest one. | ||
|
||
Sample input: | ||
``` | ||
[11, 3, 6, 4, 0, 10, 6, 5, -3, -1, 10] | ||
``` | ||
|
||
Sample output: | ||
``` | ||
6 | ||
``` | ||
|
||
|
||
```python | ||
def longest_peak(A): | ||
""" | ||
Complexity: O(1) space, O(n) time, where n is the total number of elements in the array A. | ||
""" | ||
|
||
longest_peak_length = 0 | ||
# start from the second elem in array (i = 1) | ||
i = 1 | ||
while i < len(A) - 1: | ||
# check each element for peakness | ||
peak = array[i - 1] < array[i] and array[i] > array[i + 1] | ||
if not peak: | ||
i += 1 | ||
continue | ||
# spread outwards in left direction to find peak start. | ||
left_index = i - 2 | ||
while left_index >= 0 and A[left_index] < A[left_index + 1]: | ||
left_index -= 1 | ||
# spread outwards in right direction to find peak stop. | ||
right_index = i + 2 | ||
while right_index < len(A) and A[right_index] < A[right_index - 1]: | ||
right_index += 1 | ||
|
||
current_peak_length = right_index - left_index - 1 | ||
longest_peak_length = max(current_peak_length, longest_peak_length) | ||
i = right_index | ||
return longest_peak_length | ||
``` | ||
|
||
|
||
```python | ||
array = [1, 2, 3, 3, 4, 0, 10, 6, 5, -1, -3, 2, 3] | ||
longest_peak(array) | ||
``` | ||
|
||
|
||
|
||
|
||
6 | ||
|
||
|
Oops, something went wrong.