Skip to content
Open
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
123 changes: 123 additions & 0 deletions Problem_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'''
42 Trapping Rain Water
https://leetcode.com/problems/trapping-rain-water/description/

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

Example 1:
Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

Example 2:
Input: height = [4,2,0,3,2,5]
Output: 9

Constraints:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105

Solution:
1. Prefix and Suffix array (sub-optimal)
Compute a running max of height from start of the array (index = 0) to current index i, where 0<=i<=N-1. Save the max values in L. Thus, L[i] = max value of height from index 0 to index i. This is the prefix array.

Compute a running max of height from end of the array (index = N-1) to current index i, where 0<=i<=N-1. Save the max values in R. Thus, R[i] = max value of height from index N-1 to index i. This is the suffix array.

Now compute the total water in bin i using min(L(i), R(i)) - height[i]. Calculate this for all bins and add up.
Time: O(N), Space: O(N)

2. Two pointers (optimal)
This technique reduces the O(N) space used in the prefix-suffix array approach to O(1) space. For this, we use two pointers from both ends and keep track of the tallest walls on each side. At each step, we process the side with the smaller wall since that determines water trapping. We add trapped water if current height is less than the known max wall on that side.
https://www.youtube.com/watch?v=aslfns1i0yY
Time: O(N), Space: O(1)


3. Two pointers (optimal)
Same as the Two pointers approach in #2. Code here is a bit easier to follow.
https://youtu.be/UHHp8USwx4M?t=1051
Good explanation: https://youtu.be/UHHp8USwx4M?t=1335
Time: O(N), Space: O(1)

'''
from typing import List
def trap_PrefixSuffix(height: List[int]) -> int:
N = len(height)
L = [0]*(N) # L[i] = max value of height from index 0 to index i
R = [0]*(N) # R[i] = max value of height from index N-1 to index i

L[0] = height[0]
R[N-1] = height[N-1]
for i in range(1, N): # O(N)
L[i] = max(L[i-1], height[i])
R[N-i-1] = max(R[N-i], height[N-i-1])

area = 0
for i in range(N): # O(N)
this = min(L[i], R[i]) - height[i]
area += this
return area

def trap_TwoPointer2(height: List[int]) -> int:
N = len(height)
lw, l = 0, 0
rw, r = 0, N-1
result = 0
while l <= r:
if lw <= rw:
if lw >= height[l]:
result += lw - height[l]
else:
lw = height[l]
l += 1
else:
if rw >= height[r]:
result += rw - height[r]
else:
rw = height[r]
r -= 1
return result

def trap_TwoPointer3(height: List[int]) -> int:
N = len(height)
# init max height on the left and right to 0
# since we can assume that the max ht to the left of height[0]
# is 0. Likewise, max ht to the right of height[N-1] is 0
lmax, rmax = 0, 0
# two pointers
l, r = 0, N-1
# area of trapped water
result = 0
while l <= r:
lmax = max(lmax, height[l])
rmax = max(rmax, height[r])
if lmax <= rmax:
result += max(lmax - height[l], 0)
l += 1
else:
result += max(rmax - height[r], 0)
r -= 1
return result

def run_trap():
tests = [([0,1,0,2,1,0,1,3,2,1,2,1], 6),
([4,2,0,3,2,5], 9),
([1,2,3,4], 0),]
for test in tests:
height, ans = test[0], test[1]
print(f"\nheight = {height}")
for method in ['Prefix-Suffix', 'Two-Pointer2', 'Two-Pointer3']:
if method == "Prefix-Suffix":
area = trap_PrefixSuffix(height)
elif method == "Two-Pointer2":
area = trap_TwoPointer2(height)
elif method == "Two-Pointer3":
area = trap_TwoPointer3(height)
print(f"Method {method}: Area of trapped water = {area}")
success = (ans == area)
print(f"Pass: {success}")
if not success:
print("Failed")
return

run_trap()
95 changes: 95 additions & 0 deletions Problem_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'''
189 Rotate Array
https://leetcode.com/problems/rotate-array/description/

Given an integer array nums, rotate the array to the right by k steps, where k is non-negative. Do not return anything, modify nums in-place instead.

Example 1:
Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2:
Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation:
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]


Constraints:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105

Follow up:
Try to come up with as many solutions as you can. There are at least three different ways to solve this problem.
Could you do it in-place with O(1) extra space?

Solution:
1. Brute Force
Take the last element in the array and place it at the first index. This takes O(N). Repeat this step k-1 more times shifting the element from the last index and placing it at the first index. Thus, O(N) + O(N) + ... k times = O(Nk)
Time: O(Nk), Space: O(1)

2. Split and merge
Let array indices are 0, ..., N-(k+1), N-k, ..., N-2, N-1.
Split the array after the N-(k+1)th element into two partitions. Then we have in the first half and k elements in the second half.
first half = [0, ..., N-(k+1)] (N-k elements)
second half = [N-k, ..., N-2, N-1] (k elements)
Now merge the two arrays by going through the second half and then the first half.
Time: O(N), Space: O(N)

3. Reverse and merge
Similar to #2, split the array after the N-(k+1)th element into two partitions.
first half = [0, ..., N-(k+1)] (N-k elements)
second half = [N-k, ..., N-2, N-1] (k elements)
Reverse the first partition, reverse the second partition, then reverse the entire array.
https://youtu.be/aslfns1i0yY?t=2500
Time: O(N), Space: O(1)
'''

from typing import List

def rotate(nums: List[int], k: int) -> None:
def reverse(nums, left, right):
while left <= right:
nums[right], nums[left] = nums[left], nums[right]
left += 1
right -= 1

if not nums or k == 0:
return nums
N = len(nums)
k = k % N # handle cases where k > N

# Step 1: reverse 1st partition
reverse(nums, 0, N-k-1)
# Step 2: reverse 2nd partition
reverse(nums, N-k, N-1)
# Step 3: reverse the entire array
reverse(nums, 0, N-1)

# Note: Step 1 and Step 2 are interchangeable. It will not change the
# result. But Step 3 should always be done at the last.

def run_rotate():
tests = [([1,2,3,4,5,6,7], 3, [5,6,7,1,2,3,4]),
([-1,-100,3,99], 2, [3,99,-1,-100]),
]
for test in tests:
nums, k, ans = test[0], test[1], test[2]
print(f"\nnums = {nums}")
print(f"k = {k}")
nums_rotated = nums.copy()
rotate(nums_rotated, k)
print(f"rotated nums = {nums_rotated}")
success = (ans == nums_rotated)
print(f"Pass: {success}")
if not success:
print("Failed")
return

run_rotate()
176 changes: 176 additions & 0 deletions Problem_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
'''
274 H-Index
https://leetcode.com/problems/h-index/description/

Given an array of integers citations where citations[i] is the number of citations a researcher received for their ith paper, return the researcher's h-index.

According to the definition of h-index on Wikipedia: The h-index is defined as the maximum value of h such that the given researcher has published at least h papers that have each been cited at least h times.

Example 1:
Input: citations = [3,0,6,1,5]
Output: 3
Explanation: [3,0,6,1,5] means the researcher has 5 papers in total and each of them had received 3, 0, 6, 1, 5 citations respectively.
Since the researcher has 3 papers with at least 3 citations each and the remaining two with no more than 3 citations each, their h-index is 3.

Example 2:
Input: citations = [1,3,1]
Output: 1

Constraints:
n == citations.length
1 <= n <= 5000
0 <= citations[i] <= 1000

Solution:
Formally, if f is the function that corresponds to the number of citations for each publication, we compute the h-index as follows: First we order the values of f from the largest to the lowest value. Then, we look for the last position in which f is greater than or equal to the position (we call h this position). For example, if we have a researcher with 5 publications A, B, C, D, and E with 10, 8, 5, 4, and 3 citations, respectively, the h-index is equal to 4 because the 4th publication has 4 citations and the 5th has only 3. However, if the same publications have 25, 8, 5, 3, and 3 citations, then the index is 3 (i.e. the 3rd position) because the fourth paper has only 3 citations.

f(A)=10, f(B)=8, f(C)=5, f(D)=4, f(E)=3 → h-index=4
f(A)=25, f(B)=8, f(C)=5, f(D)=3, f(E)=3 → h-index=3

Thus, if we have the function f ordered in decreasing order from the largest value to the lowest one, we can compute the h-index as follows:
Formula 1: h-index (f) = max {i+1 ∈ N : f (i) ≥ i+1}, i = 0-indexed

Example 1:
f = [6, 5, 3, 1, 0]
i = [0, 1, 2, 3, 4]
i+1 = [1, 2, 3, 4, 5]
h-index (f) = max {1,2,3} = 3

Example 2:
f = [10, 8, 5, 4, 3]
i = [0, 1, 2, 3, 4]
i+1 = [1, 2, 3, 4, 5]
h-index (f) = max {1,2,3,4} = 4


If we have the function f ordered in increasing order from the smallest value to the largest one, we can compute the h-index as follows:
Formula 2: h-index (f) = max {N-i, i ∈ N : f (i) ≥ N-i}
= N - min {i, i ∈ N : f (i) ≥ N-i}

Example 1:
f = [0, 1, 3, 5, 6]
i = [0, 1, 2, 3, 4]
N-i = [5, 4, 3, 2, 1]
h-index (f) = max {3,2,1} = 3

Thus,
f = [0, 1, 3, 5, 6]
--f(i)<N-i- | --f(i)>=N-i--
h-index

Example 2:
f = [3, 4, 5, 8, 10]
i = [0, 1, 2, 3, 4]
N-i = [5, 4, 3, 2, 1]
h-index (f) = max {4,3,2,1} = 4

1. Brute Force
N papers can have a min h-index = 0 and max h-index = N. Hence, possible values of hindex = [0, N] for N papers. Hence, for each posible value of hindex (call it i), count the number of papers whose citations >= i. If this condition is saistsfied, i becomes a potential hindex value. Since we would like to get the max possible value of i, we continue incrementing i and count the no. of papers whose citations >= i.
https://youtu.be/M8AX7QvCtsg?t=3075
Time: O(N^2), Space: O(1)


2. Sort and linear search
Sort the array in increasing order. Traverse the sorted array from left to right. If the ith element of array is f[i], then we are looking for the first instance when f[i] >= N-i. When that happens, return N-i.
https://youtu.be/M8AX7QvCtsg?t=3237
Time: O(N log N), Space: O(1)

3. Count Sort
For N papers, possible values of hindex = [0,1,2,...,N] (N+1 values)
Create a bucket (array) of size N+1. The indices of the bucket are the possible values of hindex.

Now for each citation count (of N papers), use the citation count as the index of the bucket and add 1 to the value of the array at that index. That is,
if citation < N, then bucket[citation] += 1
if citation >= N, then bucket[N] += 1

After filling the bucket, start from the end of the bucket (j = N). set sum = bucket[j]. If sum >= j, then hindex = j. This is because 'sum' is the no. of papers having at least j citations. By defn, hindex = h means there are h papers having at least h citations. Hence, coming back to our bucket array, there are 'sum' papers having at least j citations. Thus, if sum >= j, then hindex = j.

If sum < j, decrease j by 1 and increase sum to sum = sum + bucket[j]. The first j where sum >= j is the hindex.

This is also known as count sort.
https://youtu.be/M8AX7QvCtsg?t=3727
https://youtu.be/mgG5KFTvfPw?t=274 (easy to understand)
Time: O(N), Space: O(N)

'''
def hIndex_1(citations):
''' Time: O(N^2), Space: O(1) '''
if not citations:
return 0
N = len(citations)
hindex = 0
for i in range(N+1): # O(N)
count = 0
for num in citations: # O(N)
if num >= i:
count += 1
if count >= i:
hindex = max(hindex, i)
return hindex

def hIndex_2(citations):
'''Time: O(N log N), Space: O(1)'''
if not citations:
return 0
N = len(citations)

# sort by increasing order of citations
# citations = sorted(citations)
# for i in range(N):
# if citations[i] >= N-i:
# return N-i
# return 0

# sort by decreasing order of citations (more natural than increasing order)
citations.sort(reverse=True)
N = len(citations)
h = 0
for i in range(N):
if citations[i] >= i+1:
h += 1
return h

def hIndex_3(citations):
'''Time: O(N), Space: O(N)'''
if not citations:
return 0
N = len(citations)
buckets = [0]*(N+1)
for c in citations: # O(N)
if c >=N:
buckets[N] += 1
else:
buckets[c] += 1

# buckets[c] = n means there are n papers
# which have received c citations

num_papers = 0
h = 0
for ncite in range(N,-1,-1):
num_papers += buckets[ncite]
if num_papers >= ncite:
h = ncite
break
return h

def run_hIndex():
tests = [([3,0,6,1,5], 3), ([1,3,1],1), ([0,0,0,0,0],0)]
for test in tests:
citations, ans = test[0], test[1]
print(f"\ncitations = {citations}")
for method in ['brute-force','sort-search', 'bucket-sort']:
if method == 'brute-force':
hIndex = hIndex_1(citations)
elif method == 'sort-search':
hIndex = hIndex_2(citations)
elif method == 'bucket-sort':
hIndex = hIndex_3(citations)
print(f"Method {method}: hIndex = {hIndex}")
success = (ans == hIndex)
print(f"Pass: {success}")
if not success:
print("Failed")
return

run_hIndex()