diff --git a/contains-duplicate/dolphinflow86.py b/contains-duplicate/dolphinflow86.py new file mode 100644 index 0000000000..c627f241c2 --- /dev/null +++ b/contains-duplicate/dolphinflow86.py @@ -0,0 +1,44 @@ +# First approach: using a set to check for duplicates while iterating through nums +# TC: O(n), where n is the number of elements in nums +# SC: O(n), where n is the number of elements in nums +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + seen = set() + + for num in nums: + # check if the number exists in the set during each iteration + if num in seen: + return True + seen.add(num) + + return False + + +# Second approach: brute-force method using nested for loops to check every pair for duplicates. +# Inefficient time complexity (quadratic time) but has a constant space complexity. +# TC: O(n^2), where n is the number of elements in nums +# SC: O(1) +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + for i in range(len(nums)): + for j in range(i + 1, len(nums)): + if nums[i] == nums[j]: + return True + + return False + + +# Third approach: to decrease time complexity from the brute-force approach, by sorting the array and comparing adjacent elements +# TC: O(n log n), where n is the number of elements in nums +# SC: O(n), where n is the number of elements in nums. Initially I thought Python's sorting algorithm was the same as C++'s IntroSort. +# However, Python uses Timsort uses which requires O(n) space in the worst case +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + nums.sort() + + # Compare adjacent elements + for i in range(len(nums) - 1): + if nums[i] == nums[i + 1]: + return True + + return False diff --git a/house-robber/dolphinflow86.py b/house-robber/dolphinflow86.py new file mode 100644 index 0000000000..3dea4f6a12 --- /dev/null +++ b/house-robber/dolphinflow86.py @@ -0,0 +1,17 @@ +# Solved using a top down dp (memoization) approach. +# TC: O(N) where N = len(nums) - Each house is visited at most once due to caching. +# SC: O(N) where N = len(nums) - Used for the memoization dictionary and recursion call stack. +class Solution: + def rec(self, house: int, nums: List[int], memo: Dict[int,int]) -> int: + if house >= len(nums): return 0 + + if house in memo: return memo[house] + + # max between (robbing current house + skipping next) or (skipping current house) + current_robbed = max(nums[house] + self.rec(house + 2, nums, memo), self.rec(house + 1, nums, memo)) + memo[house] = current_robbed + return current_robbed + + def rob(self, nums: List[int]) -> int: + memo: Dict[int,int] = {} + return self.rec(0, nums, memo) diff --git a/longest-consecutive-sequence/dolphinflow86.py b/longest-consecutive-sequence/dolphinflow86.py new file mode 100644 index 0000000000..2ef2d43127 --- /dev/null +++ b/longest-consecutive-sequence/dolphinflow86.py @@ -0,0 +1,26 @@ +# Calculate the length of the streak from its starting point. +# TC: O(N) where N is the size of nums +# SC: O(N) where N is the size of nums +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + # convert list to set for O(1) lookups + num_set: Set[int] = set(nums) + max_streak = 0 + + for num in num_set: + if num - 1 in num_set: + continue + + # found streak starting point + streak = 0 + cur_num = num + + # extend the streak as long as consecutive numbers exists + while cur_num in num_set: + streak += 1 + cur_num += 1 + + # update max streak + max_streak = max(streak, max_streak) + + return max_streak diff --git a/top-k-frequent-elements/dolphinflow86.py b/top-k-frequent-elements/dolphinflow86.py new file mode 100644 index 0000000000..6c8494b637 --- /dev/null +++ b/top-k-frequent-elements/dolphinflow86.py @@ -0,0 +1,22 @@ +# 1) Using modified bucket sort approach. +# TC: O(N) where N is the length of nums +# SC: O(N) where N is the length of nums +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + freq_map = {} + + bucket_count = len(nums) + for num in nums: + freq_map[num] = freq_map.get(num, 0) + 1 + + buckets = [[] for i in range(bucket_count + 1)] + + for num, freq in freq_map.items(): + buckets[freq].append(num) + + top_list = [] + for i in range(bucket_count, -1, -1): + for num in buckets[i]: + top_list.append(num) + if len(top_list) == k: + return top_list diff --git a/two-sum/dolphinflow86.py b/two-sum/dolphinflow86.py new file mode 100644 index 0000000000..4c863b6ff3 --- /dev/null +++ b/two-sum/dolphinflow86.py @@ -0,0 +1,44 @@ +# 1) Using nested for loop to find every possible combination for target sum +# TC: O(n^2) where n is the size of nums +# SC: O(1) +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + for i in range(len(nums)): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i , j] + + return [] + +#2) Using two pass approach with hash map so that find out complement with index. +# TC: O(2*n) -> O(n) where n is the size of nums +# SC: O(n) where n is the size of nums +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + seen: dict[int, int] = {} + + for i in range(len(nums)): + seen[nums[i]] = i + + for i in range(len(nums)): + complement = target - nums[i] + if complement in seen and i != seen[complement]: + return [seen[complement], i] + + return [] + +# 3) Tiny optimize from two pass version. Instead of inserting separately, insert within one loop +# TC: O(n), where n is the size of nums +# SC: O(n), where n is the size of nums +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + seen: dict[int, int] = {} + + for i in range(len(nums)): + complement = target - nums[i] + if complement in seen: + return [seen[complement], i] + + seen[nums[i]] = i + + return []