-
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.
new file: Additional Exercises/Easy 938. Range Sum of BST.py
new file: Additional Exercises/Medium 215. Kth Largest Element in an Array.py new file: Additional Exercises/Medium 791. Custom Sort String.py modified: README.md
- Loading branch information
1 parent
2f315dc
commit 96cf247
Showing
4 changed files
with
232 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,67 @@ | ||
from typing import Optional | ||
|
||
# Date of Last Practice: Mar 13, 2024 | ||
# | ||
# Time Complexity: O(N), where N is the number of nodes in the binary search tree (BST). | ||
# In the worst-case scenario, the algorithm might | ||
# need to visit every node in the tree once. | ||
# | ||
# Space Complexity: O(H), where H is the height of the tree. | ||
# This space is used by the call stack | ||
# during the recursive traversal of the tree. | ||
# In the worst-case scenario (a degenerate tree), | ||
# the space complexity can become O(N). | ||
|
||
|
||
class TreeNode: | ||
def __init__(self, val=0, left=None, right=None): | ||
self.val = val | ||
self.left = left | ||
self.right = right | ||
|
||
|
||
class Solution: | ||
def __init__(self): | ||
self.sum = 0 | ||
|
||
def rangeSumBST(self, root: Optional[TreeNode], low: int, high: int) -> int: | ||
# Helper function to perform DFS and sum values within range | ||
def _range_sum(node): | ||
if node is None: # Base case: Node is null | ||
return | ||
if low <= node.val <= high: # Node value is within range | ||
self.sum += node.val | ||
_range_sum(node.left) # Check left subtree | ||
_range_sum(node.right) # Check right subtree | ||
elif node.val < low: # Node value is less than range, skip left subtree | ||
_range_sum(node.right) | ||
elif ( | ||
node.val > high | ||
): # Node value is greater than range, skip right subtree | ||
_range_sum(node.left) | ||
|
||
_range_sum(root) # Initialize DFS from root | ||
return self.sum | ||
|
||
|
||
# Test cases | ||
solution = Solution() | ||
|
||
# Test case 1 | ||
root1 = TreeNode( | ||
10, TreeNode(5, TreeNode(3), TreeNode(7)), TreeNode(15, None, TreeNode(18)) | ||
) | ||
assert solution.rangeSumBST(root1, 7, 15) == 32, "Test case 1 failed" | ||
|
||
# Reset sum for the next test | ||
solution.sum = 0 | ||
|
||
# Test case 2 | ||
root2 = TreeNode( | ||
10, | ||
TreeNode(5, TreeNode(3, TreeNode(1)), TreeNode(7, None, TreeNode(6))), | ||
TreeNode(15, TreeNode(13), TreeNode(18)), | ||
) | ||
assert solution.rangeSumBST(root2, 6, 10) == 23, "Test case 2 failed" | ||
|
||
print("All test cases passed!") |
114 changes: 114 additions & 0 deletions
114
Additional Exercises/Medium 215. Kth Largest Element in an Array.py
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,114 @@ | ||
import heapq | ||
|
||
# Date of Last Practice: Mar 13, 2024 | ||
# | ||
# Time Complexity: O(N * logK), where N is the total elements in nums, and K is the size. | ||
# The main operations involved are heap insertions (heapq.heappush) | ||
# and deletions (heapq.heappop). The solution iterates through | ||
# each of the N elements in the given array, | ||
# performing a heap insertion for each. | ||
# If the heap size exceeds K, it also performs a deletion. | ||
# | ||
# Space Complexity: O(K), where K is the size. | ||
# The solution maintains a heap of size k. | ||
# Therefore, the space complexity is O(k) for storing these elements. | ||
|
||
|
||
class Solution: | ||
def findKthLargest(self, nums, k): | ||
# Step 1 - Initialize an empty heap | ||
heap_list = [] | ||
|
||
# Step 2 - Iterate through all numbers in the array | ||
for index, num in enumerate(nums): | ||
# Step 3 - Maintain a heap of size k | ||
heapq.heappush(heap_list, (num, index)) | ||
if len(heap_list) > k: | ||
heapq.heappop(heap_list) | ||
|
||
# Step 4 - The root of the heap is the kth largest element | ||
kth_largest, _ = heapq.heappop(heap_list) | ||
return kth_largest | ||
|
||
|
||
class QuickSelectSolution: | ||
# Reference: https://www.youtube.com/watch?v=XEmy13g1Qxc | ||
# | ||
# Time Complexity: O(N) in average and (N^2) in the worst case. | ||
# | ||
# In the average case, the partitioning step divides | ||
# the array into two parts that are roughly equal in size. | ||
# This means that each recursive call of _quick_select | ||
# operates on half the size of the array compared to the previous call. | ||
# | ||
# The time complexity can thus be described as: | ||
# | ||
# The first call processes the entire array: O(N). | ||
# The second call processes half of that: O(N/2). | ||
# The third call processes a quarter: O(N/4). | ||
# And so on... | ||
# | ||
# This series continues until the size of the processed array reduces to 1, | ||
# forming a series: O(N) + O(N/2) + O(N/4) + O(N/8) + ... which converges to O(2N) = O(N). | ||
# | ||
# The worst case can happen if the array is already sorted | ||
# (either in ascending or descending order), and | ||
# the pivot chosen is either the smallest or largest element each time. | ||
# | ||
# In such cases, the partitioning does not effectively reduce the problem size, leading to: | ||
# The first call processes the entire array: O(N). | ||
# The second call processes N-1 elements: O(N-1). | ||
# The third call processes N-2 elements: O(N-2). | ||
# And so on until there's only 1 element left. | ||
# | ||
# Summing these up gives a series that approximates O((N+1)*N/2), which simplifies to O(N^2). | ||
|
||
def findKthLargest(self, nums, k): | ||
k = len(nums) - k | ||
|
||
def _quick_select(left, right): | ||
pivot_index, partition_index = right, left | ||
for i in range(left, right): | ||
if nums[i] <= nums[pivot_index]: | ||
nums[partition_index], nums[i] = nums[i], nums[partition_index] | ||
partition_index += 1 | ||
nums[partition_index], nums[pivot_index] = ( | ||
nums[pivot_index], | ||
nums[partition_index], | ||
) | ||
|
||
if partition_index == k: | ||
return nums[partition_index] | ||
elif partition_index > k: | ||
return _quick_select(left, partition_index - 1) | ||
else: | ||
return _quick_select(partition_index + 1, right) | ||
|
||
return _quick_select(0, len(nums) - 1) | ||
|
||
|
||
# Test cases | ||
solution = Solution() | ||
|
||
# Test Case 1 | ||
assert solution.findKthLargest([3, 2, 1, 5, 6, 4], 2) == 5, "Test case 1 failed" | ||
|
||
# Test Case 2 | ||
assert ( | ||
solution.findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4 | ||
), "Test case 2 failed" | ||
|
||
print("All test cases passed!") | ||
|
||
# Test cases | ||
solution = QuickSelectSolution() | ||
|
||
# Test Case 1 | ||
assert solution.findKthLargest([3, 2, 1, 5, 6, 4], 2) == 5, "Test case 1 failed" | ||
|
||
# Test Case 2 | ||
assert ( | ||
solution.findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4 | ||
), "Test case 2 failed" | ||
|
||
print("All test cases passed!") |
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,45 @@ | ||
from collections import Counter | ||
|
||
# Date of Last Practice: Mar 13, 2024 | ||
# | ||
# Time Complexity: O(N+M), where N is the length of the input string s, and M is the length of order. | ||
# The first loop iterates over each character in s, which is O(N). | ||
# The second loop iterates over each character in order, which is O(m). | ||
# Since these steps are sequential and not nested, | ||
# the overall time complexity is O(N+M). | ||
# | ||
# Space Complexity: O(1) (or O(N) if considering the final result list). | ||
# This uses additional space proportional to | ||
# the number of unique characters in s, | ||
# which is at most O(26) if all characters are unique. | ||
|
||
|
||
class Solution: | ||
def customSortString(self, order: str, s: str) -> str: | ||
# Step 1: Count occurrences of each character in s | ||
letter_count = Counter(s) | ||
|
||
result = [] | ||
|
||
# Step 2: Append characters in the order of 'order' | ||
for char in order: | ||
result.append(char * letter_count[char]) | ||
del letter_count[char] | ||
|
||
# Step 3: Append characters not in 'order' | ||
for char in letter_count.keys(): | ||
result.append(char * letter_count[char]) | ||
|
||
return "".join(result) | ||
|
||
|
||
# Test cases | ||
sol = Solution() | ||
assert sol.customSortString("cba", "abcd") == "cbad" | ||
assert sol.customSortString("bcafg", "abcd") == "bcad" | ||
# Testing with characters not in 'order' | ||
assert sol.customSortString("xyz", "abcabc") == "aabbcc" | ||
# Testing with repeating characters in 's' | ||
assert sol.customSortString("cba", "aabbccdd") == "ccbbaadd" | ||
|
||
print("All tests passed!") |
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