├── .gitignore ├── 13. Pattern Top 'K' Elements ├── README.md ├── Top 'K' Numbers (easy).py ├── Frequency Sort (medium).py ├── Kth Largest Number in a Stream (medium).py ├── Top 'K' Frequent Numbers (medium).py ├── Connect Ropes (easy).py ├── Rearrange String (hard).py ├── 'K' Closest Points to the Origin (easy).py ├── Kth Smallest Number (easy).py ├── Problem Challenge 1 - Rearrange String K Distance Apart (hard).py └── Problem Challenge 3 - Frequency Stack (hard) .py ├── cyclicsort.png ├── mergeintervals.png ├── 14. Pattern K-way merge ├── README.md ├── Smallest Number Range (Hard).py ├── Problem Challenge 1 - K Pairs with Largest Sums (Hard).py ├── Kth Smallest Number in M Sorted Lists (Medium).py └── Merge K Sorted Lists (medium).py ├── 12. Bitwise XOR ├── Single Number (easy).py ├── Two Single Numbers (medium).py ├── Problem Challenge 1.py └── Complement of Base 10 Number (medium).py ├── 02. Pattern Two Pointers ├── Dutch National Flag Problem (medium).py ├── Squaring a Sorted Array (easy).py ├── Triplets with Smaller Sum (medium).py ├── Subarrays with Product Less than a Target (medium).py ├── Remove Duplicates (easy).py ├── Triplet Sum to Zero (medium).py ├── Triplet Sum Close to Target (medium).py ├── Problem Challenge 3 - Minimum Window Sort (medium).py ├── Pair with Target Sum (easy).py ├── Problem Challenge 2 - Comparing Strings containing Backspaces (medium).py └── Problem Challenge 1 - Quadruple Sum to Target (medium) .py ├── 05. Pattern Cyclic Sort ├── Find all Duplicate Numbers (easy).py ├── Problem Challenge 2 - Find the Smallest Missing Positive Number (medium).py ├── Problem Challenge 1 - Find the Corrupt Pair (easy).py ├── Find the Missing Number (easy).py ├── Cyclic Sort (easy).py ├── Find all Missing Numbers (easy).py ├── Problem Challenge 3 - Find the First K Missing Positive Numbers (hard).py └── Find the Duplicate Number (easy).py ├── 03. Pattern Fast & Slow pointers ├── Middle of the LinkedList (easy).py ├── Happy Number (medium).py ├── LinkedList Cycle (easy).py └── Problem Challenge 3 - Cycle in a Circular Array (hard).py ├── 15. Pattern 01 Knapsack (Dynamic Programming) └── Subset Sum (medium).py ├── 01. Pattern Sliding Window ├── Maximum Sum Subarray of Size K (easy).py ├── Longest Subarray with Ones after Replacement (hard).py ├── Smallest Subarray with a given sum (easy).py ├── Longest Substring with K Distinct Characters (medium).py ├── No-repeat Substring (hard).py ├── Longest Substring with Same Letters after Replacement (hard).py ├── Problem Challenge 4 - Words Concatenation (hard) .py ├── Fruits into Baskets (medium).py └── Problem Challenge 2 - String Anagrams (hard) .py ├── 10. Pattern Subsets ├── Subsets (easy).py ├── String Permutations by changing case (medium).py ├── Subsets With Duplicates (easy).py └── Problem Challenge 3 - Count of Structurally Unique Binary Search Trees (hard).py ├── 11. Pattern Modified Binary Search ├── Bitonic Array Maximum (easy).py ├── Problem Challenge 3 - Rotation Count (medium).py ├── Minimum Difference Element (medium).py ├── Number Range (medium) .py ├── Next Letter (medium).py ├── Order-agnostic Binary Search (easy).py └── Problem Challenge 1 - Search Bitonic Array (medium).py ├── 08. Pattern Tree Depth First Search ├── Binary Tree Path Sum (easy).py ├── Sum of Path Numbers (medium).py ├── Path With Given Sequence (medium).py └── Count Paths for a Sum (medium).py ├── 06. Pattern In-place Reversal of a LinkedList ├── Reverse a LinkedList (easy).py └── Problem Challenge 2 - Rotate a LinkedList (medium).py ├── 04. Pattern Merge Intervals └── Conflicting Appointments (medium).py ├── 07. Pattern Breath Depth First Search ├── Binary Tree Level Order Traversal (easy).py ├── Level Averages in a Binary Tree (easy).py ├── Level Order Successor (easy).py ├── Reverse Level Order Traversal (easy).py ├── Problem Challenge 1 - Connect All Level Order Siblings (medium).py └── Problem Challenge 2 - Right View of a Binary Tree (easy).py └── 09. Pattern Two Heaps └── Find the Median of a Number Stream (medium).py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 13: Top 'K' Elements 2 | -------------------------------------------------------------------------------- /cyclicsort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishsuper/Grokking-the-Coding-Interview/HEAD/cyclicsort.png -------------------------------------------------------------------------------- /mergeintervals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishsuper/Grokking-the-Coding-Interview/HEAD/mergeintervals.png -------------------------------------------------------------------------------- /14. Pattern K-way merge/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 14: K-way merge 2 | 3 | The K-way Merge pattern looks like this; 4 | - We can push the smallest (first) element of each sorted array in a Min Heap to get the overall minimum. 5 | - After this step, we can take out the smallest (top) element from the heap and then add it to the merged list. 6 | - After removing the smallest element from the heap, insert the next element of the same list into the heap. 7 | - We can repeat steps 2 and 3 to populate the merged list in sorted order. 8 | 9 | For this pattern, 10 | Time Complexity = O(N log K) where N is the total number of elements in all the K input arrays. 11 | Space Complexity = O(K) 12 | ## Ways to identify this pattern: 13 | - For this kind of pattern, the problem will feature sorted arrays, lists, or a matrix 14 | - The problem will ask us to merge sorted lists, find the smallest element in a sorted list. 15 | -------------------------------------------------------------------------------- /12. Bitwise XOR/Single Number (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | In a non-empty array of integers, every number appears twice except for one, find that single number. 4 | 5 | Example 1: 6 | 7 | Input: 1, 4, 2, 1, 3, 2, 3 8 | Output: 4 9 | Example 2: 10 | 11 | Input: 7, 9, 7 12 | Output: 9 13 | ''' 14 | 15 | #mycode 16 | def find_single_number(arr): 17 | # TODO: Write your code here 18 | s=0 19 | 20 | for i in arr: 21 | s=s^i 22 | return s 23 | 24 | def main(): 25 | arr = [1, 4, 2, 1, 3, 2, 3] 26 | print(find_single_number(arr)) 27 | 28 | main() 29 | 30 | 31 | #answer 32 | def find_single_number(arr): 33 | num = 0 34 | for i in arr: 35 | num ^= i 36 | return num 37 | 38 | def main(): 39 | arr = [1, 4, 2, 1, 3, 2, 3] 40 | print(find_single_number(arr)) 41 | 42 | main() 43 | 44 | 45 | ''' 46 | Time Complexity: 47 | Time complexity of this solution is O(n) as we iterate through all numbers of the input once. 48 | 49 | Space Complexity: 50 | The algorithm runs in constant space O(1). 51 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Dutch National Flag Problem (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array containing 0s, 1s and 2s, sort the array in-place. You should treat numbers of the array as objects, 4 | hence, we can’t count 0s, 1s, and 2s to recreate the array. 5 | 6 | The flag of the Netherlands consists of three colors: red, white and blue; 7 | and since our input array also consists of three different numbers that is why it is called Dutch National Flag problem. 8 | 9 | Example 1: 10 | 11 | Input: [1, 0, 2, 1, 0] 12 | Output: [0 0 1 1 2] 13 | 14 | Example 2: 15 | 16 | Input: [2, 2, 0, 1, 2, 0] 17 | Output: [0 0 1 2 2 2 ] 18 | ''' 19 | 20 | 21 | 22 | #mycode 23 | def dutch_flag_sort(arr): 24 | # TODO: Write your code here 25 | left, i = 0, 0 26 | right = len(arr)-1 27 | 28 | while i <= right: 29 | if arr[i] == 0: 30 | arr[i], arr[left] = arr[left], arr[i] 31 | left += 1 32 | i += 1 33 | elif arr[i] == 2: 34 | arr[i], arr[right] = arr[right], arr[i] 35 | right -= 1 36 | else: 37 | i += 1 38 | 39 | return 40 | 41 | 42 | 43 | 44 | 45 | ''' 46 | Time complexity 47 | The time complexity of the above algorithm will be O(N) as we are iterating the input array only once. 48 | 49 | Space complexity # 50 | The algorithm runs in constant space O(1). 51 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Find all Duplicate Numbers (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | We are given an unsorted array containing ‘n’ numbers taken from the range 1 to ‘n’. 4 | The array has some duplicates, find all the duplicate numbers without using any extra space. 5 | 6 | Example 1: 7 | 8 | Input: [3, 4, 4, 5, 5] 9 | Output: [4, 5] 10 | 11 | Example 2: 12 | 13 | Input: [5, 4, 7, 2, 3, 5, 3] 14 | Output: [3, 5] 15 | ''' 16 | 17 | 18 | #mycode 19 | def find_all_duplicates(nums): 20 | duplicateNumbers = [] 21 | # TODO: Write your code here 22 | for i in range(len(nums)): 23 | j=abs(nums[i])-1 24 | if nums[j] > 0: 25 | nums[j]=-nums[j] 26 | else: 27 | duplicateNumbers.append(j+1) 28 | return duplicateNumbers 29 | 30 | 31 | 32 | #answer 33 | def find_all_duplicates(nums): 34 | i = 0 35 | while i < len(nums): 36 | j = nums[i] - 1 37 | if nums[i] != nums[j]: 38 | nums[i], nums[j] = nums[j], nums[i] # swap 39 | else: 40 | i += 1 41 | 42 | duplicateNumbers = [] 43 | for i in range(len(nums)): 44 | if nums[i] != i + 1: 45 | duplicateNumbers.append(nums[i]) 46 | 47 | return duplicateNumbers 48 | 49 | 50 | def main(): 51 | print(find_all_duplicates([3, 4, 4, 5, 5])) 52 | print(find_all_duplicates([5, 4, 7, 2, 3, 5, 3])) 53 | 54 | 55 | main() 56 | 57 | 58 | ''' 59 | Time complexity 60 | The time complexity of the above algorithm is O(n). 61 | 62 | Space complexity 63 | Ignoring the space required for storing the duplicates, the algorithm runs in constant space O(1). 64 | ''' -------------------------------------------------------------------------------- /03. Pattern Fast & Slow pointers/Middle of the LinkedList (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given the head of a Singly LinkedList, write a method to return the middle node of the LinkedList. 4 | 5 | If the total number of nodes in the LinkedList is even, return the second middle node. 6 | 7 | Example 1: 8 | 9 | Input: 1 -> 2 -> 3 -> 4 -> 5 -> null 10 | Output: 3 11 | 12 | Example 2: 13 | 14 | Input: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null 15 | Output: 4 16 | 17 | Example 3: 18 | 19 | Input: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> null 20 | Output: 4 21 | ''' 22 | 23 | 24 | class Node: 25 | def __init__(self, value, next=None): 26 | self.value = value 27 | self.next = next 28 | 29 | 30 | def find_middle_of_linked_list(head): 31 | # TODO: Write your code here 32 | slow, fast = head, head 33 | while fast is not None and fast.next is not None: 34 | fast = fast.next.next 35 | slow = slow.next 36 | return slow 37 | 38 | 39 | def main(): 40 | head = Node(1) 41 | head.next = Node(2) 42 | head.next.next = Node(3) 43 | head.next.next.next = Node(4) 44 | head.next.next.next.next = Node(5) 45 | 46 | print("Middle Node: " + str(find_middle_of_linked_list(head).value)) 47 | 48 | head.next.next.next.next.next = Node(6) 49 | print("Middle Node: " + str(find_middle_of_linked_list(head).value)) 50 | 51 | head.next.next.next.next.next.next = Node(7) 52 | print("Middle Node: " + str(find_middle_of_linked_list(head).value)) 53 | 54 | 55 | main() 56 | 57 | 58 | ''' 59 | Time complexity 60 | The above algorithm will have a time complexity of O(N) where ‘N’ is the number of nodes in the LinkedList. 61 | 62 | Space complexity 63 | The algorithm runs in constant space O(1). 64 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Squaring a Sorted Array (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a sorted array, create a new array containing squares of all the number of the input array in the sorted order. 4 | 5 | Example 1: 6 | 7 | Input: [-2, -1, 0, 2, 3] 8 | Output: [0, 1, 4, 4, 9] 9 | 10 | Example 2: 11 | 12 | Input: [-3, -1, 0, 1, 2] 13 | Output: [0 1 1 4 9] 14 | ''' 15 | 16 | #mycode 17 | def make_squares(arr): 18 | squares = [0] * len(arr) 19 | # TODO: Write your code here 20 | i , j = 0, len(arr)-1 21 | k = len(arr)-1 22 | while i <= j: 23 | if arr[i]**2 >= arr[j]**2: 24 | squares[k] = arr[i]**2 25 | i += 1 26 | k -= 1 27 | else: 28 | squares[k] = arr[j]**2 29 | j -= 1 30 | k -= 1 31 | 32 | return squares 33 | 34 | 35 | #answer 36 | def make_squares(arr): 37 | n = len(arr) 38 | squares = [0 for x in range(n)] 39 | highestSquareIdx = n - 1 40 | left, right = 0, n - 1 41 | while left <= right: 42 | leftSquare = arr[left] * arr[left] 43 | rightSquare = arr[right] * arr[right] 44 | if leftSquare > rightSquare: 45 | squares[highestSquareIdx] = leftSquare 46 | left += 1 47 | else: 48 | squares[highestSquareIdx] = rightSquare 49 | right -= 1 50 | highestSquareIdx -= 1 51 | 52 | return squares 53 | 54 | 55 | def main(): 56 | 57 | print("Squares: " + str(make_squares([-2, -1, 0, 2, 3]))) 58 | print("Squares: " + str(make_squares([-3, -1, 0, 1, 2]))) 59 | 60 | 61 | main() 62 | 63 | 64 | ''' 65 | Time complexity 66 | The time complexity of the above algorithm will be O(N) as we are iterating the input array only once. 67 | 68 | Space complexity 69 | The space complexity of the above algorithm will also be O(N); this space will be used for the output array. 70 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Problem Challenge 2 - Find the Smallest Missing Positive Number (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 2 3 | Find the Smallest Missing Positive Number (medium) 4 | 5 | Given an unsorted array containing numbers, find the smallest missing positive number in it. 6 | 7 | Example 1: 8 | 9 | Input: [-3, 1, 5, 4, 2] 10 | Output: 3 11 | Explanation: The smallest missing positive number is '3' 12 | 13 | Example 2: 14 | 15 | Input: [3, -2, 0, 1, 2] 16 | Output: 4 17 | 18 | Example 3: 19 | 20 | Input: [3, 2, 5, 1] 21 | Output: 4 22 | ''' 23 | 24 | #mycode 25 | def find_first_missing_positive(nums): 26 | # TODO: Write your code here 27 | i=0 28 | while i < len(nums): 29 | j=nums[i]-1 30 | if j >= 0 and j < len(nums): 31 | if nums[i] != nums[j]: 32 | nums[i], nums[j] = nums[j], nums[i] 33 | else: 34 | i+=1 35 | else: 36 | i+=1 37 | 38 | for i in range(len(nums)): 39 | if nums[i]-1 != i: 40 | return i+1 41 | 42 | 43 | #answer 44 | def find_first_missing_positive(nums): 45 | i, n = 0, len(nums) 46 | while i < n: 47 | j = nums[i] - 1 48 | if nums[i] > 0 and nums[i] <= n and nums[i] != nums[j]: 49 | nums[i], nums[j] = nums[j], nums[i] # swap 50 | else: 51 | i += 1 52 | 53 | for i in range(n): 54 | if nums[i] != i + 1: 55 | return i + 1 56 | 57 | return nums.length + 1 58 | 59 | 60 | def main(): 61 | print(find_first_missing_positive([-3, 1, 5, 4, 2])) 62 | print(find_first_missing_positive([3, -2, 0, 1, 2])) 63 | print(find_first_missing_positive([3, 2, 5, 1])) 64 | 65 | 66 | main() 67 | 68 | 69 | ''' 70 | Time complexity 71 | The time complexity of the above algorithm is O(n). 72 | 73 | Space complexity 74 | The algorithm runs in constant space O(1). 75 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Problem Challenge 1 - Find the Corrupt Pair (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | 4 | Find the Corrupt Pair (easy) 5 | 6 | We are given an unsorted array containing ‘n’ numbers taken from the range 1 to ‘n’. The array originally contained all the numbers from 1 to ‘n’, but due to a data error, one of the numbers got duplicated which also resulted in one number going missing. Find both these numbers. 7 | 8 | Example 1: 9 | 10 | Input: [3, 1, 2, 5, 2] 11 | Output: [2, 4] 12 | Explanation: '2' is duplicated and '4' is missing. 13 | 14 | Example 2: 15 | 16 | Input: [3, 1, 2, 3, 6, 4] 17 | Output: [3, 5] 18 | Explanation: '3' is duplicated and '5' is missing. 19 | ''' 20 | 21 | 22 | #mycode 23 | def find_corrupt_numbers(nums): 24 | # TODO: Write your code here 25 | duplicate, missing = 0, 0 26 | i=0 27 | 28 | while i < len(nums): 29 | j=nums[i]-1 30 | if i != j: 31 | if nums[i] != nums[j]: 32 | nums[i], nums[j] = nums[j], nums[i] 33 | else: 34 | duplicate=nums[i] 35 | i += 1 36 | else: 37 | i += 1 38 | 39 | for i in range(len(nums)): 40 | if nums[i]-1 != i: 41 | missing= i+1 42 | return [duplicate, missing] 43 | 44 | 45 | #answer 46 | def find_corrupt_numbers(nums): 47 | i = 0 48 | while i < len(nums): 49 | j = nums[i] - 1 50 | if nums[i] != nums[j]: 51 | nums[i], nums[j] = nums[j], nums[i] # swap 52 | else: 53 | i += 1 54 | 55 | for i in range(len(nums)): 56 | if nums[i] != i + 1: 57 | return [nums[i], i + 1] 58 | 59 | return [-1, -1] 60 | 61 | 62 | def main(): 63 | print(find_corrupt_numbers([3, 1, 2, 5, 2])) 64 | print(find_corrupt_numbers([3, 1, 2, 3, 6, 4])) 65 | 66 | 67 | main() 68 | 69 | 70 | ''' 71 | Time complexity 72 | The time complexity of the above algorithm is O(n). 73 | 74 | Space complexity 75 | The algorithm runs in constant space O(1). 76 | ''' -------------------------------------------------------------------------------- /03. Pattern Fast & Slow pointers/Happy Number (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Any number will be called a happy number if, 4 | after repeatedly replacing it with a number equal to the sum of the square of all of its digits, 5 | leads us to number ‘1’. All other (not-happy) numbers will never reach ‘1’. 6 | Instead, they will be stuck in a cycle of numbers which does not include ‘1’. 7 | ''' 8 | 9 | #mycode 10 | def find_happy_number(num): 11 | # TODO: Write your code here 12 | fast, slow = num, num 13 | while True: 14 | fast = square(square(fast)) 15 | slow = square(slow) 16 | 17 | if fast == slow: 18 | break 19 | 20 | return slow == 1 21 | 22 | def square(num): 23 | square_num = 0 24 | while num > 0: 25 | square_num += (num % 10) ** 2 26 | num = num // 10 27 | return square_num 28 | 29 | 30 | def main(): 31 | print(find_happy_number(23)) 32 | print(find_happy_number(12)) 33 | 34 | 35 | main() 36 | 37 | 38 | ''' 39 | Time Complexity # 40 | The time complexity of the algorithm is difficult to determine. 41 | However we know the fact that all unhappy numbers eventually get stuck in the cycle: 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 42 | 43 | This sequence behavior tells us two things: 44 | 1. If the number NN is less than or equal to 1000, then we reach the cycle or ‘1’ in at most 1001 steps. 45 | 2. For N > 1000, suppose the number has ‘M’ digits and the next number is ‘N1’. 46 | From the above Wikipedia link, we know that the sum of the squares of the digits of ‘N’ is at most 9^2 M, or 81M 47 | (this will happen when all digits of ‘N’ are ‘9’). 48 | 49 | This means: 50 | 1. N1 < 81M 51 | 2. As we know M = log(N+1)M=log(N+1) 52 | 3. Therefore: N1 < 81 * log(N+1)N1<81∗log(N+1) => N1 = O(logN)N1=O(logN) 53 | This concludes that the above algorithm will have a time complexity of O(logN)O(logN). 54 | 55 | Space Complexity 56 | The algorithm runs in constant space O(1)O(1). 57 | ''' -------------------------------------------------------------------------------- /15. Pattern 01 Knapsack (Dynamic Programming)/Subset Sum (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a set of positive numbers, determine if a subset exists whose sum is equal to a given number ‘S’. 4 | 5 | Example 1: 6 | Input: {1, 2, 3, 7}, S=6 7 | Output: True 8 | The given set has a subset whose sum is '6': {1, 2, 3} 9 | 10 | Example 2: 11 | Input: {1, 2, 7, 1, 5}, S=10 12 | Output: True 13 | The given set has a subset whose sum is '10': {1, 2, 7} 14 | 15 | Example 3: 16 | Input: {1, 3, 4, 8}, S=6 17 | Output: False 18 | The given set does not have any subset whose sum is equal to '6'. 19 | ''' 20 | 21 | 22 | def can_partition(num, sum): 23 | n = len(num) 24 | dp = [False for x in range(sum+1)] 25 | 26 | # handle sum=0, as we can always have '0' sum with an empty set 27 | dp[0] = True 28 | 29 | # with only one number, we can have a subset only when the required sum is equal to its value 30 | for s in range(1, sum+1): 31 | dp[s] = num[0] == s 32 | 33 | # process all subsets for all sums 34 | for i in range(1, n): 35 | for s in range(sum, -1, -1): 36 | # if dp[s]==true, this means we can get the sum 's' without num[i], hence we can move on to 37 | # the next number else we can include num[i] and see if we can find a subset to get the 38 | # remaining sum 39 | if not dp[s] and s >= num[i]: 40 | dp[s] = dp[s - num[i]] 41 | 42 | return dp[sum] 43 | 44 | 45 | def main(): 46 | print("Can partition: " + str(can_partition([1, 2, 3, 7], 6))) 47 | print("Can partition: " + str(can_partition([1, 2, 7, 1, 5], 10))) 48 | print("Can partition: " + str(can_partition([1, 3, 4, 8], 6))) 49 | 50 | 51 | main() 52 | 53 | 54 | ''' 55 | Time and Space complexity 56 | The above solution has the time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the required sum. 57 | 58 | Challenge 59 | Can we improve our bottom-up DP solution even further? Can you find an algorithm that has O(S) space complexity? 60 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Find the Missing Number (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | We are given an array containing ‘n’ distinct numbers taken from the range 0 to ‘n’. 4 | Since the array has only ‘n’ numbers out of the total ‘n+1’ numbers, find the missing number. 5 | 6 | Example 1: 7 | 8 | Input: [4, 0, 3, 1] 9 | Output: 2 10 | 11 | Example 2: 12 | 13 | Input: [8, 3, 5, 2, 4, 6, 0, 1] 14 | Output: 7 15 | ''' 16 | 17 | #mycode 18 | def find_missing_number(nums): 19 | # TODO: Write your code here 20 | for i in range(len(nums)): 21 | if abs(nums[i]) < len(nums): 22 | nums[abs(nums[i])] = -nums[abs(nums[i])] 23 | 24 | for i in range(len(nums)): 25 | if nums[i] > 0: 26 | return i 27 | return len(nums) 28 | 29 | #answer 30 | def find_missing_number(nums): 31 | i, n = 0, len(nums) 32 | while i < n: 33 | j = nums[i] 34 | if nums[i] < n and nums[i] != nums[j]: 35 | nums[i], nums[j] = nums[j], nums[i] # swap 36 | else: 37 | i += 1 38 | 39 | # find the first number missing from its index, that will be our required number 40 | for i in range(n): 41 | if nums[i] != i: 42 | return i 43 | 44 | return n 45 | 46 | 47 | def main(): 48 | print(find_missing_number([4, 0, 3, 1])) 49 | print(find_missing_number([8, 3, 5, 2, 4, 6, 0, 1])) 50 | 51 | 52 | main() 53 | 54 | 55 | 56 | ''' 57 | Time complexity 58 | The time complexity of the above algorithm is O(n). 59 | In the while loop, although we are not incrementing the index i when swapping the numbers, 60 | this will result in more than ‘n’ iterations of the loop, 61 | but in the worst-case scenario, the while loop will swap a total of ‘n-1’ numbers and once a number is at its correct index, 62 | we will move on to the next number by incrementing i. 63 | In the end, we iterate the input array again to find the first number missing from its index, 64 | so overall, our algorithm will take O(n) + O(n-1) + O(n) which is asymptotically equivalent to O(n). 65 | 66 | Space complexity 67 | The algorithm runs in constant space O(1). 68 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Maximum Sum Subarray of Size K (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement # 3 | Given an array of positive numbers and a positive number ‘k’, find the maximum sum of any contiguous subarray of size ‘k’. 4 | 5 | Example 1: 6 | 7 | Input: [2, 1, 5, 1, 3, 2], k=3 8 | Output: 9 9 | Explanation: Subarray with maximum sum is [5, 1, 3]. 10 | 11 | Example 2: 12 | 13 | Input: [2, 3, 4, 1, 5], k=2 14 | Output: 7 15 | Explanation: Subarray with maximum sum is [3, 4]. 16 | ''' 17 | 18 | #mycode 19 | def max_sub_array_of_size_k(k, arr): 20 | # TODO: Write your code here 21 | max_sum , window_sum = 0, 0 22 | window_start = 0 23 | 24 | for window_end in range(len(arr)): 25 | window_sum += arr[window_end] # add the next element 26 | # slide the window, we don't need to slide if we've not hit the required window size of 'k' 27 | if window_end >= k-1: 28 | max_sum = max(max_sum, window_sum) 29 | window_sum -= arr[window_start] # subtract the element going out 30 | window_start += 1 # slide the window ahead 31 | return max_sum 32 | 33 | #answer 34 | def max_sub_array_of_size_k(k, arr): 35 | max_sum , window_sum = 0, 0 36 | window_start = 0 37 | 38 | for window_end in range(len(arr)): 39 | window_sum += arr[window_end] # add the next element 40 | # slide the window, we don't need to slide if we've not hit the required window size of 'k' 41 | if window_end >= k-1: 42 | max_sum = max(max_sum, window_sum) 43 | window_sum -= arr[window_start] # subtract the element going out 44 | window_start += 1 # slide the window ahead 45 | return max_sum 46 | 47 | 48 | def main(): 49 | print("Maximum sum of a subarray of size K: " + str(max_sub_array_of_size_k(3, [2, 1, 5, 1, 3, 2]))) 50 | print("Maximum sum of a subarray of size K: " + str(max_sub_array_of_size_k(2, [2, 3, 4, 1, 5]))) 51 | 52 | main() 53 | 54 | 55 | ''' 56 | Time Complexity 57 | The time complexity of the above algorithm will be O(N). 58 | 59 | Space Complexity 60 | The algorithm runs in constant space O(1). 61 | ''' 62 | 63 | -------------------------------------------------------------------------------- /10. Pattern Subsets/Subsets (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a set with distinct elements, find all of its distinct subsets. 4 | 5 | Example 1: 6 | 7 | Input: [1, 3] 8 | Output: [], [1], [3], [1,3] 9 | Example 2: 10 | 11 | Input: [1, 5, 3] 12 | Output: [], [1], [5], [3], [1,5], [1,3], [5,3], [1,5,3] 13 | ''' 14 | 15 | 16 | #mycode 17 | def find_subsets(nums): 18 | subsets = [] 19 | subsets.append([]) 20 | 21 | for currentNumber in nums: 22 | for i in range(len(subsets)): 23 | j = subsets[i].copy() 24 | j.append(currentNumber) 25 | subsets.append(j) 26 | 27 | return subsets 28 | 29 | 30 | def main(): 31 | 32 | print("Here is the list of subsets: " + str(find_subsets([1, 3]))) 33 | print("Here is the list of subsets: " + str(find_subsets([1, 5, 3]))) 34 | 35 | 36 | main() 37 | 38 | 39 | #answer 40 | def find_subsets(nums): 41 | subsets = [] 42 | # start by adding the empty subset 43 | subsets.append([]) 44 | for currentNumber in nums: 45 | # we will take all existing subsets and insert the current number in them to create new subsets 46 | n = len(subsets) 47 | for i in range(n): 48 | # create a new subset from the existing subset and insert the current element to it 49 | set = subsets[i].copy() 50 | set.append(currentNumber) 51 | subsets.append(set) 52 | 53 | return subsets 54 | 55 | 56 | def main(): 57 | 58 | print("Here is the list of subsets: " + str(find_subsets([1, 3]))) 59 | print("Here is the list of subsets: " + str(find_subsets([1, 5, 3]))) 60 | 61 | 62 | main() 63 | 64 | 65 | ''' 66 | Time complexity 67 | Since, in each step, the number of subsets doubles as we add each element to all the existing subsets, the time complexity of the above algorithm is O(2^N), 68 | where ‘N’ is the total number of elements in the input set. This also means that, in the end, we will have a total of O(2^N) subsets. 69 | 70 | Space complexity 71 | All the additional space used by our algorithm is for the output list. Since we will have a total of O(2^N) subsets, 72 | the space complexity of our algorithm is also O(2^N). 73 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Cyclic Sort (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | We are given an array containing ‘n’ objects. Each object, when created, was assigned a unique number from 1 to ‘n’ based on their creation sequence. 4 | This means that the object with sequence number ‘3’ was created just before the object with sequence number ‘4’. 5 | 6 | Write a function to sort the objects in-place on their creation sequence number in O(n) and without any extra space. 7 | For simplicity, let’s assume we are passed an integer array containing only the sequence numbers, though each number is actually an object. 8 | 9 | Example 1: 10 | 11 | Input: [3, 1, 5, 4, 2] 12 | Output: [1, 2, 3, 4, 5] 13 | 14 | Example 2: 15 | 16 | Input: [2, 6, 4, 3, 1, 5] 17 | Output: [1, 2, 3, 4, 5, 6] 18 | 19 | Example 3: 20 | 21 | Input: [1, 5, 6, 4, 3, 2] 22 | Output: [1, 2, 3, 4, 5, 6] 23 | ''' 24 | 25 | #mycode 26 | def cyclic_sort(nums): 27 | # TODO: Write your code here 28 | i=0 29 | while i < len(nums): 30 | j=nums[i]-1 31 | if i != j: 32 | nums[i], nums[j] = nums[j], nums[i] 33 | else: 34 | i += 1 35 | return nums 36 | 37 | 38 | #answer 39 | def cyclic_sort(nums): 40 | i = 0 41 | while i < len(nums): 42 | j = nums[i] - 1 43 | if nums[i] != nums[j]: 44 | nums[i], nums[j] = nums[j], nums[i] # swap 45 | else: 46 | i += 1 47 | return nums 48 | 49 | 50 | def main(): 51 | print(cyclic_sort([3, 1, 5, 4, 2])) 52 | print(cyclic_sort([2, 6, 4, 3, 1, 5])) 53 | print(cyclic_sort([1, 5, 6, 4, 3, 2])) 54 | 55 | 56 | main() 57 | 58 | 59 | ''' 60 | Time complexity 61 | The time complexity of the above algorithm is O(n). 62 | Although we are not incrementing the index i when swapping the numbers, this will result in more than ‘n’ iterations of the loop, 63 | but in the worst-case scenario, the while loop will swap a total of ‘n-1’ numbers and once a number is at its correct index, 64 | we will move on to the next number by incrementing i. So overall, our algorithm will take O(n) + O(n-1) which is asymptotically equivalent to O(n). 65 | 66 | Space complexity 67 | The algorithm runs in constant space O(1). 68 | ''' 69 | -------------------------------------------------------------------------------- /12. Bitwise XOR/Two Single Numbers (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | In a non-empty array of numbers, every number appears exactly twice except two numbers that appear only once. Find the two numbers that appear only once. 4 | 5 | Example 1: 6 | 7 | Input: [1, 4, 2, 1, 3, 5, 6, 2, 3, 5] 8 | Output: [4, 6] 9 | Example 2: 10 | 11 | Input: [2, 1, 3, 2] 12 | Output: [1, 3] 13 | ''' 14 | 15 | #mycode 16 | def find_single_numbers(nums): 17 | # TODO: Write your code here 18 | sum = 0 19 | for num in nums: 20 | sum ^= num 21 | 22 | diff = 1 23 | while (diff & sum == 0): 24 | diff = diff << 1 25 | 26 | num1, num2 = 0, 0 27 | 28 | for num in nums: 29 | if num & diff ==0: 30 | num1 ^= num 31 | else: 32 | num2 ^= num 33 | 34 | 35 | return [num1, num2] 36 | 37 | 38 | def main(): 39 | print('Single numbers are:' + 40 | str(find_single_numbers([1, 4, 2, 1, 3, 5, 6, 2, 3, 5]))) 41 | print('Single numbers are:' + str(find_single_numbers([2, 1, 3, 2]))) 42 | 43 | main() 44 | 45 | 46 | ''' 47 | First for loop is to find num1 ^ num2. The first right one of num1^num2 marks the difference of the two numbers, 48 | and the third loop is to divide the array into two sets and find the single number respectively. 49 | ''' 50 | 51 | 52 | 53 | #answer 54 | def find_single_numbers(nums): 55 | # get the XOR of the all the numbers 56 | n1xn2 = 0 57 | for num in nums: 58 | n1xn2 ^= num 59 | 60 | # get the rightmost bit that is '1' 61 | rightmost_set_bit = 1 62 | while (rightmost_set_bit & n1xn2) == 0: 63 | rightmost_set_bit = rightmost_set_bit << 1 64 | num1, num2 = 0, 0 65 | 66 | for num in nums: 67 | if (num & rightmost_set_bit) != 0: # the bit is set 68 | num1 ^= num 69 | else: # the bit is not set 70 | num2 ^= num 71 | 72 | return [num1, num2] 73 | 74 | 75 | def main(): 76 | print('Single numbers are:' + 77 | str(find_single_numbers([1, 4, 2, 1, 3, 5, 6, 2, 3, 5]))) 78 | print('Single numbers are:' + str(find_single_numbers([2, 1, 3, 2]))) 79 | 80 | 81 | main() 82 | 83 | 84 | 85 | ''' 86 | Time Complexity 87 | The time complexity of this solution is O(n) where ‘n’ is the number of elements in the input array. 88 | 89 | Space Complexity 90 | The algorithm runs in constant space O(1). 91 | ''' -------------------------------------------------------------------------------- /12. Bitwise XOR/Problem Challenge 1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | 4 | Given a binary matrix representing an image, we want to flip the image horizontally, then invert it. 5 | 6 | To flip an image horizontally means that each row of the image is reversed. For example, flipping [0, 1, 1] horizontally results in [1, 1, 0]. 7 | 8 | To invert an image means that each 0 is replaced by 1, and each 1 is replaced by 0. For example, inverting [1, 1, 0] results in [0, 0, 1]. 9 | 10 | Example 1: 11 | 12 | Input: [ 13 | [1,0,1], 14 | [1,1,1], 15 | [0,1,1] 16 | ] 17 | Output: [ 18 | [0,1,0], 19 | [0,0,0], 20 | [0,0,1] 21 | ] 22 | Explanation: First reverse each row: [[1,0,1],[1,1,1],[1,1,0]]. Then, invert the image: [[0,1,0],[0,0,0],[0,0,1]] 23 | 24 | Example 2: 25 | 26 | Input: [ 27 | [1,1,0,0], 28 | [1,0,0,1], 29 | [0,1,1,1], 30 | [1,0,1,0] 31 | ] 32 | Output: [ 33 | [1,1,0,0], 34 | [0,1,1,0], 35 | [0,0,0,1], 36 | [1,0,1,0] 37 | ] 38 | Explanation: First reverse each row: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]]. Then invert the image: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] 39 | ''' 40 | 41 | 42 | #mycode 43 | def flip_and_invert_image(matrix): 44 | #TODO: Write your code here. 45 | 46 | 47 | 48 | for i in range(len(matrix)): 49 | start, end = 0, len(matrix[i])-1 50 | while start <= end: 51 | temp = matrix[i][end]^1 52 | matrix[i][end] = matrix[i][start]^1 53 | matrix[i][start] = temp 54 | end -=1 55 | start +=1 56 | 57 | return matrix 58 | 59 | def main(): 60 | print(flip_and_invert_image([[1,0,1], [1,1,1], [0,1,1]])) 61 | print(flip_and_invert_image([[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]])) 62 | 63 | main() 64 | 65 | 66 | 67 | #answer 68 | def flip_an_invert_image(matrix): 69 | C = len(matrix) 70 | for row in matrix: 71 | for i in range((C+1)//2): 72 | row[i], row[C - i - 1] = row[C - i - 1] ^ 1, row[i] ^ 1 73 | 74 | return matrix 75 | 76 | def main(): 77 | print(flip_an_invert_image([[1,0,1], [1,1,1], [0,1,1]])) 78 | print(flip_an_invert_image([[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]])) 79 | 80 | main() 81 | 82 | 83 | 84 | ''' 85 | Time Complexity 86 | The time complexity of this solution is O(n) as we iterate through all elements of the input. 87 | 88 | Space Complexity 89 | The space complexity of this solution is O(1). 90 | ''' 91 | -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Top 'K' Numbers (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an unsorted array of numbers, find the ‘K’ largest numbers in it. 4 | 5 | Note: For a detailed discussion about different approaches to solve this problem, take a look at Kth Smallest Number. 6 | 7 | Example 1: 8 | 9 | Input: [3, 1, 5, 12, 2, 11], K = 3 10 | Output: [5, 12, 11] 11 | Example 2: 12 | 13 | Input: [5, 12, 11, -1, 12], K = 3 14 | Output: [12, 11, 12] 15 | ''' 16 | 17 | 18 | #mycode 19 | from heapq import * 20 | 21 | 22 | def find_k_largest_numbers(nums, k): 23 | result = [] 24 | # TODO: Write your code here 25 | 26 | for num in nums: 27 | if len(result) < k: 28 | heappush(result, num) 29 | else: 30 | if num > result[0]: 31 | heappop(result) 32 | heappush(result, num) 33 | 34 | return result 35 | 36 | 37 | def main(): 38 | 39 | print("Here are the top K numbers: " + 40 | str(find_k_largest_numbers([3, 1, 5, 12, 2, 11], 3))) 41 | 42 | print("Here are the top K numbers: " + 43 | str(find_k_largest_numbers([5, 12, 11, -1, 12], 3))) 44 | 45 | 46 | main() 47 | 48 | 49 | 50 | #answer 51 | from heapq import * 52 | 53 | 54 | def find_k_largest_numbers(nums, k): 55 | minHeap = [] 56 | # put first 'K' numbers in the min heap 57 | for i in range(k): 58 | heappush(minHeap, nums[i]) 59 | 60 | # go through the remaining numbers of the array, if the number from the array is bigger than the 61 | # top(smallest) number of the min-heap, remove the top number from heap and add the number from array 62 | for i in range(k, len(nums)): 63 | if nums[i] > minHeap[0]: 64 | heappop(minHeap) 65 | heappush(minHeap, nums[i]) 66 | 67 | # the heap has the top 'K' numbers, return them in a list 68 | return list(minHeap) 69 | 70 | 71 | def main(): 72 | 73 | print("Here are the top K numbers: " + 74 | str(find_k_largest_numbers([3, 1, 5, 12, 2, 11], 3))) 75 | 76 | print("Here are the top K numbers: " + 77 | str(find_k_largest_numbers([5, 12, 11, -1, 12], 3))) 78 | 79 | 80 | main() 81 | 82 | 83 | 84 | ''' 85 | Time complexity 86 | As discussed above, the time complexity of this algorithm is O(K*logK+(N-K)*logK), which is asymptotically equal to O(N*logK) 87 | 88 | Space complexity 89 | The space complexity will be O(K) since we need to store the top ‘K’ numbers in the heap. 90 | ''' -------------------------------------------------------------------------------- /12. Bitwise XOR/Complement of Base 10 Number (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Every non-negative integer N has a binary representation, for example, 8 can be represented as “1000” in binary and 7 as “0111” in binary. 4 | 5 | The complement of a binary representation is the number in binary that we get when we change every 1 to a 0 and every 0 to a 1. For example, the binary complement of “1010” is “0101”. 6 | 7 | For a given positive number N in base-10, return the complement of its binary representation as a base-10 integer. 8 | 9 | Example 1: 10 | 11 | Input: 8 12 | Output: 7 13 | Explanation: 8 is 1000 in binary, its complement is 0111 in binary, which is 7 in base-10. 14 | Example 2: 15 | 16 | Input: 10 17 | Output: 5 18 | Explanation: 10 is 1010 in binary, its complement is 0101 in binary, which is 5 in base-10. 19 | ''' 20 | 21 | #mycode 22 | def calculate_bitwise_complement(n): 23 | #TODO: Write your code here 24 | count, num = 0, n 25 | while num>0: 26 | num = num>>1 27 | count += 1 28 | s = 2 ** count -1 29 | 30 | return n^s 31 | 32 | def main(): 33 | print('Bitwise complement is: ' + str(calculate_bitwise_complement(8))) 34 | print('Bitwise complement is: ' + str(calculate_bitwise_complement(10))) 35 | 36 | main() 37 | 38 | 39 | #answer 40 | def calculate_bitwise_complement(num): 41 | # count number of total bits in 'num' 42 | bit_count, n = 0, num 43 | while n > 0: 44 | bit_count += 1 45 | n = n >> 1 46 | 47 | # for a number which is a complete power of '2' i.e., it can be written as pow(2, n), if we 48 | # subtract '1' from such a number, we get a number which has 'n' least significant bits set to '1'. 49 | # For example, '4' which is a complete power of '2', and '3' (which is one less than 4) has a binary 50 | # representation of '11' i.e., it has '2' least significant bits set to '1' 51 | all_bits_set = pow(2, bit_count) - 1 52 | 53 | # from the solution description: complement = number ^ all_bits_set 54 | return num ^ all_bits_set 55 | 56 | 57 | print('Bitwise complement is: ' + str(calculate_bitwise_complement(8))) 58 | print('Bitwise complement is: ' + str(calculate_bitwise_complement(10))) 59 | 60 | 61 | 62 | ''' 63 | Time Complexity 64 | Time complexity of this solution is O(b)O where ‘b’ is the number of bits required to store the given number. 65 | 66 | Space Complexity 67 | Space complexity of this solution is O(1). 68 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Bitonic Array Maximum (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Find the maximum value in a given Bitonic array. An array is considered bitonic if it is monotonically increasing and then monotonically decreasing. Monotonically increasing or decreasing means that for any index i in the array arr[i] != arr[i+1]. 4 | 5 | Example 1: 6 | 7 | Input: [1, 3, 8, 12, 4, 2] 8 | Output: 12 9 | Explanation: The maximum number in the input bitonic array is '12'. 10 | 11 | Example 2: 12 | 13 | Input: [3, 8, 3, 1] 14 | Output: 8 15 | 16 | Example 3: 17 | 18 | Input: [1, 3, 8, 12] 19 | Output: 12 20 | 21 | Example 4: 22 | 23 | Input: [10, 9, 8] 24 | Output: 10 25 | ''' 26 | 27 | 28 | #mycode 29 | def find_max_in_bitonic_array(arr): 30 | # TODO: Write your code here 31 | start, end = 0, len(arr)-1 32 | while start <= end: 33 | mid = (start + end) //2 34 | if mid == len(arr)-1: 35 | return arr[mid] 36 | if mid < len(arr)-1 and arr[mid] < arr[mid+1]: 37 | start = mid + 1 38 | else: 39 | if mid-1 >= 0 and arr[mid] > arr[mid-1]: 40 | return arr[mid] 41 | elif mid == 0: 42 | return arr[mid] 43 | else: 44 | end = mid-1 45 | 46 | 47 | def main(): 48 | print(find_max_in_bitonic_array([1, 3, 8, 12, 4, 2])) 49 | print(find_max_in_bitonic_array([3, 8, 3, 1])) 50 | print(find_max_in_bitonic_array([1, 3, 8, 12])) 51 | print(find_max_in_bitonic_array([10, 9, 8])) 52 | 53 | 54 | main() 55 | 56 | 57 | 58 | #answer 59 | def find_max_in_bitonic_array(arr): 60 | start, end = 0, len(arr) - 1 61 | while start < end: 62 | mid = start + (end - start) // 2 63 | if arr[mid] > arr[mid + 1]: 64 | end = mid 65 | else: 66 | start = mid + 1 67 | 68 | # at the end of the while loop, 'start == end' 69 | return arr[start] 70 | 71 | 72 | def main(): 73 | print(find_max_in_bitonic_array([1, 3, 8, 12, 4, 2])) 74 | print(find_max_in_bitonic_array([3, 8, 3, 1])) 75 | print(find_max_in_bitonic_array([1, 3, 8, 12])) 76 | print(find_max_in_bitonic_array([10, 9, 8])) 77 | 78 | 79 | main() 80 | 81 | 82 | 83 | ''' 84 | Time complexity 85 | Since we are reducing the search range by half at every step, 86 | this means that the time complexity of our algorithm will be O(logN) 87 | where ‘N’ is the total elements in the given array. 88 | 89 | Space complexity 90 | The algorithm runs in constant space O(1). 91 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Triplets with Smaller Sum (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array arr of unsorted numbers and a target sum, count all triplets in it such that arr[i] + arr[j] + arr[k] < target where i, j, and k are three different indices. Write a function to return the count of such triplets. 4 | 5 | Example 1: 6 | 7 | Input: [-1, 0, 2, 3], target=3 8 | Output: 2 9 | Explanation: There are two triplets whose sum is less than the target: [-1, 0, 3], [-1, 0, 2] 10 | 11 | Example 2: 12 | 13 | Input: [-1, 4, 2, 1, 3], target=5 14 | Output: 4 15 | Explanation: There are four triplets whose sum is less than the target: 16 | [-1, 1, 4], [-1, 1, 3], [-1, 1, 2], [-1, 2, 3] 17 | ''' 18 | 19 | #mycode 20 | def triplet_with_smaller_sum(arr, target): 21 | count = 0 22 | # TODO: Write your code here 23 | arr.sort() 24 | 25 | for i in range(len(arr)): 26 | j, k = i+1, len(arr)-1 27 | while j < k: 28 | if arr[i] + arr[j] + arr[k] < target: 29 | count += (k-j) 30 | j += 1 31 | else: 32 | k -= 1 33 | 34 | return count 35 | 36 | 37 | 38 | #answer 39 | def triplet_with_smaller_sum(arr, target): 40 | arr.sort() 41 | count = 0 42 | for i in range(len(arr)-2): 43 | count += search_pair(arr, target - arr[i], i) 44 | return count 45 | 46 | 47 | def search_pair(arr, target_sum, first): 48 | count = 0 49 | left, right = first + 1, len(arr) - 1 50 | while (left < right): 51 | if arr[left] + arr[right] < target_sum: # found the triplet 52 | # since arr[right] >= arr[left], therefore, we can replace arr[right] by any number between 53 | # left and right to get a sum less than the target sum 54 | count += right - left 55 | left += 1 56 | else: 57 | right -= 1 # we need a pair with a smaller sum 58 | return count 59 | 60 | 61 | def main(): 62 | print(triplet_with_smaller_sum([-1, 0, 2, 3], 3)) 63 | print(triplet_with_smaller_sum([-1, 4, 2, 1, 3], 5)) 64 | 65 | 66 | main() 67 | 68 | 69 | ''' 70 | Time complexity 71 | Sorting the array will take O(N * logN). The searchPair() will take O(N). 72 | So, overall searchTriplets() will take O(N * logN + N^2), which is asymptotically equivalent to O(N^2). 73 | 74 | Space complexity 75 | Ignoring the space required for the output array, 76 | the space complexity of the above algorithm will be O(N) which is required for sorting if we are not using an in-place sorting algorithm. 77 | ''' -------------------------------------------------------------------------------- /10. Pattern Subsets/String Permutations by changing case (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a string, find all of its permutations preserving the character sequence but changing case. 4 | 5 | Example 1: 6 | 7 | Input: "ad52" 8 | Output: "ad52", "Ad52", "aD52", "AD52" 9 | 10 | Example 2: 11 | 12 | Input: "ab7c" 13 | Output: "ab7c", "Ab7c", "aB7c", "AB7c", "ab7C", "Ab7C", "aB7C", "AB7C" 14 | ''' 15 | 16 | #mycode 17 | def find_letter_case_string_permutations(str): 18 | 19 | # TODO: Write your code here 20 | permutations = [] 21 | permutations.append(str) 22 | 23 | for i in range(len(str)): 24 | if str[i].isalpha(): 25 | for j in range(len(permutations)): 26 | newPermutation = list(permutations[j]) 27 | newPermutation[i] = newPermutation[i].swapcase() 28 | permutations.append(''.join(newPermutation)) 29 | 30 | return permutations 31 | 32 | 33 | def main(): 34 | print("String permutations are: " + 35 | str(find_letter_case_string_permutations("ad52"))) 36 | print("String permutations are: " + 37 | str(find_letter_case_string_permutations("ab7c"))) 38 | 39 | 40 | main() 41 | 42 | 43 | #answer 44 | def find_letter_case_string_permutations(str): 45 | permutations = [] 46 | permutations.append(str) 47 | # process every character of the string one by one 48 | for i in range(len(str)): 49 | if str[i].isalpha(): # only process characters, skip digits 50 | # we will take all existing permutations and change the letter case appropriately 51 | n = len(permutations) 52 | for j in range(n): 53 | chs = list(permutations[j]) 54 | # if the current character is in upper case, change it to lower case or vice versa 55 | chs[i] = chs[i].swapcase() 56 | permutations.append(''.join(chs)) 57 | 58 | return permutations 59 | 60 | 61 | def main(): 62 | print("String permutations are: " + 63 | str(find_letter_case_string_permutations("ad52"))) 64 | print("String permutations are: " + 65 | str(find_letter_case_string_permutations("ab7c"))) 66 | 67 | 68 | main() 69 | 70 | 71 | 72 | ''' 73 | Time complexity 74 | Since we can have 2^N permutations at the most and while processing each permutation we convert it into a character array, 75 | the overall time complexity of the algorithm will be O(N*2^N). 76 | 77 | Space complexity 78 | All the additional space used by our algorithm is for the output list. 79 | Since we can have a total of O(2^N) permutations, the space complexity of our algorithm is O(N*2^N). 80 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Longest Subarray with Ones after Replacement (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array containing 0s and 1s, if you are allowed to replace no more than ‘k’ 0s with 1s, find the length of the longest contiguous subarray having all 1s. 4 | 5 | Example 1: 6 | 7 | Input: Array=[0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1], k=2 8 | Output: 6 9 | Explanation: Replace the '0' at index 5 and 8 to have the longest contiguous subarray of 1s having length 6. 10 | 11 | Example 2: 12 | 13 | Input: Array=[0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1], k=3 14 | Output: 9 15 | Explanation: Replace the '0' at index 6, 9, and 10 to have the longest contiguous subarray of 1s having length 9. 16 | ''' 17 | 18 | #mycode 19 | def length_of_longest_substring(arr, k): 20 | win_start, max_len, cnt = 0, 0, 0 21 | zero_posi=[0] * (k+1) 22 | 23 | for win_end in range(len(arr)): 24 | if arr[win_end] == 0: 25 | cnt +=1 26 | zero_posi[cnt-1]=win_end 27 | 28 | if cnt > k: 29 | win_start=zero_posi[0]+1 30 | zero_posi=zero_posi[1:]+[0] 31 | cnt -=1 32 | 33 | 34 | max_len=max(max_len, win_end-win_start+1) 35 | 36 | return max_len 37 | 38 | 39 | #answer 40 | def length_of_longest_substring(arr, k): 41 | window_start, max_length, max_ones_count = 0, 0, 0 42 | 43 | # Try to extend the range [window_start, window_end] 44 | for window_end in range(len(arr)): 45 | if arr[window_end] == 1: 46 | max_ones_count += 1 47 | 48 | # Current window size is from window_start to window_end, overall we have a maximum of 1s 49 | # repeating 'max_ones_count' times, this means we can have a window with 'max_ones_count' 1s 50 | # and the remaining are 0s which should replace with 1s. 51 | # now, if the remaining 1s are more than 'k', it is the time to shrink the window as we 52 | # are not allowed to replace more than 'k' 0s 53 | if (window_end - window_start + 1 - max_ones_count) > k: 54 | if arr[window_start] == 1: 55 | max_ones_count -= 1 56 | window_start += 1 57 | 58 | max_length = max(max_length, window_end - window_start + 1) 59 | return max_length 60 | 61 | 62 | def main(): 63 | print(length_of_longest_substring([0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1], 2)) 64 | print(length_of_longest_substring( 65 | [0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1], 3)) 66 | 67 | 68 | main() 69 | 70 | 71 | ''' 72 | Time Complexity 73 | The time complexity of the above algorithm will be O(N) where ‘N’ is the count of numbers in the input array. 74 | 75 | Space Complexity 76 | The algorithm runs in constant space O(1). 77 | ''' 78 | -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Subarrays with Product Less than a Target (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array with positive numbers and a target number, 4 | find all of its contiguous subarrays whose product is less than the target number. 5 | 6 | Example 1: 7 | 8 | Input: [2, 5, 3, 10], target=30 9 | Output: [2], [5], [2, 5], [3], [5, 3], [10] 10 | Explanation: There are six contiguous subarrays whose product is less than the target. 11 | Example 2: 12 | 13 | Input: [8, 2, 6, 5], target=50 14 | Output: [8], [2], [8, 2], [6], [2, 6], [5], [6, 5] 15 | Explanation: There are seven contiguous subarrays whose product is less than the target. 16 | ''' 17 | 18 | #mycode 19 | def find_subarrays(arr, target): 20 | result = [] 21 | product = 1 22 | win_start=0 23 | 24 | for win_end in range(len(arr)): 25 | 26 | product *= arr[win_end] 27 | print(product) 28 | 29 | while product >= target and win_start < len(arr): 30 | product /= arr[win_start] 31 | win_start += 1 32 | 33 | if product < target: 34 | temp_i=[] 35 | for i in range(win_end, win_start-1,-1): 36 | temp_i.append(arr[i]) 37 | temp=temp_i.copy() 38 | result.append(temp) 39 | 40 | return result 41 | 42 | 43 | 44 | #answer 45 | from collections import deque 46 | 47 | 48 | def find_subarrays(arr, target): 49 | result = [] 50 | product = 1 51 | left = 0 52 | for right in range(len(arr)): 53 | product *= arr[right] 54 | while (product >= target and left < len(arr)): 55 | product /= arr[left] 56 | left += 1 57 | # since the product of all numbers from left to right is less than the target therefore, 58 | # all subarrays from lef to right will have a product less than the target too; to avoid 59 | # duplicates, we will start with a subarray containing only arr[right] and then extend it 60 | temp_list = deque() 61 | for i in range(right, left-1, -1): 62 | temp_list.appendleft(arr[i]) 63 | result.append(list(temp_list)) 64 | return result 65 | 66 | 67 | def main(): 68 | print(find_subarrays([2, 5, 3, 10], 30)) 69 | print(find_subarrays([8, 2, 6, 5], 50)) 70 | 71 | 72 | main() 73 | 74 | 75 | ''' 76 | Time complexity 77 | The main for-loop managing the sliding window takes O(N)but creating subarrays can take up to O(N^2) in the worst case. 78 | Therefore overall, our algorithm will take O(N^3). 79 | 80 | Space complexity 81 | Ignoring the space required for the output list, the algorithm runs in O(N) space which is used for the temp list. 82 | At the most, we need a space of O(n^2) for all the output lists. 83 | ''' -------------------------------------------------------------------------------- /08. Pattern Tree Depth First Search/Binary Tree Path Sum (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree and a number ‘S’, 4 | find if the tree has a path from root-to-leaf such that the sum of all the node values of that path equals ‘S’. 5 | ''' 6 | 7 | 8 | #mycode 9 | class TreeNode: 10 | def __init__(self, val, left=None, right=None): 11 | self.val = val 12 | self.left = left 13 | self.right = right 14 | 15 | 16 | def has_path(root, sum): 17 | # TODO: Write your code here 18 | if not root: 19 | return False 20 | 21 | if root.val == sum and root.left is None and root.right is None: 22 | return True 23 | 24 | return has_path(root.left, sum - root.val) or has_path(root.right, sum - root.val) 25 | 26 | 27 | def main(): 28 | 29 | root = TreeNode(12) 30 | root.left = TreeNode(7) 31 | root.right = TreeNode(1) 32 | root.left.left = TreeNode(9) 33 | root.right.left = TreeNode(10) 34 | root.right.right = TreeNode(5) 35 | print("Tree has path: " + str(has_path(root, 23))) 36 | print("Tree has path: " + str(has_path(root, 16))) 37 | 38 | 39 | main() 40 | 41 | 42 | #answer 43 | class TreeNode: 44 | def __init__(self, val, left=None, right=None): 45 | self.val = val 46 | self.left = left 47 | self.right = right 48 | 49 | 50 | def has_path(root, sum): 51 | if root is None: 52 | return False 53 | 54 | # if the current node is a leaf and its value is equal to the sum, we've found a path 55 | if root.val == sum and root.left is None and root.right is None: 56 | return True 57 | 58 | # recursively call to traverse the left and right sub-tree 59 | # return true if any of the two recursive call return true 60 | return has_path(root.left, sum - root.val) or has_path(root.right, sum - root.val) 61 | 62 | 63 | def main(): 64 | 65 | root = TreeNode(12) 66 | root.left = TreeNode(7) 67 | root.right = TreeNode(1) 68 | root.left.left = TreeNode(9) 69 | root.right.left = TreeNode(10) 70 | root.right.right = TreeNode(5) 71 | print("Tree has path: " + str(has_path(root, 23))) 72 | print("Tree has path: " + str(has_path(root, 16))) 73 | 74 | 75 | main() 76 | 77 | 78 | ''' 79 | Time complexity 80 | The time complexity of the above algorithm is O(N)O, where ‘N’ is the total number of nodes in the tree. 81 | This is due to the fact that we traverse each node once. 82 | 83 | Space complexity 84 | The space complexity of the above algorithm will be O(N) in the worst case. 85 | This space will be used to store the recursion stack. 86 | The worst case will happen when the given tree is a linked list (i.e., every node has only one child). 87 | ''' -------------------------------------------------------------------------------- /10. Pattern Subsets/Subsets With Duplicates (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a set of numbers that might contain duplicates, find all of its distinct subsets. 4 | 5 | Example 1: 6 | 7 | Input: [1, 3, 3] 8 | Output: [], [1], [3], [1,3], [3,3], [1,3,3] 9 | 10 | Example 2: 11 | 12 | Input: [1, 5, 3, 3] 13 | Output: [], [1], [5], [3], [1,5], [1,3], [5,3], [1,5,3], [3,3], [1,3,3], [3,3,5], [1,5,3,3] 14 | ''' 15 | 16 | #mycode 17 | def find_subsets(nums): 18 | subsets = [] 19 | # TODO: Write your code here 20 | subsets.append([]) 21 | 22 | start, end = 0, 0 23 | for i in range(len(nums)): 24 | start = 0 25 | if i>0 and nums[i] == nums[i-1]: 26 | start = end 27 | end = len(subsets) 28 | 29 | for j in range(start, end): 30 | set = subsets[j].copy() 31 | set.append(nums[i]) 32 | subsets.append(set) 33 | 34 | return subsets 35 | 36 | 37 | def main(): 38 | 39 | print("Here is the list of subsets: " + str(find_subsets([1, 3, 3]))) 40 | print("Here is the list of subsets: " + str(find_subsets([1, 5, 3, 3]))) 41 | 42 | 43 | main() 44 | 45 | 46 | 47 | #answer 48 | def find_subsets(nums): 49 | # sort the numbers to handle duplicates 50 | list.sort(nums) 51 | subsets = [] 52 | subsets.append([]) 53 | startIndex, endIndex = 0, 0 54 | for i in range(len(nums)): 55 | startIndex = 0 56 | # if current and the previous elements are same, create new subsets only from the subsets 57 | # added in the previous step 58 | if i > 0 and nums[i] == nums[i - 1]: 59 | startIndex = endIndex + 1 60 | endIndex = len(subsets) - 1 61 | for j in range(startIndex, endIndex+1): 62 | # create a new subset from the existing subset and add the current element to it 63 | set = list(subsets[j]) 64 | set.append(nums[i]) 65 | subsets.append(set) 66 | return subsets 67 | 68 | 69 | def main(): 70 | 71 | print("Here is the list of subsets: " + str(find_subsets([1, 3, 3]))) 72 | print("Here is the list of subsets: " + str(find_subsets([1, 5, 3, 3]))) 73 | 74 | 75 | main() 76 | 77 | 78 | ''' 79 | Time complexity 80 | Since, in each step, the number of subsets could double (if not duplicate) as we add each element to all the existing subsets, 81 | the time complexity of the above algorithm is O(2^N), where ‘N’ is the total number of elements in the input set. 82 | This also means that, in the end, we will have a total of O(2^N) subsets at the most. 83 | 84 | Space complexity 85 | All the additional space used by our algorithm is for the output list. Since at most we will have a total of O(2^N) subsets, 86 | the space complexity of our algorithm is also O(2^N). 87 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Problem Challenge 3 - Rotation Count (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Rotation Count (medium) 5 | Given an array of numbers which is sorted in ascending order and is rotated ‘k’ times around a pivot, find ‘k’. 6 | 7 | You can assume that the array does not have any duplicates. 8 | 9 | Example 1: 10 | 11 | Input: [10, 15, 1, 3, 8] 12 | Output: 2 13 | Explanation: The array has been rotated 2 times. 14 | 15 | Example 2: 16 | 17 | Input: [4, 5, 7, 9, 10, -1, 2] 18 | Output: 5 19 | Explanation: The array has been rotated 5 times. 20 | 21 | Example 3: 22 | 23 | Input: [1, 3, 8, 10] 24 | Output: 0 25 | Explanation: The array has been not been rotated. 26 | ''' 27 | 28 | #mycode 29 | def count_rotations(arr): 30 | # TODO: Write your code here 31 | start, end = 0, len(arr) - 1 32 | while start <= end: 33 | mid = (start + end) //2 34 | 35 | if mid > start and arr[mid-1] > arr[mid]: 36 | return mid 37 | if mid < start and arr[mid] > arr[mid+1]: 38 | return mid + 1 39 | 40 | if arr[start] < arr[mid]: 41 | start = mid + 1 42 | else: 43 | end = mid - 1 44 | 45 | return 0 46 | 47 | 48 | 49 | def main(): 50 | print(count_rotations([10, 15, 1, 3, 8])) 51 | print(count_rotations([4, 5, 7, 9, 10, -1, 2])) 52 | print(count_rotations([1, 3, 8, 10])) 53 | 54 | 55 | main() 56 | 57 | 58 | 59 | #answer 60 | def count_rotations(arr): 61 | start, end = 0, len(arr) - 1 62 | while start < end: 63 | mid = start + (end - start) // 2 64 | 65 | # if mid is greater than the next element 66 | if mid < end and arr[mid] > arr[mid + 1]: 67 | return mid + 1 68 | 69 | # if mid is smaller than the previous element 70 | if mid > start and arr[mid - 1] > arr[mid]: 71 | return mid 72 | 73 | if arr[start] < arr[mid]: # left side is sorted, so the pivot is on right side 74 | start = mid + 1 75 | else: # right side is sorted, so the pivot is on the left side 76 | end = mid - 1 77 | 78 | return 0 # the array has not been rotated 79 | 80 | 81 | def main(): 82 | print(count_rotations([10, 15, 1, 3, 8])) 83 | print(count_rotations([4, 5, 7, 9, 10, -1, 2])) 84 | print(count_rotations([1, 3, 8, 10])) 85 | 86 | 87 | main() 88 | 89 | 90 | 91 | ''' 92 | Time complexity 93 | This algorithm will run in O(logN) most of the times, 94 | but since we only skip two numbers in case of duplicates instead of the half of the numbers, 95 | therefore the worst case time complexity will become O(N). 96 | 97 | Space complexity 98 | The algorithm runs in constant space O(1). 99 | ''' 100 | -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Frequency Sort (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a string, sort it based on the decreasing frequency of its characters. 4 | 5 | Example 1: 6 | 7 | Input: "Programming" 8 | Output: "rrggmmPiano" 9 | Explanation: 'r', 'g', and 'm' appeared twice, so they need to appear before any other character. 10 | Example 2: 11 | 12 | Input: "abcbab" 13 | Output: "bbbaac" 14 | Explanation: 'b' appeared three times, 'a' appeared twice, and 'c' appeared only once. 15 | ''' 16 | 17 | 18 | #mycode 19 | from heapq import * 20 | 21 | def sort_character_by_frequency(str): 22 | # TODO: Write your code here 23 | mapping= {} 24 | for i in str: 25 | mapping[i] = mapping.get(i,0) + 1 26 | 27 | temp =[] 28 | for i, freq in mapping.items(): 29 | heappush(temp,(-freq,i)) 30 | 31 | result="" 32 | while temp: 33 | freq, i = heappop(temp) 34 | result += i*(-freq) 35 | return result 36 | 37 | 38 | def main(): 39 | 40 | print("String after sorting characters by frequency: " + 41 | sort_character_by_frequency("Programming")) 42 | print("String after sorting characters by frequency: " + 43 | sort_character_by_frequency("abcbab")) 44 | 45 | 46 | main() 47 | 48 | 49 | #answer 50 | from heapq import * 51 | 52 | 53 | def sort_character_by_frequency(str): 54 | 55 | # find the frequency of each character 56 | charFrequencyMap = {} 57 | for char in str: 58 | charFrequencyMap[char] = charFrequencyMap.get(char, 0) + 1 59 | 60 | maxHeap = [] 61 | # add all characters to the max heap 62 | for char, frequency in charFrequencyMap.items(): 63 | heappush(maxHeap, (-frequency, char)) 64 | 65 | 66 | # build a string, appending the most occurring characters first 67 | sortedString = [] 68 | while maxHeap: 69 | frequency, char = heappop(maxHeap) 70 | for _ in range(-frequency): 71 | sortedString.append(char) 72 | 73 | return ''.join(sortedString) 74 | 75 | 76 | def main(): 77 | 78 | print("String after sorting characters by frequency: " + 79 | sort_character_by_frequency("Programming")) 80 | print("String after sorting characters by frequency: " + 81 | sort_character_by_frequency("abcbab")) 82 | 83 | 84 | main() 85 | 86 | 87 | ''' 88 | Time complexity 89 | The time complexity of the above algorithm is O(D*logD) where ‘D’ is the number of distinct characters in the input string. 90 | This means, in the worst case, when all characters are unique the time complexity of the algorithm will be O(N*logN) 91 | where ‘N’ is the total number of characters in the string. 92 | 93 | Space complexity 94 | The space complexity will be O(N), as in the worst case, we need to store all the ‘N’ characters in the HashMap. 95 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Find all Missing Numbers (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | We are given an unsorted array containing numbers taken from the range 1 to ‘n’. 4 | The array can have duplicates, which means some numbers will be missing. Find all those missing numbers. 5 | 6 | Example 1: 7 | 8 | Input: [2, 3, 1, 8, 2, 3, 5, 1] 9 | Output: 4, 6, 7 10 | Explanation: The array should have all numbers from 1 to 8, due to duplicates 4, 6, and 7 are missing. 11 | 12 | Example 2: 13 | 14 | Input: [2, 4, 1, 2] 15 | Output: 3 16 | 17 | Example 3: 18 | 19 | Input: [2, 3, 2, 1] 20 | Output: 4 21 | ''' 22 | 23 | 24 | #mycode 25 | def find_missing_numbers(nums): 26 | missingNumbers = [] 27 | # TODO: Write your code here 28 | i=0 29 | while i < len(nums): 30 | j=nums[i]-1 31 | if i !=j and j!= nums[j] -1: 32 | nums[i], nums[j] = nums[j], nums[i] 33 | else: 34 | i += 1 35 | for i in range(len(nums)): 36 | if i != nums[i]-1: 37 | missingNumbers.append(i+1) 38 | return missingNumbers 39 | 40 | ''' 41 | Be careful about i != j and nums[i] != nums[j] 42 | when there are duplicates in index i, then using i!=j as condition, while will keep looping 43 | using nums[i] != nums[j] can avoid this problem, because duplicates means there exists nums[i] = nums[j] 44 | 45 | 2 4 1 2 46 | 47 | i=0 48 | 4 2 1 2 49 | 2 2 1 4 50 | i=1 51 | 2 2 1 4 52 | i=2 53 | 1 2 2 4 54 | i=3 55 | 1 2 2 4 56 | ''' 57 | 58 | 59 | #mycode2 60 | def find_missing_numbers(nums): 61 | missingNumbers = [] 62 | # TODO: Write your code here 63 | for i in range(len(nums)): 64 | j=abs(nums[i])-1 65 | if nums[j] >= 0: 66 | nums[j] = -nums[j] 67 | 68 | for i in range(len(nums)): 69 | if nums[i] > 0: 70 | missingNumbers.append(i+1) 71 | return missingNumbers 72 | 73 | 74 | 75 | #answer 76 | def find_missing_numbers(nums): 77 | i = 0 78 | while i < len(nums): 79 | j = nums[i] - 1 80 | if nums[i] != nums[j]: 81 | nums[i], nums[j] = nums[j], nums[i] # swap 82 | else: 83 | i += 1 84 | 85 | missingNumbers = [] 86 | 87 | for i in range(len(nums)): 88 | if nums[i] != i + 1: 89 | missingNumbers.append(i + 1) 90 | 91 | return missingNumbers 92 | 93 | 94 | def main(): 95 | print(find_missing_numbers([2, 3, 1, 8, 2, 3, 5, 1])) 96 | print(find_missing_numbers([2, 4, 1, 2])) 97 | print(find_missing_numbers([2, 3, 2, 1])) 98 | 99 | 100 | main() 101 | 102 | 103 | ''' 104 | Time complexity 105 | The time complexity of the above algorithm is O(n). 106 | 107 | Space complexity 108 | Ignoring the space required for the output array, the algorithm runs in constant space O(1). 109 | ''' 110 | -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Smallest Subarray with a given sum (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of positive numbers and a positive number ‘S’, find the length of the smallest contiguous subarray whose sum is greater than or equal to ‘S’. Return 0, if no such subarray exists. 4 | 5 | Example 1: 6 | 7 | Input: [2, 1, 5, 2, 3, 2], S=7 8 | Output: 2 9 | Explanation: The smallest subarray with a sum great than or equal to '7' is [5, 2]. 10 | 11 | Example 2: 12 | 13 | Input: [2, 1, 5, 2, 8], S=7 14 | Output: 1 15 | Explanation: The smallest subarray with a sum greater than or equal to '7' is [8]. 16 | 17 | Example 3: 18 | 19 | Input: [3, 4, 1, 1, 6], S=8 20 | Output: 3 21 | Explanation: Smallest subarrays with a sum greater than or equal to '8' are [3, 4, 1] or [1, 1, 6]. 22 | ''' 23 | 24 | #mycode 25 | import math 26 | def smallest_subarray_with_given_sum(s, arr): 27 | window_sum = 0 28 | min_length = math.inf 29 | window_start = 0 30 | 31 | for window_end in range(0, len(arr)): 32 | window_sum += arr[window_end] # add the next element 33 | # shrink the window as small as possible until the 'window_sum' is smaller than 's' 34 | while window_sum >= s: 35 | min_length = min(min_length, window_end - window_start + 1) 36 | window_sum -= arr[window_start] 37 | window_start += 1 38 | if min_length == math.inf: 39 | return 0 40 | return min_length 41 | 42 | 43 | #answer 44 | import math 45 | 46 | def smallest_subarray_with_given_sum(s, arr): 47 | window_sum = 0 48 | min_length = math.inf 49 | window_start = 0 50 | 51 | for window_end in range(0, len(arr)): 52 | window_sum += arr[window_end] # add the next element 53 | # shrink the window as small as possible until the 'window_sum' is smaller than 's' 54 | while window_sum >= s: 55 | min_length = min(min_length, window_end - window_start + 1) 56 | window_sum -= arr[window_start] 57 | window_start += 1 58 | if min_length == math.inf: 59 | return 0 60 | return min_length 61 | 62 | 63 | def main(): 64 | print("Smallest subarray length: " + str(smallest_subarray_with_given_sum(7, [2, 1, 5, 2, 3, 2]))) 65 | print("Smallest subarray length: " + str(smallest_subarray_with_given_sum(7, [2, 1, 5, 2, 8]))) 66 | print("Smallest subarray length: " + str(smallest_subarray_with_given_sum(8, [3, 4, 1, 1, 6]))) 67 | 68 | main() 69 | 70 | 71 | ''' 72 | Time Complexity 73 | The time complexity of the above algorithm will be O(N). 74 | The outer for loop runs for all elements and the inner while loop processes each element only once, therefore the time complexity of the algorithm will be O(N+N) which is asymptotically equivalent to O(N). 75 | 76 | Space Complexity 77 | The algorithm runs in constant space O(1). 78 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Kth Largest Number in a Stream (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Design a class to efficiently find the Kth largest element in a stream of numbers. 4 | 5 | The class should have the following two things: 6 | 7 | The constructor of the class should accept an integer array containing initial numbers from the stream and an integer ‘K’. 8 | The class should expose a function add(int num) which will store the given number and return the Kth largest number. 9 | Example 1: 10 | 11 | Input: [3, 1, 5, 12, 2, 11], K = 4 12 | 1. Calling add(6) should return '5'. 13 | 2. Calling add(13) should return '6'. 14 | 2. Calling add(4) should still return '6'. 15 | ''' 16 | 17 | 18 | #mycode 19 | from heapq import * 20 | 21 | class KthLargestNumberInStream: 22 | def __init__(self, nums, k): 23 | # TODO: Write your code here 24 | self.k = k 25 | self.result = [] 26 | 27 | for num in nums: 28 | heappush(self.result,num) 29 | if len(self.result) > self.k: 30 | heappop(self.result) 31 | 32 | def add(self, num): 33 | # TODO: Write your code here 34 | heappush(self.result,num) 35 | if len(self.result) > self.k: 36 | heappop(self.result) 37 | return self.result[0] 38 | 39 | 40 | def main(): 41 | 42 | kthLargestNumber = KthLargestNumberInStream([3, 1, 5, 12, 2, 11], 4) 43 | print("4th largest number is: " + str(kthLargestNumber.add(6))) 44 | print("4th largest number is: " + str(kthLargestNumber.add(13))) 45 | print("4th largest number is: " + str(kthLargestNumber.add(4))) 46 | 47 | 48 | main() 49 | 50 | 51 | 52 | #answer 53 | from heapq import * 54 | 55 | 56 | class KthLargestNumberInStream: 57 | minHeap = [] 58 | 59 | def __init__(self, nums, k): 60 | self.k = k 61 | # add the numbers in the min heap 62 | for num in nums: 63 | self.add(num) 64 | 65 | def add(self, num): 66 | # add the new number in the min heap 67 | heappush(self.minHeap, num) 68 | 69 | # if heap has more than 'k' numbers, remove one number 70 | if len(self.minHeap) > self.k: 71 | heappop(self.minHeap) 72 | 73 | # return the 'Kth largest number 74 | return self.minHeap[0] 75 | 76 | 77 | def main(): 78 | 79 | kthLargestNumber = KthLargestNumberInStream([3, 1, 5, 12, 2, 11], 4) 80 | print("4th largest number is: " + str(kthLargestNumber.add(6))) 81 | print("4th largest number is: " + str(kthLargestNumber.add(13))) 82 | print("4th largest number is: " + str(kthLargestNumber.add(4))) 83 | 84 | 85 | main() 86 | 87 | 88 | 89 | ''' 90 | Time complexity 91 | The time complexity of the add() function will be O(logK) since we are inserting the new number in the heap. 92 | 93 | Space complexity 94 | The space complexity will be O(K) for storing numbers in the heap. 95 | ''' -------------------------------------------------------------------------------- /06. Pattern In-place Reversal of a LinkedList/Reverse a LinkedList (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given the head of a Singly LinkedList, reverse the LinkedList. 4 | Write a function to return the new head of the reversed LinkedList. 5 | ''' 6 | 7 | #mycode 8 | from __future__ import print_function 9 | 10 | 11 | class Node: 12 | def __init__(self, value, next=None): 13 | self.value = value 14 | self.next = next 15 | 16 | def print_list(self): 17 | temp = self 18 | while temp is not None: 19 | print(temp.value, end=" ") 20 | temp = temp.next 21 | print() 22 | 23 | 24 | def reverse(head): 25 | # TODO: Write your code here 26 | former, latter = None, head 27 | while latter: 28 | temp = latter 29 | latter = latter.next 30 | temp.next = former 31 | former = temp 32 | return temp 33 | 34 | 35 | def main(): 36 | head = Node(2) 37 | head.next = Node(4) 38 | head.next.next = Node(6) 39 | head.next.next.next = Node(8) 40 | head.next.next.next.next = Node(10) 41 | 42 | print("Nodes of original LinkedList are: ", end='') 43 | head.print_list() 44 | result = reverse(head) 45 | print("Nodes of reversed LinkedList are: ", end='') 46 | result.print_list() 47 | 48 | 49 | main() 50 | 51 | 52 | #answer 53 | from __future__ import print_function 54 | 55 | 56 | class Node: 57 | def __init__(self, value, next=None): 58 | self.value = value 59 | self.next = next 60 | 61 | def print_list(self): 62 | temp = self 63 | while temp is not None: 64 | print(temp.value, end=" ") 65 | temp = temp.next 66 | print() 67 | 68 | 69 | def reverse(head): 70 | previous, current, next = None, head, None 71 | while current is not None: 72 | next = current.next # temporarily store the next node 73 | current.next = previous # reverse the current node 74 | previous = current # before we move to the next node, point previous to the current node 75 | current = next # move on the next node 76 | return previous 77 | 78 | 79 | def main(): 80 | head = Node(2) 81 | head.next = Node(4) 82 | head.next.next = Node(6) 83 | head.next.next.next = Node(8) 84 | head.next.next.next.next = Node(10) 85 | 86 | print("Nodes of original LinkedList are: ", end='') 87 | head.print_list() 88 | result = reverse(head) 89 | print("Nodes of reversed LinkedList are: ", end='') 90 | result.print_list() 91 | 92 | 93 | main() 94 | 95 | 96 | ''' 97 | Time complexity 98 | The time complexity of our algorithm will be O(N) where ‘N’ is the total number of nodes in the LinkedList. 99 | 100 | Space complexity 101 | We only used constant space, therefore, the space complexity of our algorithm is O(1). 102 | ''' -------------------------------------------------------------------------------- /04. Pattern Merge Intervals/Conflicting Appointments (medium).py: -------------------------------------------------------------------------------- 1 | Problem Statement 2 | Given an array of intervals representing ‘N’ appointments, find out if a person can attend all the appointments. 3 | 4 | Example 1: 5 | 6 | Appointments: [[1,4], [2,5], [7,9]] 7 | Output: false 8 | Explanation: Since [1,4] and [2,5] overlap, a person cannot attend both of these appointments. 9 | 10 | Example 2: 11 | 12 | Appointments: [[6,7], [2,4], [8,12]] 13 | Output: true 14 | Explanation: None of the appointments overlap, therefore a person can attend all of them. 15 | 16 | Example 3: 17 | 18 | Appointments: [[4,5], [2,3], [3,6]] 19 | Output: false 20 | Explanation: Since [4,5] and [3,6] overlap, a person cannot attend both of these appointments. 21 | 22 | #mycode 23 | def can_attend_all_appointments(intervals): 24 | # TODO: Write your code here 25 | 26 | intervals.sort(key=lambda x:x[0]) 27 | 28 | for i in range(1,len(intervals)): 29 | 30 | if intervals[i][0] < intervals[i-1][1]: 31 | return False 32 | 33 | return True 34 | 35 | 36 | 37 | def main(): 38 | print("Can attend all appointments: " + str(can_attend_all_appointments([[1, 4], [2, 5], [7, 9]]))) 39 | print("Can attend all appointments: " + str(can_attend_all_appointments([[6, 7], [2, 4], [8, 12]]))) 40 | print("Can attend all appointments: " + str(can_attend_all_appointments([[4, 5], [2, 3], [3, 6]]))) 41 | 42 | 43 | main() 44 | 45 | 46 | 47 | 48 | #answer 49 | def can_attend_all_appointments(intervals): 50 | intervals.sort(key=lambda x: x[0]) 51 | start, end = 0, 1 52 | for i in range(1, len(intervals)): 53 | if intervals[i][start] < intervals[i-1][end]: 54 | # please note the comparison above, it is "<" and not "<=" 55 | # while merging we needed "<=" comparison, as we will be merging the two 56 | # intervals having condition "intervals[i][start] == intervals[i - 1][end]" but 57 | # such intervals don't represent conflicting appointments as one starts right 58 | # after the other 59 | return False 60 | return True 61 | 62 | 63 | def main(): 64 | print("Can attend all appointments: " + str(can_attend_all_appointments([[1, 4], [2, 5], [7, 9]]))) 65 | print("Can attend all appointments: " + str(can_attend_all_appointments([[6, 7], [2, 4], [8, 12]]))) 66 | print("Can attend all appointments: " + str(can_attend_all_appointments([[4, 5], [2, 3], [3, 6]]))) 67 | 68 | 69 | main() 70 | 71 | 72 | 73 | ''' 74 | Time complexity 75 | The time complexity of the above algorithm is O(N*logN), where ‘N’ is the total number of appointments. 76 | Though we are iterating the intervals only once, our algorithm will take O(N * logN) since we need to sort them in the beginning. 77 | 78 | Space complexity 79 | The space complexity of the above algorithm will be O(N), which we need for sorting. 80 | For Java, Arrays.sort() uses Timsort, which needs O(N) space. 81 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Minimum Difference Element (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of numbers sorted in ascending order, 4 | find the element in the array that has the minimum difference with the given ‘key’. 5 | 6 | Example 1: 7 | 8 | Input: [4, 6, 10], key = 7 9 | Output: 6 10 | Explanation: The difference between the key '7' and '6' is minimum than any other number in the array 11 | 12 | Example 2: 13 | 14 | Input: [4, 6, 10], key = 4 15 | Output: 4 16 | 17 | Example 3: 18 | 19 | Input: [1, 3, 8, 10, 15], key = 12 20 | Output: 10 21 | 22 | Example 4: 23 | 24 | Input: [4, 6, 10], key = 17 25 | Output: 10 26 | ''' 27 | 28 | #mycode 29 | 30 | def search_min_diff_element(arr, key): 31 | # TODO: Write your code here 32 | if key <= arr[0]: 33 | return arr[0] 34 | if key >= arr[-1]: 35 | return arr[-1] 36 | 37 | start, end = 0, len(arr)-1 38 | while start <= end: 39 | mid = (start + end) //2 40 | 41 | if arr[mid] < key: 42 | start = mid + 1 43 | elif arr[mid] > key: 44 | end = mid - 1 45 | else: 46 | return arr[mid] 47 | 48 | 49 | if abs(arr[start] - key) >= abs(arr[end]-key): 50 | return arr[end] 51 | else: 52 | return arr[start] 53 | 54 | 55 | 56 | def main(): 57 | print(search_min_diff_element([4, 6, 10], 7)) 58 | print(search_min_diff_element([4, 6, 10], 4)) 59 | print(search_min_diff_element([1, 3, 8, 10, 15], 12)) 60 | print(search_min_diff_element([4, 6, 10], 17)) 61 | 62 | 63 | main() 64 | 65 | 66 | #answer 67 | def search_min_diff_element(arr, key): 68 | if key < arr[0]: 69 | return arr[0] 70 | n = len(arr) 71 | if key > arr[n - 1]: 72 | return arr[n - 1] 73 | 74 | start, end = 0, n - 1 75 | while start <= end: 76 | mid = start + (end - start) // 2 77 | if key < arr[mid]: 78 | end = mid - 1 79 | elif key > arr[mid]: 80 | start = mid + 1 81 | else: 82 | return arr[mid] 83 | 84 | # at the end of the while loop, 'start == end+1' 85 | # we are not able to find the element in the given array 86 | # return the element which is closest to the 'key' 87 | if (arr[start] - key) < (key - arr[end]): 88 | return arr[start] 89 | return arr[end] 90 | 91 | 92 | def main(): 93 | print(search_min_diff_element([4, 6, 10], 7)) 94 | print(search_min_diff_element([4, 6, 10], 4)) 95 | print(search_min_diff_element([1, 3, 8, 10, 15], 12)) 96 | print(search_min_diff_element([4, 6, 10], 17)) 97 | 98 | 99 | main() 100 | 101 | 102 | 103 | ''' 104 | Time complexity 105 | Since, we are reducing the search range by half at every step, 106 | this means the time complexity of our algorithm will be O(logN) 107 | where ‘N’ is the total elements in the given array. 108 | 109 | Space complexity 110 | The algorithm runs in constant space O(1). 111 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Remove Duplicates (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of sorted numbers, remove all duplicates from it. You should not use any extra space; after removing the duplicates in-place return the new length of the array. 4 | 5 | Example 1: 6 | 7 | Input: [2, 3, 3, 3, 6, 9, 9] 8 | Output: 4 9 | Explanation: The first four elements after removing the duplicates will be [2, 3, 6, 9]. 10 | 11 | Example 2: 12 | 13 | Input: [2, 2, 2, 11] 14 | Output: 2 15 | Explanation: The first two elements after removing the duplicates will be [2, 11]. 16 | ''' 17 | 18 | #mycode 19 | def remove_duplicates(arr): 20 | # TODO: Write your code here 21 | i, result = 1, 1 22 | while i < len(arr): 23 | if arr[i] != arr[i-1]: 24 | result += 1 25 | i+=1 26 | return result 27 | 28 | 29 | #answer 30 | def remove_duplicates(arr): 31 | # index of the next non-duplicate element 32 | next_non_duplicate = 1 33 | 34 | i = 1 35 | while(i < len(arr)): 36 | if arr[next_non_duplicate - 1] != arr[i]: 37 | arr[next_non_duplicate] = arr[i] 38 | next_non_duplicate += 1 39 | i += 1 40 | 41 | return next_non_duplicate 42 | 43 | 44 | def main(): 45 | print(remove_duplicates([2, 3, 3, 3, 6, 9, 9])) 46 | print(remove_duplicates([2, 2, 2, 11])) 47 | 48 | 49 | main() 50 | 51 | 52 | ''' 53 | Time Complexity 54 | The time complexity of the above algorithm will be O(N), 55 | where ‘N’ is the total number of elements in the given array. 56 | 57 | Space Complexity 58 | The algorithm runs in constant space O(1). 59 | ''' 60 | 61 | 62 | ''' 63 | Similar Questions # 64 | Problem 1: Given an unsorted array of numbers and a target ‘key’, remove all instances of ‘key’ in-place and return the new length of the array. 65 | 66 | Example 1: 67 | 68 | Input: [3, 2, 3, 6, 3, 10, 9, 3], Key=3 69 | Output: 4 70 | Explanation: The first four elements after removing every 'Key' will be [2, 6, 10, 9]. 71 | 72 | Example 2: 73 | 74 | Input: [2, 11, 2, 2, 1], Key=2 75 | Output: 2 76 | Explanation: The first two elements after removing every 'Key' will be [11, 1]. 77 | ''' 78 | 79 | def remove_element(arr, key): 80 | nextElement = 0 # index of the next element which is not 'key' 81 | for i in range(len(arr)): 82 | if arr[i] != key: 83 | arr[nextElement] = arr[i] 84 | nextElement += 1 85 | 86 | return nextElement 87 | 88 | 89 | def main(): 90 | print("Array new length: " + 91 | str(remove_element([3, 2, 3, 6, 3, 10, 9, 3], 3))) 92 | print("Array new length: " + 93 | str(remove_element([2, 11, 2, 2, 1], 2))) 94 | 95 | 96 | main() 97 | 98 | ''' 99 | Time and Space Complexity: 100 | The time complexity of the above algorithm will be O(N), where ‘N’ is the total number of elements in the given array. 101 | 102 | The algorithm runs in constant space O(1). 103 | ''' -------------------------------------------------------------------------------- /10. Pattern Subsets/Problem Challenge 3 - Count of Structurally Unique Binary Search Trees (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Count of Structurally Unique Binary Search Trees (hard) 5 | 6 | Given a number ‘n’, write a function to return the count of structurally unique Binary Search Trees (BST) that can store values 1 to ‘n’. 7 | 8 | Example 1: 9 | 10 | Input: 2 11 | Output: 2 12 | Explanation: As we saw in the previous problem, there are 2 unique BSTs storing numbers from 1-2. 13 | 14 | Example 2: 15 | 16 | Input: 3 17 | Output: 5 18 | Explanation: There will be 5 unique BSTs that can store numbers from 1 to 5. 19 | ''' 20 | 21 | 22 | #mycode 23 | class TreeNode: 24 | def __init__(self, val): 25 | self.val = val 26 | self.left = None 27 | self.right = None 28 | 29 | 30 | def count_trees(n): 31 | if n < 0: 32 | return -1 33 | if n <=1 : 34 | return 1 35 | 36 | count = 0 37 | for i in range(1,n+1): 38 | left = count_trees(i-1) 39 | right = count_trees(n-i) 40 | count += left * right 41 | return count 42 | 43 | 44 | def main(): 45 | print("Total trees: " + str(count_trees(2))) 46 | print("Total trees: " + str(count_trees(3))) 47 | 48 | 49 | main() 50 | 51 | 52 | #answer 53 | class TreeNode: 54 | def __init__(self, val): 55 | self.val = val 56 | self.left = None 57 | self.right = None 58 | 59 | 60 | def count_trees(n): 61 | return count_trees_rec({}, n) 62 | 63 | 64 | def count_trees_rec(map, n): 65 | if n in map: 66 | return map[n] 67 | 68 | if n <= 1: 69 | return 1 70 | count = 0 71 | for i in range(1, n+1): 72 | # making 'i' the root of the tree 73 | countOfLeftSubtrees = count_trees_rec(map, i - 1) 74 | countOfRightSubtrees = count_trees_rec(map, n - i) 75 | count += (countOfLeftSubtrees * countOfRightSubtrees) 76 | 77 | map[n] = count 78 | return count 79 | 80 | 81 | def main(): 82 | print("Total trees: " + str(count_trees(2))) 83 | print("Total trees: " + str(count_trees(3))) 84 | 85 | 86 | main() 87 | 88 | 89 | 90 | ''' 91 | Time complexity # 92 | The time complexity of this algorithm will be exponential and will be similar to Balanced Parentheses. 93 | Estimated time complexity will be O(n*2^n) but the actual time complexity ( O(4^n/\sqrt{n})) 94 | is bounded by the Catalan number and is beyond the scope of a coding interview. 95 | 96 | Space complexity 97 | The space complexity of this algorithm will be exponential too, estimated O(2^n) but the actual will be (O(4^n/\sqrt{n}). 98 | 99 | Memorized version 100 | Our algorithm has overlapping subproblems as our recursive call will be evaluating the same sub-expression multiple times. 101 | To resolve this, we can use memorization and store the intermediate results in a HashMap. 102 | In each function call, we can check our map to see if we have already evaluated this sub-expression before. 103 | ''' 104 | -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Top 'K' Frequent Numbers (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an unsorted array of numbers, find the top ‘K’ frequently occurring numbers in it. 4 | 5 | Example 1: 6 | 7 | Input: [1, 3, 5, 12, 11, 12, 11], K = 2 8 | Output: [12, 11] 9 | Explanation: Both '11' and '12' apeared twice. 10 | Example 2: 11 | 12 | Input: [5, 12, 11, 3, 11], K = 2 13 | Output: [11, 5] or [11, 12] or [11, 3] 14 | Explanation: Only '11' appeared twice, all other numbers appeared once. 15 | ''' 16 | 17 | #mycode 18 | from heapq import * 19 | 20 | def find_k_frequent_numbers(nums, k): 21 | topNumbers = [] 22 | result = [] 23 | # TODO: Write your code here 24 | mapping = {} 25 | for num in nums: 26 | if num not in mapping: 27 | mapping[num] = 1 28 | else: 29 | mapping[num] += 1 30 | 31 | for num, freq in mapping.items(): 32 | if len(result) result[0][0]: 36 | heappop(result) 37 | heappush(result,(freq, num)) 38 | 39 | for i in range(len(result)-1,-1,-1): 40 | topNumbers.append(result[i][1]) 41 | return topNumbers 42 | 43 | 44 | def main(): 45 | 46 | print("Here are the K frequent numbers: " + 47 | str(find_k_frequent_numbers([1, 3, 5, 12, 11, 12, 11], 2))) 48 | 49 | print("Here are the K frequent numbers: " + 50 | str(find_k_frequent_numbers([5, 12, 11, 3, 11], 2))) 51 | 52 | 53 | main() 54 | 55 | 56 | 57 | #answer 58 | from heapq import * 59 | 60 | 61 | def find_k_frequent_numbers(nums, k): 62 | 63 | # find the frequency of each number 64 | numFrequencyMap = {} 65 | for num in nums: 66 | numFrequencyMap[num] = numFrequencyMap.get(num, 0) + 1 67 | 68 | minHeap = [] 69 | 70 | # go through all numbers of the numFrequencyMap and push them in the minHeap, which will have 71 | # top k frequent numbers. If the heap size is more than k, we remove the smallest(top) number 72 | for num, frequency in numFrequencyMap.items(): 73 | heappush(minHeap, (frequency, num)) 74 | if len(minHeap) > k: 75 | heappop(minHeap) 76 | 77 | # create a list of top k numbers 78 | topNumbers = [] 79 | while minHeap: 80 | topNumbers.append(heappop(minHeap)[1]) 81 | 82 | return topNumbers 83 | 84 | 85 | def main(): 86 | 87 | print("Here are the K frequent numbers: " + 88 | str(find_k_frequent_numbers([1, 3, 5, 12, 11, 12, 11], 2))) 89 | 90 | print("Here are the K frequent numbers: " + 91 | str(find_k_frequent_numbers([5, 12, 11, 3, 11], 2))) 92 | 93 | 94 | main() 95 | 96 | 97 | 98 | ''' 99 | Time complexity 100 | The time complexity of the above algorithm is O(N+N*logK). 101 | 102 | Space complexity 103 | The space complexity will be O(N). Even though we are storing only ‘K’ numbers in the heap. 104 | For the frequency map, however, we need to store all the ‘N’ numbers. 105 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Number Range (medium) .py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of numbers sorted in ascending order, find the range of a given number ‘key’. The range of the ‘key’ will be the first and last position of the ‘key’ in the array. 4 | 5 | Write a function to return the range of the ‘key’. If the ‘key’ is not present return [-1, -1]. 6 | 7 | Example 1: 8 | 9 | Input: [4, 6, 6, 6, 9], key = 6 10 | Output: [1, 3] 11 | 12 | Example 2: 13 | 14 | Input: [1, 3, 8, 10, 15], key = 10 15 | Output: [3, 3] 16 | 17 | Example 3: 18 | 19 | Input: [1, 3, 8, 10, 15], key = 12 20 | Output: [-1, -1] 21 | ''' 22 | 23 | #mycode 24 | def find_range(arr, key): 25 | result = [- 1, -1] 26 | # TODO: Write your code here 27 | result[0]=binary_search(arr, key, False) 28 | if result[0] != -1: 29 | result[1]=binary_search(arr, key, True) 30 | return result 31 | 32 | 33 | def binary_search(arr, key, findMax): 34 | start, end = 0, len(arr)-1 35 | 36 | keyIndex = -1 37 | while start <= end: 38 | mid = (start + end) //2 39 | 40 | if arr[mid] < key: 41 | start = mid+1 42 | elif arr[mid] > key: 43 | end = mid - 1 44 | else: 45 | keyIndex = mid 46 | if findMax: 47 | start = mid +1 48 | else: 49 | end = mid - 1 50 | 51 | return keyIndex 52 | 53 | def main(): 54 | print(find_range([4, 6, 6, 6, 9], 6)) 55 | print(find_range([1, 3, 8, 10, 15], 10)) 56 | print(find_range([1, 3, 8, 10, 15], 12)) 57 | 58 | 59 | main() 60 | 61 | 62 | 63 | #answer 64 | def find_range(arr, key): 65 | result = [- 1, -1] 66 | result[0] = binary_search(arr, key, False) 67 | if result[0] != -1: # no need to search, if 'key' is not present in the input array 68 | result[1] = binary_search(arr, key, True) 69 | return result 70 | 71 | 72 | # modified Binary Search 73 | def binary_search(arr, key, findMaxIndex): 74 | keyIndex = -1 75 | start, end = 0, len(arr) - 1 76 | while start <= end: 77 | mid = start + (end - start) // 2 78 | if key < arr[mid]: 79 | end = mid - 1 80 | elif key > arr[mid]: 81 | start = mid + 1 82 | else: # key == arr[mid] 83 | keyIndex = mid 84 | if findMaxIndex: 85 | start = mid + 1 # search ahead to find the last index of 'key' 86 | else: 87 | end = mid - 1 # search behind to find the first index of 'key' 88 | 89 | return keyIndex 90 | 91 | 92 | def main(): 93 | print(find_range([4, 6, 6, 6, 9], 6)) 94 | print(find_range([1, 3, 8, 10, 15], 10)) 95 | print(find_range([1, 3, 8, 10, 15], 12)) 96 | 97 | 98 | main() 99 | 100 | 101 | 102 | ''' 103 | Time complexity 104 | Since, we are reducing the search range by half at every step, this means that the time complexity of our algorithm 105 | will be O(logN) where ‘N’ is the total elements in the given array. 106 | 107 | Space complexity 108 | The algorithm runs in constant space O(1). 109 | ''' -------------------------------------------------------------------------------- /08. Pattern Tree Depth First Search/Sum of Path Numbers (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree where each node can only have a digit (0-9) value, 4 | each root-to-leaf path will represent a number. Find the total sum of all the numbers represented by all paths. 5 | ''' 6 | 7 | #mycode 8 | class TreeNode: 9 | def __init__(self, val, left=None, right=None): 10 | self.val = val 11 | self.left = left 12 | self.right = right 13 | 14 | 15 | def find_sum_of_path_numbers(root): 16 | # TODO: Write your code here 17 | return find_root_to_leaf_path_numbers(root, 0) 18 | 19 | def find_root_to_leaf_path_numbers(currentNode, currentSum): 20 | if not currentNode: 21 | return 0 22 | 23 | currentSum = currentSum*10 + currentNode.val 24 | 25 | if currentNode.left is None and currentNode.right is None: 26 | return currentSum 27 | else: 28 | return find_root_to_leaf_path_numbers(currentNode.left, currentSum) + find_root_to_leaf_path_numbers(currentNode.right, currentSum) 29 | 30 | 31 | 32 | 33 | def main(): 34 | root = TreeNode(1) 35 | root.left = TreeNode(0) 36 | root.right = TreeNode(1) 37 | root.left.left = TreeNode(1) 38 | root.right.left = TreeNode(6) 39 | root.right.right = TreeNode(5) 40 | print("Total Sum of Path Numbers: " + str(find_sum_of_path_numbers(root))) 41 | 42 | 43 | main() 44 | 45 | 46 | 47 | #answer 48 | class TreeNode: 49 | def __init__(self, val, left=None, right=None): 50 | self.val = val 51 | self.left = left 52 | self.right = right 53 | 54 | 55 | def find_sum_of_path_numbers(root): 56 | return find_root_to_leaf_path_numbers(root, 0) 57 | 58 | 59 | def find_root_to_leaf_path_numbers(currentNode, pathSum): 60 | if currentNode is None: 61 | return 0 62 | 63 | # calculate the path number of the current node 64 | pathSum = 10 * pathSum + currentNode.val 65 | 66 | # if the current node is a leaf, return the current path sum 67 | if currentNode.left is None and currentNode.right is None: 68 | return pathSum 69 | 70 | # traverse the left and the right sub-tree 71 | return find_root_to_leaf_path_numbers(currentNode.left, pathSum) + find_root_to_leaf_path_numbers(currentNode.right, pathSum) 72 | 73 | 74 | def main(): 75 | root = TreeNode(1) 76 | root.left = TreeNode(0) 77 | root.right = TreeNode(1) 78 | root.left.left = TreeNode(1) 79 | root.right.left = TreeNode(6) 80 | root.right.right = TreeNode(5) 81 | print("Total Sum of Path Numbers: " + str(find_sum_of_path_numbers(root))) 82 | 83 | 84 | main() 85 | 86 | 87 | ''' 88 | Time complexity # 89 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 90 | This is due to the fact that we traverse each node once. 91 | 92 | Space complexity # 93 | The space complexity of the above algorithm will be O(N) in the worst case. 94 | This space will be used to store the recursion stack. 95 | The worst case will happen when the given tree is a linked list (i.e., every node has only one child). 96 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Connect Ropes (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given ‘N’ ropes with different lengths, we need to connect these ropes into one big rope with minimum cost. 4 | The cost of connecting two ropes is equal to the sum of their lengths. 5 | 6 | Example 1: 7 | 8 | Input: [1, 3, 11, 5] 9 | Output: 33 10 | Explanation: First connect 1+3(=4), then 4+5(=9), and then 9+11(=20). So the total cost is 33 (4+9+20) 11 | Example 2: 12 | 13 | Input: [3, 4, 5, 6] 14 | Output: 36 15 | Explanation: First connect 3+4(=7), then 5+6(=11), 7+11(=18). Total cost is 36 (7+11+18) 16 | Example 3: 17 | 18 | Input: [1, 3, 11, 5, 2] 19 | Output: 42 20 | Explanation: First connect 1+2(=3), then 3+3(=6), 6+5(=11), 11+11(=22). Total cost is 42 (3+6+11+22) 21 | ''' 22 | 23 | 24 | #mycode 25 | from heapq import * 26 | 27 | def minimum_cost_to_connect_ropes(ropeLengths): 28 | result = [] 29 | # TODO: Write your code here 30 | for i in ropeLengths: 31 | heappush(result,i) 32 | 33 | lenghth = 0 34 | while len(result)>1: 35 | temp = heappop(result) + heappop(result) 36 | lenghth += temp 37 | heappush(result,temp) 38 | return lenghth 39 | 40 | 41 | def main(): 42 | 43 | print("Minimum cost to connect ropes: " + 44 | str(minimum_cost_to_connect_ropes([1, 3, 11, 5]))) 45 | print("Minimum cost to connect ropes: " + 46 | str(minimum_cost_to_connect_ropes([3, 4, 5, 6]))) 47 | print("Minimum cost to connect ropes: " + 48 | str(minimum_cost_to_connect_ropes([1, 3, 11, 5, 2]))) 49 | 50 | 51 | main() 52 | 53 | 54 | 55 | #answer 56 | from heapq import * 57 | 58 | 59 | def minimum_cost_to_connect_ropes(ropeLengths): 60 | minHeap = [] 61 | # add all ropes to the min heap 62 | for i in ropeLengths: 63 | heappush(minHeap, i) 64 | 65 | # go through the values of the heap, in each step take top (lowest) rope lengths from the min heap 66 | # connect them and push the result back to the min heap. 67 | # keep doing this until the heap is left with only one rope 68 | result, temp = 0, 0 69 | while len(minHeap) > 1: 70 | temp = heappop(minHeap) + heappop(minHeap) 71 | result += temp 72 | heappush(minHeap, temp) 73 | 74 | return result 75 | 76 | 77 | def main(): 78 | 79 | print("Minimum cost to connect ropes: " + 80 | str(minimum_cost_to_connect_ropes([1, 3, 11, 5]))) 81 | print("Minimum cost to connect ropes: " + 82 | str(minimum_cost_to_connect_ropes([3, 4, 5, 6]))) 83 | print("Minimum cost to connect ropes: " + 84 | str(minimum_cost_to_connect_ropes([1, 3, 11, 5, 2]))) 85 | 86 | main() 87 | 88 | 89 | 90 | 91 | ''' 92 | Time complexity 93 | Given ‘N’ ropes, we need O(N*logN)to insert all the ropes in the heap. 94 | In each step, while processing the heap, we take out two elements from the heap and insert one. 95 | This means we will have a total of ‘N’ steps, having a total time complexity of O(N*logN). 96 | 97 | Space complexity # 98 | The space complexity will be O(N) because we need to store all the ropes in the heap. 99 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Problem Challenge 3 - Find the First K Missing Positive Numbers (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Find the First K Missing Positive Numbers (hard) 5 | 6 | Given an unsorted array containing numbers and a number ‘k’, find the first ‘k’ missing positive numbers in the array. 7 | 8 | Example 1: 9 | 10 | Input: [3, -1, 4, 5, 5], k=3 11 | Output: [1, 2, 6] 12 | Explanation: The smallest missing positive numbers are 1, 2 and 6. 13 | 14 | Example 2: 15 | 16 | Input: [2, 3, 4], k=3 17 | Output: [1, 5, 6] 18 | Explanation: The smallest missing positive numbers are 1, 5 and 6. 19 | 20 | Example 3: 21 | 22 | Input: [-2, -3, 4], k=2 23 | Output: [1, 2] 24 | Explanation: The smallest missing positive numbers are 1 and 2. 25 | ''' 26 | 27 | #mycode 28 | def find_first_k_missing_positive(nums, k): 29 | missingNumbers = [] 30 | # TODO: Write your code here 31 | i=0 32 | while i < len(nums): 33 | j=nums[i]-1 34 | if j >= 0 and j 0 and nums[i] <= n and nums[i] != nums[j]: 67 | nums[i], nums[j] = nums[j], nums[i] # swap 68 | else: 69 | i += 1 70 | 71 | missingNumbers = [] 72 | extraNumbers = set() 73 | for i in range(n): 74 | if len(missingNumbers) < k: 75 | if nums[i] != i + 1: 76 | missingNumbers.append(i + 1) 77 | extraNumbers.add(nums[i]) 78 | 79 | # add the remaining missing numbers 80 | i = 1 81 | while len(missingNumbers) < k: 82 | candidateNumber = i + n 83 | # ignore if the array contains the candidate number 84 | if candidateNumber not in extraNumbers: 85 | missingNumbers.append(candidateNumber) 86 | i += 1 87 | 88 | return missingNumbers 89 | 90 | 91 | def main(): 92 | print(find_first_k_missing_positive([3, -1, 4, 5, 5], 3)) 93 | print(find_first_k_missing_positive([2, 3, 4], 3)) 94 | print(find_first_k_missing_positive([-2, -3, 4], 2)) 95 | 96 | 97 | main() 98 | 99 | 100 | ''' 101 | Time complexity 102 | The time complexity of the above algorithm is O(n + k), as the last two for loops will run for O(n) and O(k) times respectively. 103 | 104 | Space complexity 105 | The algorithm needs O(k) space to store the extraNumbers. 106 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Triplet Sum to Zero (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of unsorted numbers, find all unique triplets in it that add up to zero. 4 | 5 | Example 1: 6 | 7 | Input: [-3, 0, 1, 2, -1, 1, -2] 8 | Output: [-3, 1, 2], [-2, 0, 2], [-2, 1, 1], [-1, 0, 1] 9 | Explanation: There are four unique triplets whose sum is equal to zero. 10 | 11 | Example 2: 12 | 13 | Input: [-5, 2, -1, -2, 3] 14 | Output: [[-5, 2, 3], [-2, -1, 3]] 15 | Explanation: There are two unique triplets whose sum is equal to zero. 16 | ''' 17 | 18 | 19 | #mycode 20 | def search_triplets(arr): 21 | triplets = [] 22 | arr.sort() 23 | # TODO: Write your code here 24 | for i in range(len(arr)): 25 | if i > 0 and arr[i] == arr[i-1]: 26 | continue 27 | search_pair(arr, -arr[i], i+1, triplets) 28 | 29 | return triplets 30 | 31 | def search_pair(arr, target_sum, left, triplets): 32 | right = len(arr)-1 33 | while left < right: 34 | if arr[left] + arr[right] == target_sum: 35 | triplets.append([-target_sum, arr[left], arr[right]]) 36 | left += 1 37 | right -= 1 38 | 39 | while left < right and arr[left] == arr[left-1]: 40 | left += 1 41 | while left < right and arr[right] == arr[right+1]: 42 | right -= 1 43 | 44 | elif arr[left] + arr[right] > target_sum: 45 | right -=1 46 | else: 47 | left += 1 48 | 49 | 50 | 51 | #answer 52 | def search_triplets(arr): 53 | arr.sort() 54 | triplets = [] 55 | for i in range(len(arr)): 56 | if i > 0 and arr[i] == arr[i-1]: # skip same element to avoid duplicate triplets 57 | continue 58 | search_pair(arr, -arr[i], i+1, triplets) 59 | 60 | return triplets 61 | 62 | 63 | def search_pair(arr, target_sum, left, triplets): 64 | right = len(arr) - 1 65 | while(left < right): 66 | current_sum = arr[left] + arr[right] 67 | if current_sum == target_sum: # found the triplet 68 | triplets.append([-target_sum, arr[left], arr[right]]) 69 | left += 1 70 | right -= 1 71 | while left < right and arr[left] == arr[left - 1]: 72 | left += 1 # skip same element to avoid duplicate triplets 73 | while left < right and arr[right] == arr[right + 1]: 74 | right -= 1 # skip same element to avoid duplicate triplets 75 | elif target_sum > current_sum: 76 | left += 1 # we need a pair with a bigger sum 77 | else: 78 | right -= 1 # we need a pair with a smaller sum 79 | 80 | 81 | def main(): 82 | print(search_triplets([-3, 0, 1, 2, -1, 1, -2])) 83 | print(search_triplets([-5, 2, -1, -2, 3])) 84 | 85 | 86 | main() 87 | 88 | 89 | ''' 90 | Time complexity 91 | Sorting the array will take O(N * logN). 92 | The searchPair() function will take O(N). 93 | As we are calling searchPair() for every number in the input array, 94 | this means that overall searchTriplets() will take O(N * logN + N^2), which is asymptotically equivalent to O(N^2). 95 | 96 | Space complexity 97 | Ignoring the space required for the output array, 98 | the space complexity of the above algorithm will be O(N) which is required for sorting. 99 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Binary Tree Level Order Traversal (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree, populate an array to represent its level-by-level traversal. 4 | You should populate the values of all nodes of each level from left to right in separate sub-arrays. 5 | ''' 6 | 7 | #answer 8 | from collections import deque 9 | 10 | 11 | class TreeNode: 12 | def __init__(self, val): 13 | self.val = val 14 | self.left, self.right = None, None 15 | 16 | 17 | def traverse(root): 18 | result = [] 19 | # TODO: Write your code here 20 | if not root: 21 | return result 22 | stack = [root] 23 | value=[[root.val]] 24 | while stack: 25 | current=[] 26 | current_val=[] 27 | for i in stack: 28 | current_val.append(i.val) 29 | if i.left: 30 | current.append(i.left) 31 | if i.right: 32 | current.append(i.right) 33 | result.append(current_val) 34 | stack = current 35 | return result 36 | 37 | 38 | def main(): 39 | root = TreeNode(12) 40 | root.left = TreeNode(7) 41 | root.right = TreeNode(1) 42 | root.left.left = TreeNode(9) 43 | root.right.left = TreeNode(10) 44 | root.right.right = TreeNode(5) 45 | print("Level order traversal: " + str(traverse(root))) 46 | 47 | 48 | main() 49 | 50 | 51 | #answer 52 | from collections import deque 53 | 54 | 55 | class TreeNode: 56 | def __init__(self, val): 57 | self.val = val 58 | self.left, self.right = None, None 59 | 60 | 61 | def traverse(root): 62 | result = [] 63 | if root is None: 64 | return result 65 | 66 | queue = deque() 67 | queue.append(root) 68 | while queue: 69 | levelSize = len(queue) 70 | currentLevel = [] 71 | for _ in range(levelSize): 72 | currentNode = queue.popleft() 73 | # add the node to the current level 74 | currentLevel.append(currentNode.val) 75 | # insert the children of current node in the queue 76 | if currentNode.left: 77 | queue.append(currentNode.left) 78 | if currentNode.right: 79 | queue.append(currentNode.right) 80 | 81 | result.append(currentLevel) 82 | 83 | return result 84 | 85 | 86 | def main(): 87 | root = TreeNode(12) 88 | root.left = TreeNode(7) 89 | root.right = TreeNode(1) 90 | root.left.left = TreeNode(9) 91 | root.right.left = TreeNode(10) 92 | root.right.right = TreeNode(5) 93 | print("Level order traversal: " + str(traverse(root))) 94 | 95 | 96 | main() 97 | 98 | 99 | ''' 100 | Time complexity 101 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 102 | This is due to the fact that we traverse each node once. 103 | 104 | Space complexity 105 | The space complexity of the above algorithm will be O(N) as we need to return a list containing the level order traversal. 106 | We will also need O(N) space for the queue. Since we can have a maximum of N/2 nodes at any level (this could happen only at the lowest level), 107 | therefore we will need O(N) space to store them in the queue. 108 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Longest Substring with K Distinct Characters (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement # 3 | Given a string, find the length of the longest substring in it with no more than K distinct characters. 4 | 5 | Example 1: 6 | 7 | Input: String="araaci", K=2 8 | Output: 4 9 | Explanation: The longest substring with no more than '2' distinct characters is "araa". 10 | 11 | Example 2: 12 | 13 | Input: String="araaci", K=1 14 | Output: 2 15 | Explanation: The longest substring with no more than '1' distinct characters is "aa". 16 | 17 | Example 3: 18 | 19 | Input: String="cbbebi", K=3 20 | Output: 5 21 | Explanation: The longest substrings with no more than '3' distinct characters are "cbbeb" & "bbebi". 22 | ''' 23 | 24 | #mycode 25 | def longest_substring_with_k_distinct(str, k): 26 | dict_arr={} 27 | max_len, win_start = 0, 0 28 | 29 | for win_end in range(len(str)): 30 | if str[win_end] not in dict_arr: 31 | dict_arr[str[win_end]]=1 32 | else: 33 | dict_arr[str[win_end]] +=1 34 | 35 | while len(dict_arr) > k: 36 | if dict_arr[str[win_start]] == 1: 37 | del dict_arr[str[win_start]] 38 | else: 39 | dict_arr[str[win_start]] -= 1 40 | win_start += 1 41 | 42 | if len(dict_arr) == k: 43 | max_len=max(max_len,sum(dict_arr.values())) 44 | 45 | return max_len 46 | 47 | 48 | #answer 49 | def longest_substring_with_k_distinct(str, k): 50 | window_start = 0 51 | max_length = 0 52 | char_frequency = {} 53 | 54 | # in the following loop we'll try to extend the range [window_start, window_end] 55 | for window_end in range(len(str)): 56 | right_char = str[window_end] 57 | if right_char not in char_frequency: 58 | char_frequency[right_char] = 0 59 | char_frequency[right_char] += 1 60 | 61 | # shrink the sliding window, until we are left with 'k' distinct characters in the char_frequency 62 | while len(char_frequency) > k: 63 | left_char = str[window_start] 64 | char_frequency[left_char] -= 1 65 | if char_frequency[left_char] == 0: 66 | del char_frequency[left_char] 67 | window_start += 1 # shrink the window 68 | # remember the maximum length so far 69 | max_length = max(max_length, window_end-window_start + 1) 70 | return max_length 71 | 72 | 73 | def main(): 74 | print("Length of the longest substring: " + str(longest_substring_with_k_distinct("araaci", 2))) 75 | print("Length of the longest substring: " + str(longest_substring_with_k_distinct("araaci", 1))) 76 | print("Length of the longest substring: " + str(longest_substring_with_k_distinct("cbbebi", 3))) 77 | 78 | 79 | main() 80 | 81 | 82 | ''' 83 | Time Complexity 84 | The time complexity of the above algorithm will be O(N) where ‘N’ is the number of characters in the input string. 85 | The outer for loop runs for all characters and the inner while loop processes each character only once, therefore the time complexity of the algorithm will be O(N+N) which is asymptotically equivalent to O(N). 86 | 87 | Space Complexity 88 | The space complexity of the algorithm is O(K), as we will be storing a maximum of ‘K+1’ characters in the HashMap. 89 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Triplet Sum Close to Target (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Triplet Sum Close to Target (medium) 3 | 4 | Problem Statement 5 | Given an array of unsorted numbers and a target number, find a triplet in the array whose sum is as close to the target number as possible, return the sum of the triplet. 6 | If there are more than one such triplet, return the sum of the triplet with the smallest sum. 7 | 8 | Example 1: 9 | 10 | Input: [-2, 0, 1, 2], target=2 11 | Output: 1 12 | Explanation: The triplet [-2, 1, 2] has the closest sum to the target. 13 | 14 | Example 2: 15 | 16 | Input: [-3, -1, 1, 2], target=1 17 | Output: 0 18 | Explanation: The triplet [-3, 1, 2] has the closest sum to the target. 19 | 20 | Example 3: 21 | 22 | Input: [1, 0, 1, 1], target=100 23 | Output: 3 24 | Explanation: The triplet [1, 1, 1] has the closest sum to the target. 25 | ''' 26 | 27 | #mycode 28 | import math 29 | 30 | def triplet_sum_close_to_target(arr, target_sum): 31 | 32 | # TODO: Write your code here 33 | arr.sort() 34 | min_sum, err_min = 0, math.inf 35 | 36 | for i in range(len(arr)): 37 | j, k = i+1, len(arr)-1 38 | while j < k: 39 | err = abs(arr[i]+arr[j]+arr[k]-target_sum) 40 | if err < err_min: 41 | err_min=err 42 | min_sum = arr[i]+arr[j]+arr[k] 43 | elif err == err_min: 44 | min_sum=min(min_sum,arr[i]+arr[j]+arr[k]) 45 | 46 | if arr[i]+arr[j]+arr[k] < target_sum: 47 | j += 1 48 | elif arr[i]+arr[j]+arr[k] > target_sum: 49 | k -= 1 50 | 51 | return min_sum 52 | 53 | 54 | 55 | #answer 56 | import math 57 | def triplet_sum_close_to_target(arr, target_sum): 58 | arr.sort() 59 | smallest_difference = math.inf 60 | for i in range(len(arr)-2): 61 | left = i + 1 62 | right = len(arr) - 1 63 | while (left < right): 64 | target_diff = target_sum - arr[i] - arr[left] - arr[right] 65 | if target_diff == 0: # we've found a triplet with an exact sum 66 | return target_sum - target_diff # return sum of all the numbers 67 | 68 | # the second part of the following 'if' is to handle the smallest sum when we have more than one solution 69 | if abs(target_diff) < abs(smallest_difference) or (abs(target_diff) == abs(smallest_difference) and target_diff > smallest_difference): 70 | smallest_difference = target_diff # save the closest and the biggest difference 71 | 72 | if target_diff > 0: 73 | left += 1 # we need a triplet with a bigger sum 74 | else: 75 | right -= 1 # we need a triplet with a smaller sum 76 | 77 | return target_sum - smallest_difference 78 | 79 | 80 | def main(): 81 | print(triplet_sum_close_to_target([-2, 0, 1, 2], 2)) 82 | print(triplet_sum_close_to_target([-3, -1, 1, 2], 1)) 83 | print(triplet_sum_close_to_target([1, 0, 1, 1], 100)) 84 | 85 | 86 | main() 87 | 88 | 89 | ''' 90 | Time complexity 91 | Sorting the array will take O(N* logN)O(N∗logN). Overall searchTriplet() will take O(N * logN + N^2), 92 | which is asymptotically equivalent to O(N^2). 93 | 94 | Space complexity 95 | The space complexity of the above algorithm will be O(N) which is required for sorting. 96 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Next Letter (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of lowercase letters sorted in ascending order, 4 | find the smallest letter in the given array greater than a given ‘key’. 5 | 6 | Assume the given array is a circular list, which means that the last letter is assumed to be connected with the first letter. 7 | This also means that the smallest letter in the given array is greater than the last letter of the array and is also the first letter of the array. 8 | 9 | Write a function to return the next letter of the given ‘key’. 10 | 11 | Example 1: 12 | 13 | Input: ['a', 'c', 'f', 'h'], key = 'f' 14 | Output: 'h' 15 | Explanation: The smallest letter greater than 'f' is 'h' in the given array. 16 | 17 | Example 2: 18 | 19 | Input: ['a', 'c', 'f', 'h'], key = 'b' 20 | Output: 'c' 21 | Explanation: The smallest letter greater than 'b' is 'c'. 22 | 23 | Example 3: 24 | 25 | Input: ['a', 'c', 'f', 'h'], key = 'm' 26 | Output: 'a' 27 | Explanation: As the array is assumed to be circular, the smallest letter greater than 'm' is 'a'. 28 | 29 | Example 4: 30 | 31 | Input: ['a', 'c', 'f', 'h'], key = 'h' 32 | Output: 'a' 33 | Explanation: As the array is assumed to be circular, the smallest letter greater than 'h' is 'a'. 34 | ''' 35 | 36 | 37 | #mycode 38 | def search_next_letter(letters, key): 39 | # TODO: Write your code here 40 | if key < letters[0] or key >= letters[-1]: 41 | return letters[0] 42 | 43 | start, end = 0, len(letters)-1 44 | while start <= end: 45 | mid = (start + end) // 2 46 | if letters[mid] < key: 47 | start = mid +1 48 | elif letters[mid] > key: 49 | end = mid -1 50 | else: 51 | return letters[mid+1 % len(letters)] 52 | return letters[start] 53 | 54 | 55 | def main(): 56 | print(search_next_letter(['a', 'c', 'f', 'h'], 'f')) 57 | print(search_next_letter(['a', 'c', 'f', 'h'], 'b')) 58 | print(search_next_letter(['a', 'c', 'f', 'h'], 'm')) 59 | 60 | 61 | main() 62 | 63 | 64 | 65 | #answer 66 | def search_next_letter(letters, key): 67 | n = len(letters) 68 | if key < letters[0] or key > letters[n - 1]: 69 | return letters[0] 70 | 71 | start, end = 0, n - 1 72 | while start <= end: 73 | mid = start + (end - start) // 2 74 | if key < letters[mid]: 75 | end = mid - 1 76 | else: # key >= letters[mid]: 77 | start = mid + 1 78 | 79 | # since the loop is running until 'start <= end', so at the end of the while loop, 'start == end+1' 80 | return letters[start % n] 81 | 82 | 83 | def main(): 84 | print(search_next_letter(['a', 'c', 'f', 'h'], 'f')) 85 | print(search_next_letter(['a', 'c', 'f', 'h'], 'b')) 86 | print(search_next_letter(['a', 'c', 'f', 'h'], 'm')) 87 | 88 | 89 | main() 90 | 91 | 92 | 93 | ''' 94 | Time complexity 95 | Since, we are reducing the search range by half at every step, 96 | this means that the time complexity of our algorithm will be O(logN)O 97 | where ‘N’ is the total elements in the given array. 98 | 99 | Space complexity 100 | The algorithm runs in constant space O(1). 101 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Level Averages in a Binary Tree (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree, populate an array to represent the averages of all of its levels. 4 | ''' 5 | 6 | #mycode 7 | from collections import deque 8 | 9 | class TreeNode: 10 | def __init__(self, val): 11 | self.val = val 12 | self.left, self.right = None, None 13 | 14 | 15 | def find_level_averages(root): 16 | result = [] 17 | # TODO: Write your code here 18 | if not root: 19 | return result 20 | queue = deque() 21 | queue.append(root) 22 | 23 | while queue: 24 | value = 0 25 | n = len(queue) 26 | for i in range(n): 27 | current = queue.popleft() 28 | value += current.val 29 | 30 | if current.left: 31 | queue.append(current.left) 32 | if current.right: 33 | queue.append(current.right) 34 | result.append(value/n) 35 | 36 | return result 37 | 38 | 39 | def main(): 40 | root = TreeNode(12) 41 | root.left = TreeNode(7) 42 | root.right = TreeNode(1) 43 | root.left.left = TreeNode(9) 44 | root.left.right = TreeNode(2) 45 | root.right.left = TreeNode(10) 46 | root.right.right = TreeNode(5) 47 | print("Level averages are: " + str(find_level_averages(root))) 48 | 49 | 50 | main() 51 | 52 | 53 | 54 | #answer 55 | from collections import deque 56 | 57 | 58 | class TreeNode: 59 | def __init__(self, val): 60 | self.val = val 61 | self.left, self.right = None, None 62 | 63 | 64 | def find_level_averages(root): 65 | result = [] 66 | if root is None: 67 | return result 68 | 69 | queue = deque() 70 | queue.append(root) 71 | while queue: 72 | levelSize = len(queue) 73 | levelSum = 0.0 74 | for _ in range(levelSize): 75 | currentNode = queue.popleft() 76 | # add the node's value to the running sum 77 | levelSum += currentNode.val 78 | # insert the children of current node to the queue 79 | if currentNode.left: 80 | queue.append(currentNode.left) 81 | if currentNode.right: 82 | queue.append(currentNode.right) 83 | 84 | # append the current level's average to the result array 85 | result.append(levelSum / levelSize) 86 | 87 | return result 88 | 89 | 90 | def main(): 91 | root = TreeNode(12) 92 | root.left = TreeNode(7) 93 | root.right = TreeNode(1) 94 | root.left.left = TreeNode(9) 95 | root.left.right = TreeNode(2) 96 | root.right.left = TreeNode(10) 97 | root.right.right = TreeNode(5) 98 | print("Level averages are: " + str(find_level_averages(root))) 99 | 100 | 101 | main() 102 | 103 | 104 | ''' 105 | Time complexity 106 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 107 | This is due to the fact that we traverse each node once. 108 | 109 | Space complexity 110 | The space complexity of the above algorithm will be O(N)O which is required for the queue. 111 | Since we can have a maximum of N/2 nodes at any level (this could happen only at the lowest level), 112 | therefore we will need O(N) space to store them in the queue. 113 | ''' -------------------------------------------------------------------------------- /14. Pattern K-way merge/Smallest Number Range (Hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given ‘M’ sorted arrays, find the smallest range that includes at least one number from each of the ‘M’ lists. 4 | 5 | Example 1: 6 | 7 | Input: L1=[1, 5, 8], L2=[4, 12], L3=[7, 8, 10] 8 | Output: [4, 7] 9 | Explanation: The range [4, 7] includes 5 from L1, 4 from L2 and 7 from L3. 10 | Example 2: 11 | 12 | Input: L1=[1, 9], L2=[4, 12], L3=[7, 10, 16] 13 | Output: [9, 12] 14 | Explanation: The range [9, 12] includes 9 from L1, 12 from L2 and 10 from L3. 15 | ''' 16 | 17 | 18 | #mycode 19 | from heapq import * 20 | import math 21 | 22 | def find_smallest_range(lists): 23 | # TODO: Write your code here 24 | heap = [] 25 | start, end = -math.inf, math.inf 26 | current_max = -math.inf 27 | for i in lists: 28 | heappush(heap,(i[0],0,i)) 29 | current_max = max(i[0], current_max) 30 | 31 | while len(heap) == len(lists): 32 | number, i, current_list = heappop(heap) 33 | 34 | if current_max - number < end - start: 35 | start = number 36 | end = current_max 37 | 38 | if i+1 < len(current_list): 39 | heappush(heap,(current_list[i+1],i+1,current_list)) 40 | current_max = max(current_max,current_list[i+1]) 41 | 42 | return [start, end] 43 | 44 | 45 | def main(): 46 | print("Smallest range is: " + 47 | str(find_smallest_range([[1, 5, 8], [4, 12], [7, 8, 10]]))) 48 | 49 | 50 | main() 51 | 52 | 53 | 54 | #answer 55 | from heapq import * 56 | import math 57 | 58 | 59 | def find_smallest_range(lists): 60 | minHeap = [] 61 | rangeStart, rangeEnd = 0, math.inf 62 | currentMaxNumber = -math.inf 63 | 64 | # put the 1st element of each array in the max heap 65 | for arr in lists: 66 | heappush(minHeap, (arr[0], 0, arr)) 67 | currentMaxNumber = max(currentMaxNumber, arr[0]) 68 | 69 | # take the smallest(top) element form the min heap, if it gives us smaller range, update the ranges 70 | # if the array of the top element has more elements, insert the next element in the heap 71 | while len(minHeap) == len(lists): 72 | num, i, arr = heappop(minHeap) 73 | if rangeEnd - rangeStart > currentMaxNumber - num: 74 | rangeStart = num 75 | rangeEnd = currentMaxNumber 76 | 77 | if len(arr) > i+1: 78 | # insert the next element in the heap 79 | heappush(minHeap, (arr[i+1], i+1, arr)) 80 | currentMaxNumber = max(currentMaxNumber, arr[i+1]) 81 | 82 | return [rangeStart, rangeEnd] 83 | 84 | 85 | def main(): 86 | print("Smallest range is: " + 87 | str(find_smallest_range([[1, 5, 8], [4, 12], [7, 8, 10]]))) 88 | 89 | 90 | main() 91 | 92 | 93 | 94 | 95 | ''' 96 | Time complexity 97 | Since, at most, we’ll be going through all the elements of all the arrays and will remove/add one element in the heap in each step, t 98 | he time complexity of the above algorithm will be O(N*logM) where ‘N’ is the total number of elements in all the ‘M’ input arrays. 99 | 100 | Space complexity 101 | The space complexity will be O(M) because, at any time, our min-heap will be store one number from all the ‘M’ input arrays. 102 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Order-agnostic Binary Search (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a sorted array of numbers, find if a given number ‘key’ is present in the array. Though we know that the array is sorted, we don’t know if it’s sorted in ascending or descending order. You should assume that the array can have duplicates. 4 | 5 | Write a function to return the index of the ‘key’ if it is present in the array, otherwise return -1. 6 | 7 | Example 1: 8 | 9 | Input: [4, 6, 10], key = 10 10 | Output: 2 11 | 12 | Example 2: 13 | 14 | Input: [1, 2, 3, 4, 5, 6, 7], key = 5 15 | Output: 4 16 | 17 | Example 3: 18 | 19 | Input: [10, 6, 4], key = 10 20 | Output: 0 21 | 22 | Example 4: 23 | 24 | Input: [10, 6, 4], key = 4 25 | Output: 2 26 | ''' 27 | 28 | 29 | #mycode 30 | def binary_search(arr, key): 31 | # TODO: Write your code here 32 | if arr[0] == arr[-1]: 33 | return 0 34 | 35 | if arr[0] < arr[-1]: 36 | start = 0 37 | end = len(arr) -1 38 | 39 | while start <= end: 40 | index = (start+end)//2 41 | if arr[index] < key: 42 | start = index + 1 43 | elif arr[index] > key: 44 | end = index - 1 45 | else: 46 | return index 47 | 48 | if arr[0] > arr[-1]: 49 | start = 0 50 | end = len(arr) -1 51 | 52 | while start <= end: 53 | index = (start+end)//2 54 | if arr[index] > key: 55 | start = index + 1 56 | elif arr[index] < key: 57 | end = index - 1 58 | else: 59 | return index 60 | 61 | return -1 62 | 63 | def main(): 64 | print(binary_search([4, 6, 10], 10)) 65 | print(binary_search([1, 2, 3, 4, 5, 6, 7], 5)) 66 | print(binary_search([10, 6, 4], 10)) 67 | print(binary_search([10, 6, 4], 4)) 68 | 69 | 70 | main() 71 | 72 | 73 | #answer 74 | def binary_search(arr, key): 75 | start, end = 0, len(arr) - 1 76 | isAscending = arr[start] < arr[end] 77 | while start <= end: 78 | # calculate the middle of the current range 79 | mid = start + (end - start) // 2 80 | 81 | if key == arr[mid]: 82 | return mid 83 | 84 | if isAscending: # ascending order 85 | if key < arr[mid]: 86 | end = mid - 1 # the 'key' can be in the first half 87 | else: # key > arr[mid] 88 | start = mid + 1 # the 'key' can be in the second half 89 | else: # descending order 90 | if key > arr[mid]: 91 | end = mid - 1 # the 'key' can be in the first half 92 | else: # key < arr[mid] 93 | start = mid + 1 # the 'key' can be in the second half 94 | 95 | return -1 # element not found 96 | 97 | 98 | def main(): 99 | print(binary_search([4, 6, 10], 10)) 100 | print(binary_search([1, 2, 3, 4, 5, 6, 7], 5)) 101 | print(binary_search([10, 6, 4], 10)) 102 | print(binary_search([10, 6, 4], 4)) 103 | 104 | 105 | main() 106 | 107 | 108 | ''' 109 | Time complexity 110 | Since, we are reducing the search range by half at every step, 111 | this means that the time complexity of our algorithm will be O(logN) where ‘N’ is the total elements in the given array. 112 | 113 | Space complexity 114 | The algorithm runs in constant space O(1). 115 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Level Order Successor (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree and a node, find the level order successor of the given node in the tree. 4 | The level order successor is the node that appears right after the given node in the level order traversal. 5 | ''' 6 | 7 | 8 | #mycode 9 | from collections import deque 10 | 11 | 12 | class TreeNode: 13 | def __init__(self, val): 14 | self.val = val 15 | self.left, self.right = None, None 16 | 17 | 18 | def find_successor(root, key): 19 | # TODO: Write your code here 20 | if not root: 21 | return None 22 | 23 | queue = deque() 24 | queue.append(root) 25 | 26 | flag = False 27 | while queue: 28 | 29 | 30 | for i in range(len(queue)): 31 | current = queue.popleft() 32 | if flag: 33 | return current 34 | 35 | if current.val == key: 36 | flag = True 37 | 38 | if current.left: 39 | queue.append(current.left) 40 | if current.right: 41 | queue.append(current.right) 42 | return None 43 | 44 | def main(): 45 | root = TreeNode(12) 46 | root.left = TreeNode(7) 47 | root.right = TreeNode(1) 48 | root.left.left = TreeNode(9) 49 | root.right.left = TreeNode(10) 50 | root.right.right = TreeNode(5) 51 | result = find_successor(root, 12) 52 | if result: 53 | print(result.val) 54 | result = find_successor(root, 10) 55 | if result: 56 | print(result.val) 57 | 58 | 59 | main() 60 | 61 | 62 | 63 | #answer 64 | from collections import deque 65 | 66 | 67 | class TreeNode: 68 | def __init__(self, val): 69 | self.val = val 70 | self.left, self.right = None, None 71 | 72 | 73 | def find_successor(root, key): 74 | if root is None: 75 | return None 76 | 77 | queue = deque() 78 | queue.append(root) 79 | while queue: 80 | currentNode = queue.popleft() 81 | # insert the children of current node in the queue 82 | if currentNode.left: 83 | queue.append(currentNode.left) 84 | if currentNode.right: 85 | queue.append(currentNode.right) 86 | 87 | # break if we have found the key 88 | if currentNode.val == key: 89 | break 90 | 91 | return queue[0] if queue else None 92 | 93 | 94 | def main(): 95 | root = TreeNode(12) 96 | root.left = TreeNode(7) 97 | root.right = TreeNode(1) 98 | root.left.left = TreeNode(9) 99 | root.right.left = TreeNode(10) 100 | root.right.right = TreeNode(5) 101 | result = find_successor(root, 12) 102 | if result: 103 | print(result.val) 104 | result = find_successor(root, 9) 105 | if result: 106 | print(result.val) 107 | 108 | 109 | main() 110 | 111 | 112 | ''' 113 | Time complexity 114 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 115 | This is due to the fact that we traverse each node once. 116 | 117 | Space complexity 118 | The space complexity of the above algorithm will be O(N) which is required for the queue. 119 | Since we can have a maximum of N/2 nodes at any level (this could happen only at the lowest level), 120 | therefore we will need O(N) space to store them in the queue. 121 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/No-repeat Substring (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a string, find the length of the longest substring which has no repeating characters. 4 | 5 | Example 1: 6 | 7 | Input: String="aabccbb" 8 | Output: 3 9 | Explanation: The longest substring without any repeating characters is "abc". 10 | 11 | Example 2: 12 | 13 | Input: String="abbbb" 14 | Output: 2 15 | Explanation: The longest substring without any repeating characters is "ab". 16 | 17 | Example 3: 18 | 19 | Input: String="abccde" 20 | Output: 3 21 | Explanation: Longest substrings without any repeating characters are "abc" & "cde". 22 | ''' 23 | 24 | #mycode 25 | def non_repeat_substring(str): 26 | max_len, win_start = 0, 0 27 | dict_str={} 28 | 29 | for win_end in range(len(str)): 30 | if str[win_end] not in dict_str: 31 | dict_str[str[win_end]] = 1 32 | else: 33 | dict_str[str[win_end]] += 1 34 | 35 | while len(dict_str) < sum(dict_str.values()): 36 | if dict_str[str[win_start]] == 1: 37 | del dict_str[str[win_start]] 38 | else: 39 | dict_str[str[win_start]] -= 1 40 | win_start += 1 41 | 42 | if len(dict_str) == sum(dict_str.values()): 43 | max_len=max(max_len, len(dict_str)) 44 | return max_len 45 | 46 | 47 | 48 | #answer 49 | def non_repeat_substring(str): 50 | window_start = 0 51 | max_length = 0 52 | char_index_map = {} 53 | 54 | # try to extend the range [windowStart, windowEnd] 55 | for window_end in range(len(str)): 56 | right_char = str[window_end] 57 | # if the map already contains the 'right_char', shrink the window from the beginning so that 58 | # we have only one occurrence of 'right_char' 59 | if right_char in char_index_map: 60 | # this is tricky; in the current window, we will not have any 'right_char' after its previous index 61 | # and if 'window_start' is already ahead of the last index of 'right_char', we'll keep 'window_start' 62 | window_start = max(window_start, char_index_map[right_char] + 1) 63 | # insert the 'right_char' into the map 64 | char_index_map[right_char] = window_end 65 | # remember the maximum length so far 66 | max_length = max(max_length, window_end - window_start + 1) 67 | return max_length 68 | 69 | 70 | def main(): 71 | print("Length of the longest substring: " + str(non_repeat_substring("aabccbb"))) 72 | print("Length of the longest substring: " + str(non_repeat_substring("abbbb"))) 73 | print("Length of the longest substring: " + str(non_repeat_substring("abccde"))) 74 | 75 | 76 | main() 77 | 78 | 79 | ''' 80 | Time Complexity 81 | The time complexity of the above algorithm will be O(N) where ‘N’ is the number of characters in the input string. 82 | 83 | Space Complexity 84 | The space complexity of the algorithm will be O(K) where KK is the number of distinct characters in the input string. 85 | This also means K<=N, because in the worst case, the whole string might not have any repeating character so the entire string will be added to the HashMap. 86 | Having said that, since we can expect a fixed set of characters in the input string (e.g., 26 for English letters), we can say that the algorithm runs in fixed space O(1); in this case, we can use a fixed-size array instead of the HashMap. 87 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Longest Substring with Same Letters after Replacement (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a string with lowercase letters only, if you are allowed to replace no more than ‘k’ letters with any letter, find the length of the longest substring having the same letters after replacement. 4 | 5 | Example 1: 6 | 7 | Input: String="aabccbb", k=2 8 | Output: 5 9 | Explanation: Replace the two 'c' with 'b' to have a longest repeating substring "bbbbb". 10 | 11 | Example 2: 12 | 13 | Input: String="abbcb", k=1 14 | Output: 4 15 | Explanation: Replace the 'c' with 'b' to have a longest repeating substring "bbbb". 16 | 17 | Example 3: 18 | 19 | Input: String="abccde", k=1 20 | Output: 3 21 | Explanation: Replace the 'b' or 'd' with 'c' to have the longest repeating substring "ccc". 22 | ''' 23 | 24 | #mycode 25 | def length_of_longest_substring(str, k): 26 | # TODO: Write your code here 27 | win_start, max_len, cnt = 0, 0, 0 28 | dict_str={} 29 | 30 | for win_end in range(len(str)): 31 | if str[win_end] not in dict_str: 32 | dict_str[str[win_end]] = 1 33 | else: 34 | dict_str[str[win_end]] += 1 35 | 36 | cnt=max(dict_str.values()) 37 | while win_end - win_start + 1 -cnt > k: 38 | dict_str[str[win_start]] -= 1 39 | win_start +=1 40 | 41 | max_len=max(max_len, win_end - win_start + 1) 42 | 43 | return max_len 44 | 45 | 46 | #answer 47 | def length_of_longest_substring(str, k): 48 | window_start, max_length, max_repeat_letter_count = 0, 0, 0 49 | frequency_map = {} 50 | 51 | # Try to extend the range [window_start, window_end] 52 | for window_end in range(len(str)): 53 | right_char = str[window_end] 54 | if right_char not in frequency_map: 55 | frequency_map[right_char] = 0 56 | frequency_map[right_char] += 1 57 | max_repeat_letter_count = max( 58 | max_repeat_letter_count, frequency_map[right_char]) 59 | 60 | # Current window size is from window_start to window_end, overall we have a letter which is 61 | # repeating 'max_repeat_letter_count' times, this means we can have a window which has one letter 62 | # repeating 'max_repeat_letter_count' times and the remaining letters we should replace. 63 | # if the remaining letters are more than 'k', it is the time to shrink the window as we 64 | # are not allowed to replace more than 'k' letters 65 | if (window_end - window_start + 1 - max_repeat_letter_count) > k: 66 | left_char = str[window_start] 67 | frequency_map[left_char] -= 1 68 | window_start += 1 69 | 70 | max_length = max(max_length, window_end - window_start + 1) 71 | return max_length 72 | 73 | 74 | def main(): 75 | print(length_of_longest_substring("aabccbb", 2)) 76 | print(length_of_longest_substring("abbcb", 1)) 77 | print(length_of_longest_substring("abccde", 1)) 78 | 79 | 80 | main() 81 | 82 | 83 | 84 | ''' 85 | Time Complexity 86 | The time complexity of the above algorithm will be O(N) where ‘N’ is the number of letters in the input string. 87 | 88 | Space Complexity 89 | As we are expecting only the lower case letters in the input string, we can conclude that the space complexity will be O(26), to store each letter’s frequency in the HashMap, which is asymptotically equal to O(1). 90 | 91 | 92 | ''' 93 | -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Problem Challenge 3 - Minimum Window Sort (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Minimum Window Sort (medium) 5 | 6 | Given an array, find the length of the smallest subarray in it which when sorted will sort the whole array. 7 | 8 | Example 1: 9 | 10 | Input: [1, 2, 5, 3, 7, 10, 9, 12] 11 | Output: 5 12 | Explanation: We need to sort only the subarray [5, 3, 7, 10, 9] to make the whole array sorted 13 | 14 | Example 2: 15 | 16 | Input: [1, 3, 2, 0, -1, 7, 10] 17 | Output: 5 18 | Explanation: We need to sort only the subarray [1, 3, 2, 0, -1] to make the whole array sorted 19 | 20 | Example 3: 21 | 22 | Input: [1, 2, 3] 23 | Output: 0 24 | Explanation: The array is already sorted 25 | 26 | Example 4: 27 | 28 | Input: [3, 2, 1] 29 | Output: 3 30 | Explanation: The whole array needs to be sorted. 31 | ''' 32 | 33 | 34 | #mycode 35 | import math 36 | 37 | def shortest_window_sort(arr): 38 | # TODO: Write your code here 39 | 40 | left, right = 1, len(arr) -1 41 | max_num, min_num = -math.inf, math.inf 42 | 43 | while left < len(arr)-1 and arr[left] < arr[left+1]: 44 | left += 1 45 | 46 | while right > 0 and arr[right] > arr[right-1]: 47 | right -= 1 48 | 49 | for i in range(left, right+1): 50 | max_num = max(max_num, arr[i]) 51 | min_num = min(min_num, arr[i]) 52 | 53 | for i in range(left,-1,-1): 54 | if arr[i] >= min_num: 55 | left = i 56 | 57 | for i in range(right, len(arr)): 58 | if arr[i] <= max_num: 59 | right = i 60 | 61 | if right == 0: 62 | return 0 63 | 64 | return right-left+1 65 | 66 | 67 | 68 | #answer 69 | import math 70 | 71 | 72 | def shortest_window_sort(arr): 73 | low, high = 0, len(arr) - 1 74 | # find the first number out of sorting order from the beginning 75 | while (low < len(arr) - 1 and arr[low] <= arr[low + 1]): 76 | low += 1 77 | 78 | if low == len(arr) - 1: # if the array is sorted 79 | return 0 80 | 81 | # find the first number out of sorting order from the end 82 | while (high > 0 and arr[high] >= arr[high - 1]): 83 | high -= 1 84 | 85 | # find the maximum and minimum of the subarray 86 | subarray_max = -math.inf 87 | subarray_min = math.inf 88 | for k in range(low, high+1): 89 | subarray_max = max(subarray_max, arr[k]) 90 | subarray_min = min(subarray_min, arr[k]) 91 | 92 | # extend the subarray to include any number which is bigger than the minimum of the subarray 93 | while (low > 0 and arr[low-1] > subarray_min): 94 | low -= 1 95 | # extend the subarray to include any number which is smaller than the maximum of the subarray 96 | while (high < len(arr)-1 and arr[high+1] < subarray_max): 97 | high += 1 98 | 99 | return high - low + 1 100 | 101 | 102 | def main(): 103 | print(shortest_window_sort([1, 2, 5, 3, 7, 10, 9, 12])) 104 | print(shortest_window_sort([1, 3, 2, 0, -1, 7, 10])) 105 | print(shortest_window_sort([1, 2, 3])) 106 | print(shortest_window_sort([3, 2, 1])) 107 | 108 | 109 | main() 110 | 111 | ''' 112 | Time complexity 113 | The time complexity of the above algorithm will be O(N)O(N). 114 | 115 | Space complexity 116 | The algorithm runs in constant space O(1)O(1). 117 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Reverse Level Order Traversal (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree, populate an array to represent its level-by-level traversal in reverse order, i.e., the lowest level comes first. 4 | You should populate the values of all nodes in each level from left to right in separate sub-arrays. 5 | ''' 6 | 7 | #mycode 8 | from collections import deque 9 | 10 | class TreeNode: 11 | def __init__(self, val): 12 | self.val = val 13 | self.left, self.right = None, None 14 | 15 | def traverse(root): 16 | result = deque() 17 | # TODO: Write your code here 18 | if not root: 19 | return result 20 | 21 | queue = deque() 22 | queue.append(root) 23 | 24 | while queue: 25 | current = [] 26 | for i in range(len(queue)): 27 | current_node = queue.popleft() 28 | current.append(current_node.val) 29 | if current_node.left: 30 | queue.append(current_node.left) 31 | if current_node.right: 32 | queue.append(current_node.right) 33 | 34 | result.appendleft(current) 35 | return result 36 | 37 | def main(): 38 | root = TreeNode(12) 39 | root.left = TreeNode(7) 40 | root.right = TreeNode(1) 41 | root.left.left = TreeNode(9) 42 | root.right.left = TreeNode(10) 43 | root.right.right = TreeNode(5) 44 | print("Reverse level order traversal: " + str(traverse(root))) 45 | 46 | 47 | main() 48 | 49 | 50 | 51 | #answer 52 | from collections import deque 53 | 54 | 55 | class TreeNode: 56 | def __init__(self, val): 57 | self.val = val 58 | self.left, self.right = None, None 59 | 60 | 61 | def traverse(root): 62 | result = deque() 63 | if root is None: 64 | return result 65 | 66 | queue = deque() 67 | queue.append(root) 68 | while queue: 69 | levelSize = len(queue) 70 | currentLevel = [] 71 | for _ in range(levelSize): 72 | currentNode = queue.popleft() 73 | # add the node to the current level 74 | currentLevel.append(currentNode.val) 75 | # insert the children of current node in the queue 76 | if currentNode.left: 77 | queue.append(currentNode.left) 78 | if currentNode.right: 79 | queue.append(currentNode.right) 80 | 81 | result.appendleft(currentLevel) 82 | 83 | return result 84 | 85 | 86 | def main(): 87 | root = TreeNode(12) 88 | root.left = TreeNode(7) 89 | root.right = TreeNode(1) 90 | root.left.left = TreeNode(9) 91 | root.right.left = TreeNode(10) 92 | root.right.right = TreeNode(5) 93 | print("Reverse level order traversal: " + str(traverse(root))) 94 | 95 | 96 | main() 97 | 98 | 99 | 100 | ''' 101 | Time complexity 102 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 103 | This is due to the fact that we traverse each node once. 104 | 105 | Space complexity 106 | The space complexity of the above algorithm will be O(N) as we need to return a list containing the level order traversal. 107 | We will also need O(N) space for the queue. Since we can have a maximum of N/2 nodes at any level (this could happen only at the lowest level), 108 | therefore we will need O(N) space to store them in the queue. 109 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Pair with Target Sum (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of sorted numbers and a target sum, find a pair in the array whose sum is equal to the given target. 4 | 5 | Write a function to return the indices of the two numbers (i.e. the pair) such that they add up to the given target. 6 | 7 | Example 1: 8 | 9 | Input: [1, 2, 3, 4, 6], target=6 10 | Output: [1, 3] 11 | Explanation: The numbers at index 1 and 3 add up to 6: 2+4=6 12 | 13 | Example 2: 14 | 15 | Input: [2, 5, 9, 11], target=11 16 | Output: [0, 2] 17 | Explanation: The numbers at index 0 and 2 add up to 11: 2+9=11 18 | ''' 19 | 20 | #mycode 21 | def pair_with_targetsum(arr, target_sum): 22 | # TODO: Write your code here 23 | i,j = 0, len(arr)-1 24 | while i < j: 25 | if arr[i]+arr[j] < target_sum: 26 | i+=1 27 | elif arr[i]+arr[j] > target_sum: 28 | j-=1 29 | else: 30 | return [i, j] 31 | 32 | return[-1,-1] 33 | 34 | 35 | #answer 36 | def pair_with_targetsum(arr, target_sum): 37 | left, right = 0, len(arr) - 1 38 | while(left < right): 39 | current_sum = arr[left] + arr[right] 40 | if current_sum == target_sum: 41 | return [left, right] 42 | 43 | if target_sum > current_sum: 44 | left += 1 # we need a pair with a bigger sum 45 | else: 46 | right -= 1 # we need a pair with a smaller sum 47 | return [-1, -1] 48 | 49 | 50 | def main(): 51 | print(pair_with_targetsum([1, 2, 3, 4, 6], 6)) 52 | print(pair_with_targetsum([2, 5, 9, 11], 11)) 53 | 54 | 55 | main() 56 | 57 | 58 | 59 | ''' 60 | Time Complexity 61 | The time complexity of the above algorithm will be O(N), 62 | where ‘N’ is the total number of elements in the given array. 63 | 64 | Space Complexity 65 | The algorithm runs in constant space O(1). 66 | 67 | An Alternate approach 68 | Instead of using a two-pointer or a binary search approach, 69 | we can utilize a HashTable to search for the required pair. 70 | We can iterate through the array one number at a time. 71 | Let’s say during our iteration we are at number ‘X’, 72 | so we need to find ‘Y’ such that “X + Y == TargetX+Y==Target”. 73 | We will do two things here: 74 | 75 | Search for ‘Y’ (which is equivalent to “Target - XTarget−X”) in the HashTable. 76 | If it is there, we have found the required pair. 77 | Otherwise, insert “X” in the HashTable, so that we can search it for the later numbers. 78 | Here is what our algorithm will look like: 79 | ''' 80 | 81 | def pair_with_targetsum(arr, target_sum): 82 | nums = {} # to store numbers and their indices 83 | for i, num in enumerate(arr): 84 | if target_sum - num in nums: 85 | return [nums[target_sum - num], i] 86 | else: 87 | nums[arr[i]] = i 88 | return [-1, -1] 89 | 90 | 91 | def main(): 92 | print(pair_with_targetsum([1, 2, 3, 4, 6], 6)) 93 | print(pair_with_targetsum([2, 5, 9, 11], 11)) 94 | 95 | 96 | main() 97 | 98 | ''' 99 | Time Complexity 100 | The time complexity of the above algorithm will be O(N), 101 | where ‘N’ is the total number of elements in the given array. 102 | 103 | Space Complexity 104 | The space complexity will also be O(N), as, in the worst case, we will be pushing ‘N’ numbers in the HashTable. 105 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Problem Challenge 4 - Words Concatenation (hard) .py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 4 3 | 4 | Words Concatenation (hard) 5 | Given a string and a list of words, find all the starting indices of substrings in the given string that are a concatenation of all the given words exactly once without any overlapping of words. It is given that all words are of the same length. 6 | 7 | Example 1: 8 | 9 | Input: String="catfoxcat", Words=["cat", "fox"] 10 | Output: [0, 3] 11 | Explanation: The two substring containing both the words are "catfox" & "foxcat". 12 | 13 | Example 2: 14 | 15 | Input: String="catcatfoxfox", Words=["cat", "fox"] 16 | Output: [3] 17 | Explanation: The only substring containing both the words is "catfox". 18 | ''' 19 | 20 | #mycode 21 | def find_word_concatenation(str, words): 22 | result_indices = [] 23 | word_count = len(words) 24 | word_len=len(words[0]) 25 | 26 | for i in range(len(str)-word_count*word_len+1): 27 | cnt = 0 28 | curr=str[i:i+word_count*word_len] 29 | 30 | for j in range(word_count): 31 | 32 | if words[j] not in curr: 33 | break 34 | else: 35 | cnt += 1 36 | 37 | if cnt== word_count: 38 | result_indices.append(i) 39 | 40 | return result_indices 41 | 42 | 43 | 44 | #answer 45 | def find_word_concatenation(str, words): 46 | if len(words) == 0 or len(words[0]) == 0: 47 | return [] 48 | 49 | word_frequency = {} 50 | 51 | for word in words: 52 | if word not in word_frequency: 53 | word_frequency[word] = 0 54 | word_frequency[word] += 1 55 | 56 | result_indices = [] 57 | words_count = len(words) 58 | word_length = len(words[0]) 59 | 60 | for i in range((len(str) - words_count * word_length)+1): 61 | words_seen = {} 62 | for j in range(0, words_count): 63 | next_word_index = i + j * word_length 64 | # Get the next word from the string 65 | word = str[next_word_index: next_word_index + word_length] 66 | if word not in word_frequency: # Break if we don't need this word 67 | break 68 | 69 | # Add the word to the 'words_seen' map 70 | if word not in words_seen: 71 | words_seen[word] = 0 72 | words_seen[word] += 1 73 | 74 | # No need to process further if the word has higher frequency than required 75 | if words_seen[word] > word_frequency.get(word, 0): 76 | break 77 | 78 | if j + 1 == words_count: # Store index if we have found all the words 79 | result_indices.append(i) 80 | 81 | return result_indices 82 | 83 | 84 | def main(): 85 | print(find_word_concatenation("catfoxcat", ["cat", "fox"])) 86 | print(find_word_concatenation("catcatfoxfox", ["cat", "fox"])) 87 | 88 | 89 | main() 90 | 91 | 92 | ''' 93 | Time Complexity 94 | The time complexity of the above algorithm will be O(N * M * Len) where ‘N’ is the number of characters in the given string, 95 | ‘M’ is the total number of words, and ‘Len’ is the length of a word. 96 | 97 | Space Complexity 98 | The space complexity of the algorithm is O(M) since at most, we will be storing all the words in the two HashMaps. 99 | In the worst case, we also need O(N) space for the resulting list. So, the overall space complexity of the algorithm will be O(M+N). 100 | ''' -------------------------------------------------------------------------------- /14. Pattern K-way merge/Problem Challenge 1 - K Pairs with Largest Sums (Hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | K Pairs with Largest Sums (Hard) 4 | Given two sorted arrays in descending order, find ‘K’ pairs with the largest sum where each pair consists of numbers from both the arrays. 5 | 6 | Example 1: 7 | 8 | Input: L1=[9, 8, 2], L2=[6, 3, 1], K=3 9 | Output: [9, 3], [9, 6], [8, 6] 10 | Explanation: These 3 pairs have the largest sum. No other pair has a sum larger than any of these. 11 | 12 | Example 2: 13 | 14 | Input: L1=[5, 2, 1], L2=[2, -1], K=3 15 | Output: [5, 2], [5, -1], [2, 2] 16 | ''' 17 | 18 | 19 | #mycode 20 | from heapq import * 21 | def find_k_largest_pairs(nums1, nums2, k): 22 | result = [] 23 | # TODO: Write your code here 24 | heap = [] 25 | 26 | for i in range(min(k, len(nums1))): 27 | for j in range(min(k, len(nums2))): 28 | if len(heap) < k: 29 | heappush(heap,(nums1[i]+nums2[j],[nums1[i],nums2[j]])) 30 | else: 31 | if nums1[i]+nums2[j] > heap[0][0]: 32 | heappop(heap) 33 | heappush(heap,(nums1[i]+nums2[j],[nums1[i], nums2[j]])) 34 | 35 | while heap: 36 | _, ans = heappop(heap) 37 | result.append(ans) 38 | 39 | return result 40 | 41 | 42 | def main(): 43 | print("Pairs with largest sum are: " + 44 | str(find_k_largest_pairs([9, 8, 2], [6, 3, 1], 3))) 45 | 46 | 47 | main() 48 | 49 | 50 | 51 | 52 | #answer 53 | from __future__ import print_function 54 | from heapq import * 55 | 56 | 57 | def find_k_largest_pairs(nums1, nums2, k): 58 | minHeap = [] 59 | for i in range(0, min(k, len(nums1))): 60 | for j in range(min(k, len(nums2))): 61 | if len(minHeap) < k: 62 | heappush(minHeap, (nums1[i] + nums2[j], i, j)) 63 | else: 64 | # if the sum of the two numbers from the two arrays is smaller than the smallest(top) 65 | # element of the heap, we can 'break' here. Since the arrays are sorted in the 66 | # descending order, we'll not be able to find a pair with a higher sum moving forward 67 | if nums1[i] + nums2[j] < minHeap[0][0]: 68 | break 69 | else: # we have a pair with a larger sum, remove top and insert this pair in the heap 70 | heappop(minHeap) 71 | heappush(minHeap, (nums1[i] + nums2[j], i, j)) 72 | 73 | result = [] 74 | for (num, i, j) in minHeap: 75 | result.append([nums1[i], nums2[j]]) 76 | 77 | return result 78 | 79 | 80 | def main(): 81 | print("Pairs with largest sum are: " + 82 | str(find_k_largest_pairs([9, 8, 2], [6, 3, 1], 3))) 83 | 84 | 85 | main() 86 | 87 | 88 | 89 | 90 | 91 | ''' 92 | Time complexity 93 | Since, at most, we’ll be going through all the elements of both arrays and we will add/remove one element in the heap in each step, 94 | the time complexity of the above algorithm will be O(N*M*logK) where ‘N’ and ‘M’ are the total number of elements in both arrays, respectively. 95 | 96 | If we assume that both arrays have at least ‘K’ elements then the time complexity can be simplified to O(K^2logK), 97 | because we are not iterating more than ‘K’ elements in both arrays. 98 | 99 | Space complexity 100 | The space complexity will be O(K) because, at any time, our Min Heap will be storing ‘K’ largest pairs. 101 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Rearrange String (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a string, find if its letters can be rearranged in such a way that no two same characters ome next to each other. 4 | 5 | Example 1: 6 | 7 | Input: "aappp" 8 | Output: "papap" 9 | Explanation: In "papap", none of the repeating characters come next to each other. 10 | 11 | Example 2: 12 | 13 | Input: "Programming" 14 | Output: "rgmrgmPiano" or "gmringmrPoa" or "gmrPagimnor", etc. 15 | Explanation: None of the repeating characters come next to each other. 16 | 17 | Example 3: 18 | 19 | Input: "aapa" 20 | Output: "" 21 | Explanation: In all arrangements of "aapa", atleast two 'a' will come together e.g., "apaa", "paaa". 22 | ''' 23 | 24 | 25 | #mycode 26 | from heapq import * 27 | 28 | 29 | def rearrange_string(str): 30 | # TODO: Write your code here 31 | mapping = {} 32 | for i in str: 33 | mapping[i] = mapping.get(i,0) +1 34 | 35 | heap=[] 36 | for i, freq in mapping.items(): 37 | heappush(heap,(-freq,i)) 38 | 39 | result ='' 40 | while heap: 41 | most_freq, most_i = heappop(heap) 42 | result += most_i 43 | if -most_freq > 1: 44 | heappush(heap,(most_freq+1,most_i)) 45 | if heap: 46 | sec_freq, sec_i =heappop(heap) 47 | if sec_i == most_i: 48 | return '' 49 | else: 50 | heappush(heap,(sec_freq,sec_i)) 51 | return result 52 | 53 | 54 | def main(): 55 | print("Rearranged string: " + rearrange_string("aappp")) 56 | print("Rearranged string: " + rearrange_string("Programming")) 57 | print("Rearranged string: " + rearrange_string("aapa")) 58 | 59 | 60 | main() 61 | 62 | 63 | 64 | 65 | #answer 66 | from heapq import * 67 | 68 | 69 | def rearrange_string(str): 70 | charFrequencyMap = {} 71 | for char in str: 72 | charFrequencyMap[char] = charFrequencyMap.get(char, 0) + 1 73 | 74 | maxHeap = [] 75 | # add all characters to the max heap 76 | for char, frequency in charFrequencyMap.items(): 77 | heappush(maxHeap, (-frequency, char)) 78 | 79 | previousChar, previousFrequency = None, 0 80 | resultString = [] 81 | while maxHeap: 82 | frequency, char = heappop(maxHeap) 83 | # add the previous entry back in the heap if its frequency is greater than zero 84 | if previousChar and -previousFrequency > 0: 85 | heappush(maxHeap, (previousFrequency, previousChar)) 86 | # append the current character to the result string and decrement its count 87 | resultString.append(char) 88 | previousChar = char 89 | previousFrequency = frequency+1 # decrement the frequency 90 | 91 | # if we were successful in appending all the characters to the result string, return it 92 | return ''.join(resultString) if len(resultString) == len(str) else "" 93 | 94 | 95 | def main(): 96 | print("Rearranged string: " + rearrange_string("aappp")) 97 | print("Rearranged string: " + rearrange_string("Programming")) 98 | print("Rearranged string: " + rearrange_string("aapa")) 99 | 100 | 101 | main() 102 | 103 | 104 | ''' 105 | Time complexity 106 | The time complexity of the above algorithm is O(N*logN) where ‘N’ is the number of characters in the input string. 107 | 108 | Space complexity 109 | The space complexity will be O(N), as in the worst case, we need to store all the ‘N’ characters in the HashMap. 110 | ''' -------------------------------------------------------------------------------- /03. Pattern Fast & Slow pointers/LinkedList Cycle (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given the head of a Singly LinkedList, write a function to determine if the LinkedList has a cycle in it or not. 4 | ''' 5 | 6 | class Node: 7 | def __init__(self, value, next=None): 8 | self.value = value 9 | self.next = next 10 | 11 | 12 | def has_cycle(head): 13 | # TODO: Write your code here 14 | slow, fast = head, head 15 | while fast is not None and fast.next is not None: 16 | fast=fast.next.next 17 | slow=slow.next 18 | if slow == fast: 19 | return True 20 | 21 | return False 22 | 23 | 24 | def main(): 25 | head = Node(1) 26 | head.next = Node(2) 27 | head.next.next = Node(3) 28 | head.next.next.next = Node(4) 29 | head.next.next.next.next = Node(5) 30 | head.next.next.next.next.next = Node(6) 31 | print("LinkedList has cycle: " + str(has_cycle(head))) 32 | 33 | head.next.next.next.next.next.next = head.next.next 34 | print("LinkedList has cycle: " + str(has_cycle(head))) 35 | 36 | head.next.next.next.next.next.next = head.next.next.next 37 | print("LinkedList has cycle: " + str(has_cycle(head))) 38 | 39 | 40 | main() 41 | 42 | 43 | ''' 44 | Time Complexity 45 | As we have concluded above, once the slow pointer enters the cycle, the fast pointer will meet the slow pointer in the same loop. 46 | Therefore, the time complexity of our algorithm will be O(N) where ‘N’ is the total number of nodes in the LinkedList. 47 | 48 | Space Complexity # 49 | The algorithm runs in constant space O(1). 50 | ''' 51 | 52 | ''' 53 | Similar Problems 54 | Problem 1: Given the head of a LinkedList with a cycle, find the length of the cycle. 55 | 56 | Solution: We can use the above solution to find the cycle in the LinkedList. 57 | Once the fast and slow pointers meet, we can save the slow pointer and iterate the whole cycle with another pointer until we see the slow pointer again to find the length of the cycle. 58 | 59 | Here is what our algorithm will look like: 60 | ''' 61 | 62 | class Node: 63 | def __init__(self, value, next=None): 64 | self.value = value 65 | self.next = next 66 | 67 | 68 | def find_cycle_length(head): 69 | slow, fast = head, head 70 | while fast is not None and fast.next is not None: 71 | fast = fast.next.next 72 | slow = slow.next 73 | if slow == fast: # found the cycle 74 | return calculate_cycle_length(slow) 75 | 76 | return 0 77 | 78 | 79 | def calculate_cycle_length(slow): 80 | current = slow 81 | cycle_length = 0 82 | while True: 83 | current = current.next 84 | cycle_length += 1 85 | if current == slow: 86 | break 87 | return cycle_length 88 | 89 | 90 | def main(): 91 | head = Node(1) 92 | head.next = Node(2) 93 | head.next.next = Node(3) 94 | head.next.next.next = Node(4) 95 | head.next.next.next.next = Node(5) 96 | head.next.next.next.next.next = Node(6) 97 | head.next.next.next.next.next.next = head.next.next 98 | print("LinkedList cycle length: " + str(find_cycle_length(head))) 99 | 100 | head.next.next.next.next.next.next = head.next.next.next 101 | print("LinkedList cycle length: " + str(find_cycle_length(head))) 102 | 103 | 104 | main() 105 | 106 | ''' 107 | Time and Space Complexity: 108 | The above algorithm runs in O(N) time complexity and O(1) space complexity. 109 | ''' -------------------------------------------------------------------------------- /14. Pattern K-way merge/Kth Smallest Number in M Sorted Lists (Medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given ‘M’ sorted arrays, find the K’th smallest number among all the arrays. 4 | 5 | Example 1: 6 | 7 | Input: L1=[2, 6, 8], L2=[3, 6, 7], L3=[1, 3, 4], K=5 8 | Output: 4 9 | Explanation: The 5th smallest number among all the arrays is 4, this can be verified from the merged 10 | list of all the arrays: [1, 2, 3, 3, 4, 6, 6, 7, 8] 11 | 12 | Example 2: 13 | 14 | Input: L1=[5, 8, 9], L2=[1, 7], K=3 15 | Output: 7 16 | Explanation: The 3rd smallest number among all the arrays is 7. 17 | ''' 18 | 19 | #mycode 20 | from heapq import * 21 | 22 | def find_Kth_smallest(lists, k): 23 | number = -1 24 | # TODO: Write your code here 25 | result = [] 26 | for i in range(len(lists)): 27 | heappush(result,(lists[i][0], 0, lists[i])) 28 | 29 | count = 0 30 | while result: 31 | number, i, cur_list = heappop(result) 32 | count += 1 33 | if count == k: 34 | return number 35 | 36 | if i+1 < len(cur_list): 37 | heappush(result, (cur_list[i+1],i+1,cur_list)) 38 | 39 | def main(): 40 | print("Kth smallest number is: " + 41 | str(find_Kth_smallest([[2, 6, 8], [3, 6, 7], [1, 3, 4]], 5))) 42 | 43 | 44 | main() 45 | 46 | 47 | 48 | #answer 49 | from heapq import * 50 | 51 | 52 | def find_Kth_smallest(lists, k): 53 | minHeap = [] 54 | 55 | # put the 1st element of each list in the min heap 56 | for i in range(len(lists)): 57 | heappush(minHeap, (lists[i][0], 0, lists[i])) 58 | 59 | # take the smallest(top) element form the min heap, if the running count is equal to k return the number 60 | numberCount, number = 0, 0 61 | while minHeap: 62 | number, i, list = heappop(minHeap) 63 | numberCount += 1 64 | if numberCount == k: 65 | break 66 | # if the array of the top element has more elements, add the next element to the heap 67 | if len(list) > i+1: 68 | heappush(minHeap, (list[i+1], i+1, list)) 69 | 70 | return number 71 | 72 | 73 | def main(): 74 | print("Kth smallest number is: " + 75 | str(find_Kth_smallest([[2, 6, 8], [3, 6, 7], [1, 3, 4]], 5))) 76 | 77 | 78 | main() 79 | 80 | 81 | 82 | ''' 83 | Time complexity 84 | Since we’ll be going through at most ‘K’ elements among all the arrays, 85 | and we will remove/add one element in the heap in each step, 86 | the time complexity of the above algorithm will be O(K*logM) where ‘M’ is the total number of input arrays. 87 | 88 | Space complexity 89 | The space complexity will be O(M) because, at any time, 90 | our min-heap will be storing one number from all the ‘M’ input arrays. 91 | ''' 92 | 93 | 94 | ''' 95 | Similar Problems 96 | Problem 1: Given ‘M’ sorted arrays, find the median number among all arrays. 97 | 98 | Solution: This problem is similar to our parent problem with K=Median. 99 | So if there are ‘N’ total numbers in all the arrays we need to find the K’th minimum number where K=N/2K. 100 | 101 | Problem 2: Given a list of ‘K’ sorted arrays, merge them into one sorted list. 102 | 103 | Solution: This problem is similar to Merge K Sorted Lists except that 104 | the input is a list of arrays compared to LinkedLists. 105 | To handle this, we can use a similar approach as discussed in our parent problem 106 | by keeping a track of the array and the element indices. 107 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Fruits into Baskets (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of characters where each character represents a fruit tree, you are given two baskets and your goal is to put maximum number of fruits in each basket. The only restriction is that each basket can have only one type of fruit. 4 | 5 | You can start with any tree, but once you have started you can’t skip a tree. You will pick one fruit from each tree until you cannot, i.e., you will stop when you have to pick from a third fruit type. 6 | 7 | Write a function to return the maximum number of fruits in both the baskets. 8 | 9 | Example 1: 10 | 11 | Input: Fruit=['A', 'B', 'C', 'A', 'C'] 12 | Output: 3 13 | Explanation: We can put 2 'C' in one basket and one 'A' in the other from the subarray ['C', 'A', 'C'] 14 | 15 | Example 2: 16 | 17 | Input: Fruit=['A', 'B', 'C', 'B', 'B', 'C'] 18 | Output: 5 19 | Explanation: We can put 3 'B' in one basket and two 'C' in the other basket. 20 | This can be done if we start with the second letter: ['B', 'C', 'B', 'B', 'C'] 21 | ''' 22 | 23 | #mycode 24 | def fruits_into_baskets(fruits): 25 | left = 0 26 | max_length = 0 27 | hash_map = defaultdict(int) 28 | for right, fruit in enumerate(fruits): 29 | hash_map[fruit] += 1 30 | while len(hash_map) > 2: 31 | hash_map[fruits[left]] -= 1 32 | if hash_map[fruits[left]] == 0: 33 | del hash_map[fruits[left]] 34 | left += 1 35 | max_length = max(max_length, right - left + 1) 36 | return max_length 37 | 38 | 39 | #answer 40 | def fruits_into_baskets(fruits): 41 | window_start = 0 42 | max_length = 0 43 | fruit_frequency = {} 44 | 45 | # try to extend the range [window_start, window_end] 46 | for window_end in range(len(fruits)): 47 | right_fruit = fruits[window_end] 48 | if right_fruit not in fruit_frequency: 49 | fruit_frequency[right_fruit] = 0 50 | fruit_frequency[right_fruit] += 1 51 | 52 | # shrink the sliding window, until we are left with '2' fruits in the fruit frequency dictionary 53 | while len(fruit_frequency) > 2: 54 | left_fruit = fruits[window_start] 55 | fruit_frequency[left_fruit] -= 1 56 | if fruit_frequency[left_fruit] == 0: 57 | del fruit_frequency[left_fruit] 58 | window_start += 1 # shrink the window 59 | max_length = max(max_length, window_end-window_start + 1) 60 | return max_length 61 | 62 | 63 | def main(): 64 | print("Maximum number of fruits: " + str(fruits_into_baskets(['A', 'B', 'C', 'A', 'C']))) 65 | print("Maximum number of fruits: " + str(fruits_into_baskets(['A', 'B', 'C', 'B', 'B', 'C']))) 66 | 67 | 68 | main() 69 | 70 | 71 | 72 | ''' 73 | Time Complexity 74 | The time complexity of the above algorithm will be O(N) where ‘N’ is the number of characters in the input array. 75 | The outer for loop runs for all characters and the inner while loop processes each character only once, therefore the time complexity of the algorithm will be O(N+N)which is asymptotically equivalent to O(N). 76 | 77 | Space Complexity 78 | The algorithm runs in constant space O(1) as there can be a maximum of three types of fruits stored in the frequency map. 79 | 80 | Similar Problems 81 | Problem 1: Longest Substring with at most 2 distinct characters 82 | 83 | Given a string, find the length of the longest substring in it with at most two distinct characters. 84 | 85 | Solution: This problem is exactly similar to our parent problem. 86 | ''' -------------------------------------------------------------------------------- /08. Pattern Tree Depth First Search/Path With Given Sequence (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree and a number sequence, find if the sequence is present as a root-to-leaf path in the given tree. 4 | ''' 5 | 6 | #mycode 7 | class TreeNode: 8 | def __init__(self, val, left=None, right=None): 9 | self.val = val 10 | self.left = left 11 | self.right = right 12 | 13 | 14 | def find_path(root, sequence): 15 | # TODO: Write your code here 16 | 17 | return find_current_path(root, sequence) 18 | 19 | def find_current_path(currentNode, sequence): 20 | 21 | if not currentNode: 22 | return False 23 | 24 | if currentNode.val != sequence[0]: 25 | return False 26 | else: 27 | if currentNode.left is None and currentNode.right is None: 28 | return True 29 | else: 30 | del sequence[0] 31 | return find_current_path(currentNode.left, sequence) or find_current_path(currentNode.right, sequence) 32 | 33 | 34 | 35 | 36 | def main(): 37 | 38 | root = TreeNode(1) 39 | root.left = TreeNode(0) 40 | root.right = TreeNode(1) 41 | root.left.left = TreeNode(1) 42 | root.right.left = TreeNode(6) 43 | root.right.right = TreeNode(5) 44 | 45 | print("Tree has path sequence: " + str(find_path(root, [1, 0, 7]))) 46 | print("Tree has path sequence: " + str(find_path(root, [1, 1, 6]))) 47 | 48 | 49 | main() 50 | 51 | 52 | 53 | #answer 54 | class TreeNode: 55 | def __init__(self, val, left=None, right=None): 56 | self.val = val 57 | self.left = left 58 | self.right = right 59 | 60 | 61 | def find_path(root, sequence): 62 | if not root: 63 | return len(sequence) == 0 64 | 65 | return find_path_recursive(root, sequence, 0) 66 | 67 | 68 | def find_path_recursive(currentNode, sequence, sequenceIndex): 69 | 70 | if currentNode is None: 71 | return False 72 | 73 | seqLen = len(sequence) 74 | if sequenceIndex >= seqLen or currentNode.val != sequence[sequenceIndex]: 75 | return False 76 | 77 | # if the current node is a leaf, add it is the end of the sequence, we have found a path! 78 | if currentNode.left is None and currentNode.right is None and sequenceIndex == seqLen - 1: 79 | return True 80 | 81 | # recursively call to traverse the left and right sub-tree 82 | # return true if any of the two recursive call return true 83 | return find_path_recursive(currentNode.left, sequence, sequenceIndex + 1) or \ 84 | find_path_recursive(currentNode.right, sequence, sequenceIndex + 1) 85 | 86 | 87 | def main(): 88 | 89 | root = TreeNode(1) 90 | root.left = TreeNode(0) 91 | root.right = TreeNode(1) 92 | root.left.left = TreeNode(1) 93 | root.right.left = TreeNode(6) 94 | root.right.right = TreeNode(5) 95 | 96 | print("Tree has path sequence: " + str(find_path(root, [1, 0, 7]))) 97 | print("Tree has path sequence: " + str(find_path(root, [1, 1, 6]))) 98 | 99 | 100 | main() 101 | 102 | 103 | ''' 104 | Time complexity 105 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 106 | This is due to the fact that we traverse each node once. 107 | 108 | Space complexity 109 | The space complexity of the above algorithm will be O(N) in the worst case. 110 | This space will be used to store the recursion stack. 111 | The worst case will happen when the given tree is a linked list (i.e., every node has only one child). 112 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/'K' Closest Points to the Origin (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of points in the a 2D2D plane, find ‘K’ closest points to the origin. 4 | 5 | Example 1: 6 | 7 | Input: points = [[1,2],[1,3]], K = 1 8 | Output: [[1,2]] 9 | Explanation: The Euclidean distance between (1, 2) and the origin is sqrt(5). 10 | The Euclidean distance between (1, 3) and the origin is sqrt(10). 11 | Since sqrt(5) < sqrt(10), therefore (1, 2) is closer to the origin. 12 | Example 2: 13 | 14 | Input: point = [[1, 3], [3, 4], [2, -1]], K = 2 15 | Output: [[1, 3], [2, -1]] 16 | ''' 17 | 18 | import math 19 | from heapq import * 20 | 21 | class Point: 22 | 23 | def __init__(self, x, y): 24 | self.x = x 25 | self.y = y 26 | 27 | def print_point(self): 28 | print("[" + str(self.x) + ", " + str(self.y) + "] ", end='') 29 | print(self.x **2+self.y **2 ) 30 | def __lt__(self, other): 31 | 32 | return (self.x **2+self.y **2 ) > (other.x **2+other.y **2) 33 | 34 | def find_closest_points(points, k): 35 | result = [] 36 | # TODO: Write your code here 37 | for point in points: 38 | if len(result) other.distance_from_origin() 74 | 75 | def distance_from_origin(self): 76 | # ignoring sqrt to calculate the distance 77 | return (self.x * self.x) + (self.y * self.y) 78 | 79 | def print_point(self): 80 | print("[" + str(self.x) + ", " + str(self.y) + "] ", end='') 81 | 82 | 83 | def find_closest_points(points, k): 84 | maxHeap = [] 85 | # put first 'k' points in the max heap 86 | for i in range(k): 87 | heappush(maxHeap, points[i]) 88 | 89 | # go through the remaining points of the input array, if a point is closer to the origin than the top point 90 | # of the max-heap, remove the top point from heap and add the point from the input array 91 | for i in range(k, len(points)): 92 | if points[i].distance_from_origin() < maxHeap[0].distance_from_origin(): 93 | heappop(maxHeap) 94 | heappush(maxHeap, points[i]) 95 | 96 | # the heap has 'k' points closest to the origin, return them in a list 97 | return list(maxHeap) 98 | 99 | 100 | def main(): 101 | 102 | result = find_closest_points([Point(1, 3), Point(3, 4), Point(2, -1)], 2) 103 | print("Here are the k points closest the origin: ", end='') 104 | for point in result: 105 | point.print_point() 106 | 107 | 108 | main() 109 | 110 | 111 | 112 | ''' 113 | Time complexity 114 | The time complexity of this algorithm is (N*logK) as we iterating all points and pushing them into the heap. 115 | 116 | Space complexity 117 | The space complexity will be O(K) because we need to store ‘K’ point in the heap. 118 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Problem Challenge 2 - Comparing Strings containing Backspaces (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 2 3 | 4 | Comparing Strings containing Backspaces (medium) 5 | 6 | Given two strings containing backspaces (identified by the character ‘#’), check if the two strings are equal. 7 | 8 | Example 1: 9 | 10 | Input: str1="xy#z", str2="xzz#" 11 | Output: true 12 | Explanation: After applying backspaces the strings become "xz" and "xz" respectively. 13 | 14 | Example 2: 15 | 16 | Input: str1="xy#z", str2="xyz#" 17 | Output: false 18 | Explanation: After applying backspaces the strings become "xz" and "xy" respectively. 19 | 20 | Example 3: 21 | 22 | Input: str1="xp#", str2="xyz##" 23 | Output: true 24 | Explanation: After applying backspaces the strings become "x" and "x" respectively. 25 | In "xyz##", the first '#' removes the character 'z' and the second '#' removes the character 'y'. 26 | 27 | Example 4: 28 | 29 | Input: str1="xywrrmp", str2="xywrrmu#p" 30 | Output: true 31 | Explanation: After applying backspaces the strings become "xywrrmp" and "xywrrmp" respectively. 32 | ''' 33 | 34 | #mycode 35 | def backspace_compare(str1, str2): 36 | # TODO: Write your code here 37 | if clean(str1) == clean(str2): 38 | return True 39 | return False 40 | 41 | def clean(str): 42 | i= len(str)-1 43 | while i >= 0: 44 | count = 0 45 | 46 | while i >= 0 and str[i] == '#': 47 | count += 1 48 | i -= 1 49 | 50 | if count > 0 and i+count == len(str)-1: 51 | str=str[:i-count+1] 52 | i = i - count 53 | 54 | elif count > 0 and i-count+1==0 : 55 | str=str[i+count+1:] 56 | i=-1 57 | 58 | elif count>0 : 59 | str=str[:i-count+1]+ str[i+count+1:] 60 | i=i-count -1 61 | print(str) 62 | else: 63 | i=i-1 64 | 65 | print(str,count,i) 66 | return str 67 | 68 | 69 | #answer 70 | def backspace_compare(str1, str2): 71 | # use two pointer approach to compare the strings 72 | index1 = len(str1) - 1 73 | index2 = len(str2) - 1 74 | while (index1 >= 0 or index2 >= 0): 75 | i1 = get_next_valid_char_index(str1, index1) 76 | i2 = get_next_valid_char_index(str2, index2) 77 | if i1 < 0 and i2 < 0: # reached the end of both the strings 78 | return True 79 | if i1 < 0 or i2 < 0: # reached the end of one of the strings 80 | return False 81 | if str1[i1] != str2[i2]: # check if the characters are equal 82 | return False 83 | 84 | index1 = i1 - 1 85 | index2 = i2 - 1 86 | 87 | return True 88 | 89 | 90 | def get_next_valid_char_index(str, index): 91 | backspace_count = 0 92 | while (index >= 0): 93 | if str[index] == '#': # found a backspace character 94 | backspace_count += 1 95 | elif backspace_count > 0: # a non-backspace character 96 | backspace_count -= 1 97 | else: 98 | break 99 | 100 | index -= 1 # skip a backspace or a valid character 101 | 102 | return index 103 | 104 | 105 | def main(): 106 | print(backspace_compare("xy#z", "xzz#")) 107 | print(backspace_compare("xy#z", "xyz#")) 108 | print(backspace_compare("xp#", "xyz##")) 109 | print(backspace_compare("xywrrmp", "xywrrmu#p")) 110 | 111 | 112 | main() 113 | 114 | 115 | ''' 116 | Time complexity 117 | The time complexity of the above algorithm will be O(M+N) where ‘M’ and ‘N’ are the lengths of the two input strings respectively. 118 | 119 | Space complexity 120 | The algorithm runs in constant space O(1). 121 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Kth Smallest Number (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an unsorted array of numbers, find Kth smallest number in it. 4 | 5 | Please note that it is the Kth smallest number in the sorted order, not the Kth distinct element. 6 | 7 | Note: For a detailed discussion about different approaches to solve this problem, take a look at Kth Smallest Number. 8 | 9 | Example 1: 10 | 11 | Input: [1, 5, 12, 2, 11, 5], K = 3 12 | Output: 5 13 | Explanation: The 3rd smallest number is '5', as the first two smaller numbers are [1, 2]. 14 | Example 2: 15 | 16 | Input: [1, 5, 12, 2, 11, 5], K = 4 17 | Output: 5 18 | Explanation: The 4th smallest number is '5', as the first three small numbers are [1, 2, 5]. 19 | Example 3: 20 | 21 | Input: [5, 12, 11, -1, 12], K = 3 22 | Output: 11 23 | Explanation: The 3rd smallest number is '11', as the first two small numbers are [5, -1]. 24 | ''' 25 | 26 | #mycode 27 | from heapq import * 28 | 29 | def find_Kth_smallest_number(nums, k): 30 | # TODO: Write your code here 31 | result = [] 32 | for num in nums: 33 | heappush(result,num) 34 | return result[k-1] 35 | 36 | 37 | def main(): 38 | 39 | print("Kth smallest number is: " + 40 | str(find_Kth_smallest_number([1, 5, 12, 2, 11, 5], 3))) 41 | 42 | # since there are two 5s in the input array, our 3rd and 4th smallest numbers should be a '5' 43 | print("Kth smallest number is: " + 44 | str(find_Kth_smallest_number([1, 5, 12, 2, 11, 5], 4))) 45 | 46 | print("Kth smallest number is: " + 47 | str(find_Kth_smallest_number([5, 12, 11, -1, 12], 3))) 48 | 49 | 50 | main() 51 | 52 | 53 | 54 | #answer 55 | from heapq import * 56 | 57 | 58 | def find_Kth_smallest_number(nums, k): 59 | maxHeap = [] 60 | # put first k numbers in the max heap 61 | for i in range(k): 62 | heappush(maxHeap, -nums[i]) 63 | 64 | # go through the remaining numbers of the array, if the number from the array is smaller than the 65 | # top(biggest) number of the heap, remove the top number from heap and add the number from array 66 | for i in range(k, len(nums)): 67 | if -nums[i] > maxHeap[0]: 68 | heappop(maxHeap) 69 | heappush(maxHeap, -nums[i]) 70 | 71 | # the root of the heap has the Kth smallest number 72 | return -maxHeap[0] 73 | 74 | 75 | def main(): 76 | 77 | print("Kth smallest number is: " + 78 | str(find_Kth_smallest_number([1, 5, 12, 2, 11, 5], 3))) 79 | 80 | # since there are two 5s in the input array, our 3rd and 4th smallest numbers should be a '5' 81 | print("Kth smallest number is: " + 82 | str(find_Kth_smallest_number([1, 5, 12, 2, 11, 5], 4))) 83 | 84 | print("Kth smallest number is: " + 85 | str(find_Kth_smallest_number([5, 12, 11, -1, 12], 3))) 86 | 87 | 88 | main() 89 | 90 | 91 | 92 | ''' 93 | Time complexity 94 | The time complexity of this algorithm is O(K*logK+(N-K)*logK), which is asymptotically equal to O(N*logK) 95 | 96 | Space complexity 97 | The space complexity will be O(K) because we need to store ‘K’ smallest numbers in the heap. 98 | 99 | An Alternate Approach 100 | Alternatively, we can use a Min Heap to find the Kth smallest number. 101 | We can insert all the numbers in the min-heap and then extract the top ‘K’ numbers from the heap to find the Kth smallest number. 102 | Initializing the min-heap with all numbers will take O(N) and extracting ‘K’ numbers will take O(KlogN). 103 | Overall, the time complexity of this algorithm will be O(N+KlogN) and the space complexity will be O(N). 104 | ''' -------------------------------------------------------------------------------- /03. Pattern Fast & Slow pointers/Problem Challenge 3 - Cycle in a Circular Array (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Cycle in a Circular Array (hard) 5 | 6 | We are given an array containing positive and negative numbers. Suppose the array contains a number ‘M’ at a particular index. 7 | Now, if ‘M’ is positive we will move forward ‘M’ indices and if ‘M’ is negative move backwards ‘M’ indices. You should assume that the array is circular which means two things: 8 | 9 | If, while moving forward, we reach the end of the array, we will jump to the first element to continue the movement. 10 | If, while moving backward, we reach the beginning of the array, we will jump to the last element to continue the movement. 11 | Write a method to determine if the array has a cycle. The cycle should have more than one element and should follow one direction which means the cycle should not contain both forward and backward movements. 12 | 13 | Example 1: 14 | 15 | Input: [1, 2, -1, 2, 2] 16 | Output: true 17 | Explanation: The array has a cycle among indices: 0 -> 1 -> 3 -> 0 18 | 19 | Example 2: 20 | 21 | Input: [2, 2, -1, 2] 22 | Output: true 23 | Explanation: The array has a cycle among indices: 1 -> 3 -> 1 24 | 25 | Example 3: 26 | 27 | Input: [2, 1, -1, -2] 28 | Output: false 29 | Explanation: The array does not have any cycle. 30 | ''' 31 | 32 | 33 | def circular_array_loop_exists(arr): 34 | for i in range(len(arr)): 35 | is_forward = arr[i] >= 0 # if we are moving forward or not 36 | slow, fast = i, i 37 | 38 | # if slow or fast becomes '-1' this means we can't find cycle for this number 39 | while True: 40 | # move one step for slow pointer 41 | slow = find_next_index(arr, is_forward, slow) 42 | # move one step for fast pointer 43 | fast = find_next_index(arr, is_forward, fast) 44 | if (fast != -1): 45 | # move another step for fast pointer 46 | fast = find_next_index(arr, is_forward, fast) 47 | if slow == -1 or fast == -1 or slow == fast: 48 | break 49 | 50 | if slow != -1 and slow == fast: 51 | return True 52 | 53 | return False 54 | 55 | 56 | def find_next_index(arr, is_forward, current_index): 57 | direction = arr[current_index] >= 0 58 | 59 | if is_forward != direction: 60 | return -1 # change in direction, return -1 61 | 62 | next_index = (current_index + arr[current_index]) % len(arr) 63 | 64 | # one element cycle, return -1 65 | if next_index == current_index: 66 | next_index = -1 67 | 68 | return next_index 69 | 70 | 71 | def main(): 72 | print(circular_array_loop_exists([1, 2, -1, 2, 2])) 73 | print(circular_array_loop_exists([2, 2, -1, 2])) 74 | print(circular_array_loop_exists([2, 1, -1, -2])) 75 | 76 | 77 | main() 78 | 79 | 80 | 81 | ''' 82 | Time Complexity 83 | The above algorithm will have a time complexity of O(N^2) where ‘N’ is the number of elements in the array. 84 | This complexity is due to the fact that we are iterating all elements of the array and trying to find a cycle for each element. 85 | 86 | Space Complexity 87 | The algorithm runs in constant space O(1). 88 | 89 | An Alternate Approach 90 | In our algorithm, we don’t keep a record of all the numbers that have been evaluated for cycles. 91 | We know that all such numbers will not produce a cycle for any other instance as well. 92 | If we can remember all the numbers that have been visited, our algorithm will improve to O(N) as, then, each number will be evaluated for cycles only once. 93 | We can keep track of this by creating a separate array however the space complexity of our algorithm will increase to O(N). 94 | ''' -------------------------------------------------------------------------------- /02. Pattern Two Pointers/Problem Challenge 1 - Quadruple Sum to Target (medium) .py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | 4 | Quadruple Sum to Target (medium) 5 | Given an array of unsorted numbers and a target number, find all unique quadruplets in it, whose sum is equal to the target number. 6 | 7 | Example 1: 8 | 9 | Input: [4, 1, 2, -1, 1, -3], target=1 10 | Output: [-3, -1, 1, 4], [-3, 1, 1, 2] 11 | Explanation: Both the quadruplets add up to the target. 12 | 13 | Example 2: 14 | 15 | Input: [2, 0, -1, 1, -2, 2], target=2 16 | Output: [-2, 0, 2, 2], [-1, 0, 1, 2] 17 | Explanation: Both the quadruplets add up to the target. 18 | ''' 19 | 20 | #mycode 21 | def search_quadruplets(arr, target): 22 | quadruplets = [] 23 | # TODO: Write your code here 24 | 25 | arr.sort() 26 | 27 | for i in range(len(arr)-3): 28 | if i>0 and arr[i] == arr[i-1]: 29 | continue 30 | for j in range(i+1,len(arr)-2): 31 | if j>i and arr[j] == arr[j-1]: 32 | continue 33 | search_pair(arr, i, j, target, quadruplets) 34 | 35 | return quadruplets 36 | 37 | def search_pair(arr, i, j, target, quadruplets): 38 | left = j+1 39 | right = len(arr) - 1 40 | 41 | sub_target=target - arr[i] - arr[j] 42 | 43 | while left < right: 44 | if arr[left] + arr[right] == sub_target: 45 | quadruplets.append([arr[i],arr[j],arr[left],arr[right]]) 46 | left += 1 47 | right -= 1 48 | 49 | while left < right and arr[left] == arr[left-1]: 50 | left += 1 51 | 52 | while left < right and arr[right] == arr[right+1]: 53 | right -=1 54 | elif arr[left] + arr[right] < sub_target: 55 | left += 1 56 | else: 57 | right -= 1 58 | 59 | 60 | 61 | #answer 62 | def search_quadruplets(arr, target): 63 | arr.sort() 64 | quadruplets = [] 65 | for i in range(0, len(arr)-3): 66 | # skip same element to avoid duplicate quadruplets 67 | if i > 0 and arr[i] == arr[i - 1]: 68 | continue 69 | for j in range(i + 1, len(arr)-2): 70 | # skip same element to avoid duplicate quadruplets 71 | if j > i + 1 and arr[j] == arr[j - 1]: 72 | continue 73 | search_pairs(arr, target, i, j, quadruplets) 74 | return quadruplets 75 | 76 | 77 | def search_pairs(arr, target_sum, first, second, quadruplets): 78 | left = second + 1 79 | right = len(arr) - 1 80 | while (left < right): 81 | sum = arr[first] + arr[second] + arr[left] + arr[right] 82 | if sum == target_sum: # found the quadruplet 83 | quadruplets.append( 84 | [arr[first], arr[second], arr[left], arr[right]]) 85 | left += 1 86 | right -= 1 87 | while (left < right and arr[left] == arr[left - 1]): 88 | left += 1 # skip same element to avoid duplicate quadruplets 89 | while (left < right and arr[right] == arr[right + 1]): 90 | right -= 1 # skip same element to avoid duplicate quadruplets 91 | elif sum < target_sum: 92 | left += 1 # we need a pair with a bigger sum 93 | else: 94 | right -= 1 # we need a pair with a smaller sum 95 | 96 | 97 | def main(): 98 | print(search_quadruplets([4, 1, 2, -1, 1, -3], 1)) 99 | print(search_quadruplets([2, 0, -1, 1, -2, 2], 2)) 100 | 101 | 102 | main() 103 | 104 | 105 | 106 | ''' 107 | Time complexity 108 | Sorting the array will take O(N*logN). Overall searchQuadruplets() will take O(N * logN + N^3), which is asymptotically equivalent to O(N^3). 109 | 110 | Space complexity 111 | The space complexity of the above algorithm will be O(N) which is required for sorting. 112 | 113 | ''' -------------------------------------------------------------------------------- /05. Pattern Cyclic Sort/Find the Duplicate Number (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | We are given an unsorted array containing ‘n+1’ numbers taken from the range 1 to ‘n’. 4 | The array has only one duplicate but it can be repeated multiple times. 5 | Find that duplicate number without using any extra space. You are, however, allowed to modify the input array. 6 | 7 | Example 1: 8 | 9 | Input: [1, 4, 4, 3, 2] 10 | Output: 4 11 | 12 | Example 2: 13 | 14 | Input: [2, 1, 3, 3, 5, 4] 15 | Output: 3 16 | 17 | Example 3: 18 | 19 | Input: [2, 4, 1, 4, 4] 20 | Output: 4 21 | ''' 22 | 23 | #mycode 24 | 25 | def find_duplicate(nums): 26 | # TODO: Write your code here 27 | for i in range(len(nums)): 28 | j=abs(nums[i])-1 29 | if nums[j] > 0: 30 | nums[j] = -nums[j] 31 | else: 32 | return j+1 33 | return -1 34 | 35 | 36 | #mycode2 37 | 38 | def find_duplicate(nums): 39 | # TODO: Write your code here 40 | i=0 41 | while i < len(nums): 42 | j=nums[i]-1 43 | 44 | if nums[i] != nums[j]: 45 | nums[i], nums[j] = nums[j], nums[i] 46 | elif nums[i] == nums[j] and i!=j: 47 | return nums[i] 48 | else: 49 | i += 1 50 | 51 | return -1 52 | 53 | 54 | #answer 55 | 56 | def find_duplicate(nums): 57 | i = 0 58 | while i < len(nums): 59 | if nums[i] != i + 1: 60 | j = nums[i] - 1 61 | if nums[i] != nums[j]: 62 | nums[i], nums[j] = nums[j], nums[i] # swap 63 | else: # we have found the duplicate 64 | return nums[i] 65 | else: 66 | i += 1 67 | 68 | return -1 69 | 70 | 71 | def main(): 72 | print(find_duplicate([1, 4, 4, 3, 2])) 73 | print(find_duplicate([2, 1, 3, 3, 5, 4])) 74 | print(find_duplicate([2, 4, 1, 4, 4])) 75 | 76 | 77 | main() 78 | 79 | 80 | ''' 81 | Time complexity 82 | The time complexity of the above algorithm is O(n). 83 | 84 | Space complexity 85 | The algorithm runs in constant space O(1) but modifies the input array. 86 | ''' 87 | 88 | 89 | ''' 90 | Similar Problems 91 | Problem 1: Can we solve the above problem in O(1) space and without modifying the input array? 92 | 93 | Solution: While doing the cyclic sort, we realized that the array will have a cycle due to the duplicate number and that the start of the cycle will always point to the duplicate number. 94 | This means that we can use the fast & the slow pointer method to find the duplicate number or the start of the cycle similar to Start of LinkedList Cycle. 95 | 96 | The time complexity of the above algorithm is O(n) and the space complexity is O(1). 97 | ''' 98 | 99 | def find_duplicate(arr): 100 | slow, fast = arr[0], arr[arr[0]] 101 | while slow != fast: 102 | slow = arr[slow] 103 | fast = arr[arr[fast]] 104 | 105 | # find cycle length 106 | current = arr[arr[slow]] 107 | cycleLength = 1 108 | while current != arr[slow]: 109 | current = arr[current] 110 | cycleLength += 1 111 | 112 | return find_start(arr, cycleLength) 113 | 114 | 115 | def find_start(arr, cycleLength): 116 | pointer1, pointer2 = arr[0], arr[0] 117 | # move pointer2 ahead 'cycleLength' steps 118 | while cycleLength > 0: 119 | pointer2 = arr[pointer2] 120 | cycleLength -= 1 121 | 122 | # increment both pointers until they meet at the start of the cycle 123 | while pointer1 != pointer2: 124 | pointer1 = arr[pointer1] 125 | pointer2 = arr[pointer2] 126 | 127 | return pointer1 128 | 129 | 130 | def main(): 131 | print(find_duplicate([1, 4, 4, 3, 2])) 132 | print(find_duplicate([2, 1, 3, 3, 5, 4])) 133 | print(find_duplicate([2, 4, 1, 4, 4])) 134 | 135 | 136 | main() 137 | -------------------------------------------------------------------------------- /06. Pattern In-place Reversal of a LinkedList/Problem Challenge 2 - Rotate a LinkedList (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 2 3 | 4 | Rotate a LinkedList (medium) 5 | 6 | Given the head of a Singly LinkedList and a number ‘k’, rotate the LinkedList to the right by ‘k’ nodes. 7 | ''' 8 | 9 | 10 | #mycode 11 | from __future__ import print_function 12 | 13 | 14 | class Node: 15 | def __init__(self, value, next=None): 16 | self.value = value 17 | self.next = next 18 | 19 | def print_list(self): 20 | temp = self 21 | while temp is not None: 22 | print(temp.value, end=" ") 23 | temp = temp.next 24 | print() 25 | 26 | 27 | def rotate(head, rotations): 28 | # TODO: Write your code here 29 | last = head 30 | leng = 1 31 | while last.next is not None: 32 | last = last.next 33 | leng += 1 34 | 35 | index = rotations%leng 36 | i = 0 37 | before_begin, begin = None, head 38 | while i < index: 39 | before_begin = begin 40 | begin = begin.next 41 | i += 1 42 | 43 | last.next = head 44 | before_begin.next = None 45 | 46 | return begin 47 | 48 | 49 | def main(): 50 | head = Node(1) 51 | head.next = Node(2) 52 | head.next.next = Node(3) 53 | head.next.next.next = Node(4) 54 | head.next.next.next.next = Node(5) 55 | head.next.next.next.next.next = Node(6) 56 | 57 | print("Nodes of original LinkedList are: ", end='') 58 | head.print_list() 59 | result = rotate(head, 3) 60 | print("Nodes of rotated LinkedList are: ", end='') 61 | result.print_list() 62 | 63 | 64 | main() 65 | 66 | 67 | 68 | 69 | #answer 70 | from __future__ import print_function 71 | 72 | 73 | class Node: 74 | def __init__(self, value, next=None): 75 | self.value = value 76 | self.next = next 77 | 78 | def print_list(self): 79 | temp = self 80 | while temp is not None: 81 | print(temp.value, end=" ") 82 | temp = temp.next 83 | print() 84 | 85 | 86 | def rotate(head, rotations): 87 | if head is None or head.next is None or rotations <= 0: 88 | return head 89 | 90 | # find the length and the last node of the list 91 | last_node = head 92 | list_length = 1 93 | while last_node.next is not None: 94 | last_node = last_node.next 95 | list_length += 1 96 | 97 | last_node.next = head # connect the last node with the head to make it a circular list 98 | rotations %= list_length # no need to do rotations more than the length of the list 99 | skip_length = list_length - rotations 100 | last_node_of_rotated_list = head 101 | for i in range(skip_length - 1): 102 | last_node_of_rotated_list = last_node_of_rotated_list.next 103 | 104 | # 'last_node_of_rotated_list.next' is pointing to the sub-list of 'k' ending nodes 105 | head = last_node_of_rotated_list.next 106 | last_node_of_rotated_list.next = None 107 | return head 108 | 109 | 110 | def main(): 111 | head = Node(1) 112 | head.next = Node(2) 113 | head.next.next = Node(3) 114 | head.next.next.next = Node(4) 115 | head.next.next.next.next = Node(5) 116 | head.next.next.next.next.next = Node(6) 117 | 118 | print("Nodes of original LinkedList are: ", end='') 119 | head.print_list() 120 | result = rotate(head, 3) 121 | print("Nodes of rotated LinkedList are: ", end='') 122 | result.print_list() 123 | 124 | 125 | main() 126 | 127 | 128 | 129 | ''' 130 | Time complexity 131 | The time complexity of our algorithm will be O(N) where ‘N’ is the total number of nodes in the LinkedList. 132 | 133 | Space complexity 134 | We only used constant space, therefore, the space complexity of our algorithm is O(1). 135 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Problem Challenge 1 - Connect All Level Order Siblings (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | 4 | Connect All Level Order Siblings (medium) 5 | Given a binary tree, connect each node with its level order successor. 6 | The last node of each level should point to the first node of the next level. 7 | ''' 8 | 9 | #mycode 10 | from __future__ import print_function 11 | from collections import deque 12 | 13 | 14 | class TreeNode: 15 | def __init__(self, val): 16 | self.val = val 17 | self.left, self.right, self.next = None, None, None 18 | 19 | # tree traversal using 'next' pointer 20 | def print_tree(self): 21 | print("Traversal using 'next' pointer: ", end='') 22 | current = self 23 | while current: 24 | print(str(current.val) + " ", end='') 25 | current = current.next 26 | 27 | 28 | def connect_all_siblings(root): 29 | # TODO: Write your code here 30 | if not root: 31 | return None 32 | 33 | queue = deque() 34 | queue.append(root) 35 | 36 | previous = None 37 | while queue: 38 | for i in range(len(queue)): 39 | current = queue.popleft() 40 | 41 | if previous is None: 42 | previous = current 43 | previous.next = current 44 | 45 | if current.left: 46 | queue.append(current.left) 47 | if current.right: 48 | queue.append(current.right) 49 | previous = current 50 | 51 | 52 | def main(): 53 | root = TreeNode(12) 54 | root.left = TreeNode(7) 55 | root.right = TreeNode(1) 56 | root.left.left = TreeNode(9) 57 | root.right.left = TreeNode(10) 58 | root.right.right = TreeNode(5) 59 | connect_all_siblings(root) 60 | root.print_tree() 61 | 62 | 63 | main() 64 | 65 | 66 | 67 | 68 | #answer 69 | from __future__ import print_function 70 | from collections import deque 71 | 72 | 73 | class TreeNode: 74 | def __init__(self, val): 75 | self.val = val 76 | self.left, self.right, self.next = None, None, None 77 | 78 | # tree traversal using 'next' pointer 79 | def print_tree(self): 80 | print("Traversal using 'next' pointer: ", end='') 81 | current = self 82 | while current: 83 | print(str(current.val) + " ", end='') 84 | current = current.next 85 | 86 | 87 | def connect_all_siblings(root): 88 | if root is None: 89 | return 90 | 91 | queue = deque() 92 | queue.append(root) 93 | currentNode, previousNode = None, None 94 | while queue: 95 | currentNode = queue.popleft() 96 | if previousNode: 97 | previousNode.next = currentNode 98 | previousNode = currentNode 99 | 100 | # insert the children of current node in the queue 101 | if currentNode.left: 102 | queue.append(currentNode.left) 103 | if currentNode.right: 104 | queue.append(currentNode.right) 105 | 106 | 107 | def main(): 108 | root = TreeNode(12) 109 | root.left = TreeNode(7) 110 | root.right = TreeNode(1) 111 | root.left.left = TreeNode(9) 112 | root.right.left = TreeNode(10) 113 | root.right.right = TreeNode(5) 114 | connect_all_siblings(root) 115 | root.print_tree() 116 | 117 | 118 | main() 119 | 120 | 121 | ''' 122 | Time complexity 123 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 124 | This is due to the fact that we traverse each node once. 125 | 126 | Space complexity 127 | The space complexity of the above algorithm will be O(N) which is required for the queue. 128 | Since we can have a maximum of N/2 nodes at any level (this could happen only at the lowest level), 129 | therefore we will need O(N) space to store them in the queue. 130 | ''' 131 | 132 | -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Problem Challenge 1 - Rearrange String K Distance Apart (hard).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | Rearrange String K Distance Apart (hard) 4 | Given a string and a number ‘K’, find if the string can be rearranged such that the same characters are at least ‘K’ distance apart from each other. 5 | 6 | Example 1: 7 | 8 | Input: "mmpp", K=2 9 | Output: "mpmp" or "pmpm" 10 | Explanation: All same characters are 2 distance apart. 11 | 12 | Example 2: 13 | 14 | Input: "Programming", K=3 15 | Output: "rgmPrgmiano" or "gmringmrPoa" or "gmrPagimnor" and a few more 16 | Explanation: All same characters are 3 distance apart. 17 | 18 | Example 3: 19 | 20 | Input: "aab", K=2 21 | Output: "aba" 22 | Explanation: All same characters are 2 distance apart. 23 | 24 | Example 4: 25 | 26 | Input: "aappa", K=3 27 | Output: "" 28 | Explanation: We cannot find an arrangement of the string where any two 'a' are 3 distance apart. 29 | ''' 30 | 31 | from heapq import * 32 | from collections import deque 33 | 34 | def reorganize_string(str, k): 35 | # TODO: Write your code here 36 | mapping = {} 37 | 38 | for i in str: 39 | mapping[i] = mapping.get(i,0) +1 40 | 41 | heap = [] 42 | for i, freq in mapping.items(): 43 | heappush(heap,(-freq,i)) 44 | 45 | result ='' 46 | queue = deque() 47 | 48 | while heap: 49 | freq, i = heappop(heap) 50 | result += i 51 | queue.append((freq+1,i)) 52 | 53 | if len(queue) >= k: 54 | freq, i = queue.popleft() 55 | if -freq > 0: 56 | heappush(heap,(freq,i)) 57 | 58 | return result if len(result) == len(str) else '' 59 | 60 | def main(): 61 | print("Reorganized string: " + reorganize_string("mmpp", 2)) 62 | print("Reorganized string: " + reorganize_string("Programming", 3)) 63 | print("Reorganized string: " + reorganize_string("aab", 2)) 64 | print("Reorganized string: " + reorganize_string("aapa", 3)) 65 | 66 | 67 | main() 68 | 69 | 70 | 71 | 72 | #answer 73 | from heapq import * 74 | from collections import deque 75 | 76 | 77 | def reorganize_string(str, k): 78 | if k <= 1: 79 | return str 80 | 81 | charFrequencyMap = {} 82 | for char in str: 83 | charFrequencyMap[char] = charFrequencyMap.get(char, 0) + 1 84 | 85 | maxHeap = [] 86 | # add all characters to the max heap 87 | for char, frequency in charFrequencyMap.items(): 88 | heappush(maxHeap, (-frequency, char)) 89 | 90 | queue = deque() 91 | resultString = [] 92 | while maxHeap: 93 | frequency, char = heappop(maxHeap) 94 | # append the current character to the result string and decrement its count 95 | resultString.append(char) 96 | # decrement the frequency and append to the queue 97 | queue.append((char, frequency+1)) 98 | if len(queue) == k: 99 | char, frequency = queue.popleft() 100 | if -frequency > 0: 101 | heappush(maxHeap, (frequency, char)) 102 | 103 | # if we were successful in appending all the characters to the result string, return it 104 | return ''.join(resultString) if len(resultString) == len(str) else "" 105 | 106 | 107 | def main(): 108 | print("Reorganized string: " + reorganize_string("Programming", 3)) 109 | print("Reorganized string: " + reorganize_string("mmpp", 2)) 110 | print("Reorganized string: " + reorganize_string("aab", 2)) 111 | print("Reorganized string: " + reorganize_string("aapa", 3)) 112 | 113 | 114 | main() 115 | 116 | 117 | 118 | ''' 119 | Time complexity 120 | The time complexity of the above algorithm is O(N*logN) where ‘N’ is the number of characters in the input string. 121 | 122 | Space complexity 123 | The space complexity will be O(N), as in the worst case, we need to store all the ‘N’ characters in the HashMap. 124 | ''' -------------------------------------------------------------------------------- /01. Pattern Sliding Window/Problem Challenge 2 - String Anagrams (hard) .py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 2 3 | 4 | String Anagrams (hard) 5 | Given a string and a pattern, find all anagrams of the pattern in the given string. 6 | 7 | Anagram is actually a Permutation of a string. For example, “abc” has the following six anagrams: 8 | 9 | abc 10 | acb 11 | bac 12 | bca 13 | cab 14 | cba 15 | Write a function to return a list of starting indices of the anagrams of the pattern in the given string. 16 | 17 | Example 1: 18 | 19 | Input: String="ppqp", Pattern="pq" 20 | Output: [1, 2] 21 | Explanation: The two anagrams of the pattern in the given string are "pq" and "qp". 22 | 23 | Example 2: 24 | 25 | Input: String="abbcabc", Pattern="abc" 26 | Output: [2, 3, 4] 27 | Explanation: The three anagrams of the pattern in the given string are "bca", "cab", and "abc". 28 | ''' 29 | 30 | #mycode 31 | def find_string_anagrams(str, pattern): 32 | result_indexes = [] 33 | # TODO: Write your code here 34 | p_dict={} 35 | s_dict={} 36 | for p in pattern: 37 | if p not in p_dict: 38 | p_dict[p] = 1 39 | else: 40 | p_dict[p] += 1 41 | 42 | for s in range(len(str)): 43 | if s= len(pattern) - 1: 91 | left_char = str[window_start] 92 | window_start += 1 93 | if left_char in char_frequency: 94 | if char_frequency[left_char] == 0: 95 | matched -= 1 # Before putting the character back, decrement the matched count 96 | char_frequency[left_char] += 1 # Put the character back 97 | 98 | return result_indices 99 | 100 | 101 | def main(): 102 | print(find_string_anagrams("ppqp", "pq")) 103 | print(find_string_anagrams("abbcabc", "abc")) 104 | 105 | 106 | main() 107 | 108 | 109 | 110 | ''' 111 | Time Complexity 112 | The time complexity of the above algorithm will be O(N + M) where ‘N’ and ‘M’ are the number of characters in the input string and the pattern respectively. 113 | 114 | Space Complexity 115 | The space complexity of the algorithm is O(M) since in the worst case, the whole pattern can have distinct characters which will go into the HashMap. 116 | In the worst case, we also need O(N) space for the result list, this will happen when the pattern has only one character and the string contains only that character. 117 | ''' -------------------------------------------------------------------------------- /14. Pattern K-way merge/Merge K Sorted Lists (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given an array of ‘K’ sorted LinkedLists, merge them into one sorted list. 4 | 5 | Example 1: 6 | 7 | Input: L1=[2, 6, 8], L2=[3, 6, 7], L3=[1, 3, 4] 8 | Output: [1, 2, 3, 3, 4, 6, 6, 7, 8] 9 | Example 2: 10 | 11 | Input: L1=[5, 8, 9], L2=[1, 7] 12 | Output: [1, 5, 7, 8, 9] 13 | ''' 14 | 15 | 16 | #mycode 17 | 18 | from __future__ import print_function 19 | from heapq import * 20 | 21 | 22 | class ListNode: 23 | def __init__(self, value): 24 | self.value = value 25 | self.next = None 26 | 27 | def __lt__(self, other): 28 | return self.value < other.value 29 | 30 | 31 | def merge_lists(lists): 32 | # TODO: Write your code here 33 | heap = [] 34 | for root in lists: 35 | if root is not None: 36 | heappush(heap,root) 37 | 38 | head, tail = None, None 39 | while heap: 40 | node = heappop(heap) 41 | if head is None: 42 | head, tail = node, node 43 | else: 44 | tail.next = node 45 | tail = tail.next 46 | if node.next is not None: 47 | heappush(heap,node.next) 48 | return head 49 | 50 | 51 | def main(): 52 | l1 = ListNode(2) 53 | l1.next = ListNode(6) 54 | l1.next.next = ListNode(8) 55 | 56 | l2 = ListNode(3) 57 | l2.next = ListNode(6) 58 | l2.next.next = ListNode(7) 59 | 60 | l3 = ListNode(1) 61 | l3.next = ListNode(3) 62 | l3.next.next = ListNode(4) 63 | 64 | result = merge_lists([l1, l2, l3]) 65 | print("Here are the elements form the merged list: ", end='') 66 | while result != None: 67 | print(str(result.value) + " ", end='') 68 | result = result.next 69 | 70 | 71 | main() 72 | 73 | 74 | 75 | #answer 76 | from __future__ import print_function 77 | from heapq import * 78 | 79 | 80 | class ListNode: 81 | def __init__(self, value): 82 | self.value = value 83 | self.next = None 84 | 85 | # used for the min-heap 86 | def __lt__(self, other): 87 | return self.value < other.value 88 | 89 | 90 | def merge_lists(lists): 91 | minHeap = [] 92 | 93 | # put the root of each list in the min heap 94 | for root in lists: 95 | if root is not None: 96 | heappush(minHeap, root) 97 | 98 | # take the smallest(top) element form the min-heap and add it to the result 99 | # if the top element has a next element add it to the heap 100 | resultHead, resultTail = None, None 101 | while minHeap: 102 | node = heappop(minHeap) 103 | if resultHead is None: 104 | resultHead = resultTail = node 105 | else: 106 | resultTail.next = node 107 | resultTail = resultTail.next 108 | 109 | if node.next is not None: 110 | heappush(minHeap, node.next) 111 | 112 | return resultHead 113 | 114 | 115 | def main(): 116 | l1 = ListNode(2) 117 | l1.next = ListNode(6) 118 | l1.next.next = ListNode(8) 119 | 120 | l2 = ListNode(3) 121 | l2.next = ListNode(6) 122 | l2.next.next = ListNode(7) 123 | 124 | l3 = ListNode(1) 125 | l3.next = ListNode(3) 126 | l3.next.next = ListNode(4) 127 | 128 | result = merge_lists([l1, l2, l3]) 129 | print("Here are the elements form the merged list: ", end='') 130 | while result is not None: 131 | print(str(result.value) + " ", end='') 132 | result = result.next 133 | 134 | 135 | main() 136 | 137 | 138 | 139 | 140 | ''' 141 | Time complexity 142 | Since we’ll be going through all the elements of all arrays and will be removing/adding one element to the heap in each step, 143 | the time complexity of the above algorithm will be O(N*logK), where ‘N’ is the total number of elements in all the ‘K’ input arrays. 144 | 145 | Space complexity 146 | The space complexity will be O(K) because, at any time, our min-heap will be storing one number from all the ‘K’ input arrays. 147 | ''' -------------------------------------------------------------------------------- /13. Pattern Top 'K' Elements/Problem Challenge 3 - Frequency Stack (hard) .py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 3 3 | 4 | Frequency Stack (hard) 5 | Design a class that simulates a Stack data structure, implementing the following two operations: 6 | 7 | push(int num): Pushes the number ‘num’ on the stack. 8 | pop(): Returns the most frequent number in the stack. If there is a tie, return the number which was pushed later. 9 | Example: 10 | 11 | After following push operations: push(1), push(2), push(3), push(2), push(1), push(2), push(5) 12 | 13 | 1. pop() should return 2, as it is the most frequent number 14 | 2. Next pop() should return 1 15 | 3. Next pop() should return 2 16 | ''' 17 | 18 | 19 | #mycode 20 | from heapq import * 21 | 22 | class FrequencyStack: 23 | def __init__(self): 24 | self.heap = [] 25 | self.mapping= {} 26 | 27 | def push(self, num): 28 | # TODO: Write your code here 29 | self.mapping[num] = self.mapping.get(num, 0) + 1 30 | if num not in self.heap: 31 | heappush(self.heap,(-self.mapping[num],num)) 32 | else: 33 | index = self.heap.index(num) 34 | if index == len(self.heap) -1: 35 | self.heap = self.heap[:index] 36 | else: 37 | self.heap = self.heap[0:index] + self.heap[index+1:] 38 | heappush(self.heap,(-mapping[num],num)) 39 | 40 | def pop(self): 41 | freq, i = heappop(self.heap) 42 | if -freq > 1: 43 | heappush(self.heap,(-freq+1, i)) 44 | return i 45 | 46 | 47 | def main(): 48 | frequencyStack = FrequencyStack() 49 | frequencyStack.push(1) 50 | frequencyStack.push(2) 51 | frequencyStack.push(3) 52 | frequencyStack.push(2) 53 | frequencyStack.push(1) 54 | frequencyStack.push(2) 55 | frequencyStack.push(5) 56 | print(frequencyStack.pop()) 57 | print(frequencyStack.pop()) 58 | print(frequencyStack.pop()) 59 | 60 | 61 | main() 62 | 63 | 64 | 65 | #answer 66 | from heapq import * 67 | 68 | 69 | class Element: 70 | 71 | def __init__(self, number, frequency, sequenceNumber): 72 | self.number = number 73 | self.frequency = frequency 74 | self.sequenceNumber = sequenceNumber 75 | 76 | def __lt__(self, other): 77 | # higher frequency wins 78 | if self.frequency != other.frequency: 79 | return self.frequency > other.frequency 80 | # if both elements have same frequency, return the element that was pushed later 81 | return self.sequenceNumber > other.sequenceNumber 82 | 83 | 84 | class FrequencyStack: 85 | sequenceNumber = 0 86 | maxHeap = [] 87 | frequencyMap = {} 88 | 89 | def push(self, num): 90 | self.frequencyMap[num] = self.frequencyMap.get(num, 0) + 1 91 | heappush(self.maxHeap, Element( 92 | num, self.frequencyMap[num], self.sequenceNumber)) 93 | self.sequenceNumber += 1 94 | 95 | def pop(self): 96 | num = heappop(self.maxHeap).number 97 | # decrement the frequency or remove if this is the last number 98 | if self.frequencyMap[num] > 1: 99 | self.frequencyMap[num] -= 1 100 | else: 101 | del self.frequencyMap[num] 102 | 103 | return num 104 | 105 | 106 | def main(): 107 | frequencyStack = FrequencyStack() 108 | frequencyStack.push(1) 109 | frequencyStack.push(2) 110 | frequencyStack.push(3) 111 | frequencyStack.push(2) 112 | frequencyStack.push(1) 113 | frequencyStack.push(2) 114 | frequencyStack.push(5) 115 | print(frequencyStack.pop()) 116 | print(frequencyStack.pop()) 117 | print(frequencyStack.pop()) 118 | 119 | 120 | main() 121 | 122 | 123 | 124 | ''' 125 | Time complexity 126 | The time complexity of push() and pop() is O(logN) where ‘N’ is the current number of elements in the heap. 127 | 128 | Space complexity 129 | We will need O(N) space for the heap and the map, so the overall space complexity of the algorithm is O(N). 130 | ''' -------------------------------------------------------------------------------- /09. Pattern Two Heaps/Find the Median of a Number Stream (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Design a class to calculate the median of a number stream. The class should have the following two methods: 4 | 5 | insertNum(int num): stores the number in the class 6 | findMedian(): returns the median of all numbers inserted in the class 7 | If the count of numbers inserted in the class is even, the median will be the average of the middle two numbers. 8 | 9 | Example 1: 10 | 11 | 1. insertNum(3) 12 | 2. insertNum(1) 13 | 3. findMedian() -> output: 2 14 | 4. insertNum(5) 15 | 5. findMedian() -> output: 3 16 | 6. insertNum(4) 17 | 7. findMedian() -> output: 3.5 18 | ''' 19 | 20 | #mycode 21 | from heapq import * 22 | 23 | class MedianOfAStream: 24 | 25 | minHeap = [] 26 | maxHeap = [] 27 | 28 | def insert_num(self, num): 29 | # TODO: Write your code here 30 | if not self.minHeap or num <= -self.minHeap[0]: 31 | heappush(self.minHeap, -num) 32 | else: 33 | heappush(self.maxHeap, num) 34 | 35 | if len(self.minHeap) > len(self.maxHeap) + 1: 36 | heappush(self.maxHeap, -heappop(self.minHeap)) 37 | elif len(self.minHeap) < len(self.maxHeap): 38 | heappush(self.minHeap,-heappop(self.maxHeap)) 39 | 40 | 41 | def find_median(self): 42 | # TODO: Write your code here 43 | if len(self.maxHeap) == len(self.minHeap): 44 | return (self.maxHeap[0] - self.minHeap[0]) / 2 45 | else: 46 | return -self.minHeap[0] 47 | 48 | 49 | def main(): 50 | medianOfAStream = MedianOfAStream() 51 | medianOfAStream.insert_num(3) 52 | medianOfAStream.insert_num(1) 53 | print("The median is: " + str(medianOfAStream.find_median())) 54 | medianOfAStream.insert_num(5) 55 | print("The median is: " + str(medianOfAStream.find_median())) 56 | medianOfAStream.insert_num(4) 57 | print("The median is: " + str(medianOfAStream.find_median())) 58 | 59 | 60 | main() 61 | 62 | 63 | 64 | #answer 65 | from heapq import * 66 | 67 | 68 | class MedianOfAStream: 69 | 70 | maxHeap = [] # containing first half of numbers 71 | minHeap = [] # containing second half of numbers 72 | 73 | def insert_num(self, num): 74 | if not self.maxHeap or -self.maxHeap[0] >= num: 75 | heappush(self.maxHeap, -num) 76 | else: 77 | heappush(self.minHeap, num) 78 | 79 | # either both the heaps will have equal number of elements or max-heap will have one 80 | # more element than the min-heap 81 | if len(self.maxHeap) > len(self.minHeap) + 1: 82 | heappush(self.minHeap, -heappop(self.maxHeap)) 83 | elif len(self.maxHeap) < len(self.minHeap): 84 | heappush(self.maxHeap, -heappop(self.minHeap)) 85 | 86 | def find_median(self): 87 | if len(self.maxHeap) == len(self.minHeap): 88 | # we have even number of elements, take the average of middle two elements 89 | return -self.maxHeap[0] / 2.0 + self.minHeap[0] / 2.0 90 | 91 | # because max-heap will have one more element than the min-heap 92 | return -self.maxHeap[0] / 1.0 93 | 94 | 95 | def main(): 96 | medianOfAStream = MedianOfAStream() 97 | medianOfAStream.insert_num(3) 98 | medianOfAStream.insert_num(1) 99 | print("The median is: " + str(medianOfAStream.find_median())) 100 | medianOfAStream.insert_num(5) 101 | print("The median is: " + str(medianOfAStream.find_median())) 102 | medianOfAStream.insert_num(4) 103 | print("The median is: " + str(medianOfAStream.find_median())) 104 | 105 | 106 | main() 107 | 108 | 109 | 110 | 111 | ''' 112 | Time complexity 113 | The time complexity of the insertNum() will be O(logN) due to the insertion in the heap. 114 | The time complexity of the findMedian() will be O(1) as we can find the median from the top elements of the heaps. 115 | 116 | Space complexity 117 | The space complexity will be O(N) because, as at any time, we will be storing all the numbers. 118 | ''' -------------------------------------------------------------------------------- /11. Pattern Modified Binary Search/Problem Challenge 1 - Search Bitonic Array (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 1 3 | Search Bitonic Array (medium) 4 | Given a Bitonic array, find if a given ‘key’ is present in it. 5 | An array is considered bitonic if it is monotonically increasing and then monotonically decreasing. 6 | Monotonically increasing or decreasing means that for any index i in the array arr[i] != arr[i+1]. 7 | 8 | Write a function to return the index of the ‘key’. If the ‘key’ is not present, return -1. 9 | 10 | Example 1: 11 | 12 | Input: [1, 3, 8, 4, 3], key=4 13 | Output: 3 14 | 15 | Example 2: 16 | 17 | Input: [3, 8, 3, 1], key=8 18 | Output: 1 19 | 20 | Example 3: 21 | 22 | Input: [1, 3, 8, 12], key=12 23 | Output: 3 24 | 25 | Example 4: 26 | 27 | Input: [10, 9, 8], key=10 28 | Output: 0 29 | ''' 30 | 31 | 32 | #mycode 33 | def search_bitonic_array(arr, key): 34 | # TODO: Write your code here 35 | maxIndex = search_maximum(arr) 36 | if key > arr[maxIndex]: 37 | return -1 38 | start, end = 0, maxIndex 39 | 40 | while start <= end: 41 | mid = (start + end) //2 42 | if arr[mid] < key: 43 | start = mid + 1 44 | elif arr[mid] > key: 45 | end = mid - 1 46 | else: 47 | return mid 48 | 49 | start, end = maxIndex, len(arr)-1 50 | while start <= end: 51 | mid = (start + end) //2 52 | if arr[mid] < key: 53 | end = mid - 1 54 | elif arr[mid] > key: 55 | start = mid + 1 56 | else: 57 | return mid 58 | 59 | return -1 60 | 61 | def search_maximum(arr): 62 | start, end = 0, len(arr)-1 63 | while start < end: 64 | mid = (start + end) //2 65 | if arr[mid] < arr[mid+1]: 66 | start = mid+1 67 | else: 68 | end = mid 69 | 70 | return start 71 | 72 | def main(): 73 | print(search_bitonic_array([1, 3, 8, 4, 3], 4)) 74 | print(search_bitonic_array([3, 8, 3, 1], 8)) 75 | print(search_bitonic_array([1, 3, 8, 12], 12)) 76 | print(search_bitonic_array([10, 9, 8], 10)) 77 | 78 | 79 | main() 80 | 81 | 82 | 83 | #answer 84 | def search_bitonic_array(arr, key): 85 | maxIndex = find_max(arr) 86 | keyIndex = binary_search(arr, key, 0, maxIndex) 87 | if keyIndex != -1: 88 | return keyIndex 89 | return binary_search(arr, key, maxIndex + 1, len(arr) - 1) 90 | 91 | 92 | # find index of the maximum value in a bitonic array 93 | def find_max(arr): 94 | start, end = 0, len(arr) - 1 95 | while start < end: 96 | mid = start + (end - start) // 2 97 | if arr[mid] > arr[mid + 1]: 98 | end = mid 99 | else: 100 | start = mid + 1 101 | 102 | # at the end of the while loop, 'start == end' 103 | return start 104 | 105 | 106 | # order-agnostic binary search 107 | def binary_search(arr, key, start, end): 108 | while start <= end: 109 | mid = int(start + (end - start) / 2) 110 | 111 | if key == arr[mid]: 112 | return mid 113 | 114 | if arr[start] < arr[end]: # ascending order 115 | if key < arr[mid]: 116 | end = mid - 1 117 | else: # key > arr[mid] 118 | start = mid + 1 119 | else: # descending order 120 | if key > arr[mid]: 121 | end = mid - 1 122 | else: # key < arr[mid] 123 | start = mid + 1 124 | 125 | return -1 # element is not found 126 | 127 | 128 | def main(): 129 | print(search_bitonic_array([1, 3, 8, 4, 3], 4)) 130 | print(search_bitonic_array([3, 8, 3, 1], 8)) 131 | print(search_bitonic_array([1, 3, 8, 12], 12)) 132 | print(search_bitonic_array([10, 9, 8], 10)) 133 | 134 | 135 | main() 136 | 137 | 138 | ''' 139 | Time complexity 140 | Since we are reducing the search range by half at every step, 141 | this means that the time complexity of our algorithm will be O(logN) 142 | where ‘N’ is the total elements in the given array. 143 | 144 | Space complexity 145 | The algorithm runs in constant space O(1). 146 | ''' -------------------------------------------------------------------------------- /08. Pattern Tree Depth First Search/Count Paths for a Sum (medium).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Statement 3 | Given a binary tree and a number ‘S’, 4 | find all paths in the tree such that the sum of all the node values of each path equals ‘S’. 5 | Please note that the paths can start or end at any node but all paths must follow direction from parent to child (top to bottom). 6 | ''' 7 | 8 | #mycode 9 | class TreeNode: 10 | def __init__(self, val, left=None, right=None): 11 | self.val = val 12 | self.left = left 13 | self.right = right 14 | 15 | 16 | def count_paths(root, S): 17 | # TODO: Write your code here 18 | return find_current_count(root,S,0) 19 | 20 | def find_current_count(currentNode, S, count): 21 | if not currentNode: 22 | return 0 23 | 24 | if currentNode.val == S: 25 | count += 1 26 | 27 | return count + find_current_count(currentNode.left, S, count) + find_current_count(currentNode.right, S, count) + find_current_count(currentNode.left, S - currentNode.val, count) + find_current_count(currentNode.right, S - currentNode.val, count) 28 | 29 | 30 | 31 | 32 | 33 | def main(): 34 | root = TreeNode(1) 35 | root.left = TreeNode(7) 36 | root.right = TreeNode(9) 37 | root.left.left = TreeNode(6) 38 | root.left.right = TreeNode(5) 39 | root.right.left = TreeNode(2) 40 | root.right.right = TreeNode(3) 41 | print("Tree has paths: " + str(count_paths(root, 12))) 42 | 43 | 44 | main() 45 | 46 | 47 | 48 | 49 | #answer 50 | class TreeNode: 51 | def __init__(self, val, left=None, right=None): 52 | self.val = val 53 | self.left = left 54 | self.right = right 55 | 56 | 57 | def count_paths(root, S): 58 | return count_paths_recursive(root, S, []) 59 | 60 | 61 | def count_paths_recursive(currentNode, S, currentPath): 62 | if currentNode is None: 63 | return 0 64 | 65 | # add the current node to the path 66 | currentPath.append(currentNode.val) 67 | pathCount, pathSum = 0, 0 68 | # find the sums of all sub-paths in the current path list 69 | for i in range(len(currentPath)-1, -1, -1): 70 | pathSum += currentPath[i] 71 | # if the sum of any sub-path is equal to 'S' we increment our path count. 72 | if pathSum == S: 73 | pathCount += 1 74 | 75 | # traverse the left sub-tree 76 | pathCount += count_paths_recursive(currentNode.left, S, currentPath) 77 | # traverse the right sub-tree 78 | pathCount += count_paths_recursive(currentNode.right, S, currentPath) 79 | 80 | # remove the current node from the path to backtrack 81 | # we need to remove the current node while we are going up the recursive call stack 82 | del currentPath[-1] 83 | 84 | return pathCount 85 | 86 | 87 | def main(): 88 | root = TreeNode(12) 89 | root.left = TreeNode(7) 90 | root.right = TreeNode(1) 91 | root.left.left = TreeNode(4) 92 | root.right.left = TreeNode(10) 93 | root.right.right = TreeNode(5) 94 | print("Tree has paths: " + str(count_paths(root, 11))) 95 | 96 | 97 | main() 98 | 99 | 100 | 101 | ''' 102 | Time complexity 103 | The time complexity of the above algorithm is O(N^2) in the worst case, where ‘N’ is the total number of nodes in the tree. 104 | This is due to the fact that we traverse each node once, but for every node, we iterate the current path. 105 | The current path, in the worst case, can be O(N) (in the case of a skewed tree). 106 | But, if the tree is balanced, then the current path will be equal to the height of the tree, i.e., O(logN). 107 | So the best case of our algorithm will be O(NlogN). 108 | 109 | Space complexity 110 | The space complexity of the above algorithm will be O(N). This space will be used to store the recursion stack. 111 | The worst case will happen when the given tree is a linked list (i.e., every node has only one child). 112 | We also need O(N) space for storing the currentPath in the worst case. 113 | Overall space complexity of our algorithm is O(N). 114 | ''' -------------------------------------------------------------------------------- /07. Pattern Breath Depth First Search/Problem Challenge 2 - Right View of a Binary Tree (easy).py: -------------------------------------------------------------------------------- 1 | ''' 2 | Problem Challenge 2 3 | 4 | Right View of a Binary Tree (easy) 5 | 6 | Given a binary tree, return an array containing nodes in its right view. 7 | The right view of a binary tree is the set of nodes visible when the tree is seen from the right side. 8 | ''' 9 | 10 | 11 | #mycode 12 | from __future__ import print_function 13 | from collections import deque 14 | 15 | 16 | class TreeNode: 17 | def __init__(self, val): 18 | self.val = val 19 | self.left, self.right = None, None 20 | 21 | 22 | def tree_right_view(root): 23 | result = [] 24 | # TODO: Write your code here 25 | if not root: 26 | return result 27 | 28 | queue = deque() 29 | queue.append(root) 30 | 31 | while queue: 32 | for i in range(len(queue)): 33 | 34 | current = queue.popleft() 35 | 36 | if current.left: 37 | queue.append(current.left) 38 | if current.right: 39 | queue.append(current.right) 40 | 41 | result.append(current) 42 | 43 | return result 44 | 45 | def main(): 46 | root = TreeNode(12) 47 | root.left = TreeNode(7) 48 | root.right = TreeNode(1) 49 | root.left.left = TreeNode(9) 50 | root.right.left = TreeNode(10) 51 | root.right.right = TreeNode(5) 52 | root.left.left.left = TreeNode(3) 53 | result = tree_right_view(root) 54 | print("Tree right view: ") 55 | for node in result: 56 | print(str(node.val) + " ", end='') 57 | 58 | 59 | main() 60 | 61 | 62 | 63 | 64 | #answer 65 | from __future__ import print_function 66 | from collections import deque 67 | 68 | 69 | class TreeNode: 70 | def __init__(self, val): 71 | self.val = val 72 | self.left, self.right = None, None 73 | 74 | 75 | def tree_right_view(root): 76 | result = [] 77 | if root is None: 78 | return result 79 | 80 | queue = deque() 81 | queue.append(root) 82 | while queue: 83 | levelSize = len(queue) 84 | for i in range(0, levelSize): 85 | currentNode = queue.popleft() 86 | # if it is the last node of this level, add it to the result 87 | if i == levelSize - 1: 88 | result.append(currentNode) 89 | # insert the children of current node in the queue 90 | if currentNode.left: 91 | queue.append(currentNode.left) 92 | if currentNode.right: 93 | queue.append(currentNode.right) 94 | 95 | return result 96 | 97 | 98 | def main(): 99 | root = TreeNode(12) 100 | root.left = TreeNode(7) 101 | root.right = TreeNode(1) 102 | root.left.left = TreeNode(9) 103 | root.right.left = TreeNode(10) 104 | root.right.right = TreeNode(5) 105 | root.left.left.left = TreeNode(3) 106 | result = tree_right_view(root) 107 | print("Tree right view: ") 108 | for node in result: 109 | print(str(node.val) + " ", end='') 110 | 111 | 112 | main() 113 | 114 | 115 | 116 | ''' 117 | Time complexity # 118 | The time complexity of the above algorithm is O(N), where ‘N’ is the total number of nodes in the tree. 119 | This is due to the fact that we traverse each node once. 120 | 121 | Space complexity 122 | The space complexity of the above algorithm will be O(N) as we need to return a list containing the level order traversal. 123 | We will also need O(N) space for the queue. Since we can have a maximum of N/2 nodes at any level 124 | (this could happen only at the lowest level), therefore we will need O(N) space to store them in the queue. 125 | ''' 126 | 127 | ''' 128 | Similar Questions # 129 | Problem 1: Given a binary tree, return an array containing nodes in its left view. 130 | The left view of a binary tree is the set of nodes visible when the tree is seen from the left side. 131 | 132 | Solution: We will be following a similar approach, 133 | but instead of appending the last element of each level we will be appending the first element of each level to the output array. 134 | ''' 135 | 136 | --------------------------------------------------------------------------------