diff --git a/Problem_1.py b/Problem_1.py new file mode 100644 index 00000000..a645c0cb --- /dev/null +++ b/Problem_1.py @@ -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() diff --git a/Problem_2.py b/Problem_2.py new file mode 100644 index 00000000..a06ef66b --- /dev/null +++ b/Problem_2.py @@ -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() \ No newline at end of file diff --git a/Problem_3.py b/Problem_3.py new file mode 100644 index 00000000..52eb451f --- /dev/null +++ b/Problem_3.py @@ -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-- + 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() \ No newline at end of file