├── code ├── move_zeros.py ├── toeplitz_matrix.py ├── getting_diff_num.py ├── merging_2_packages.py ├── largest_smaller_bst_key.py ├── array_products.py ├── sentence_reverse.py ├── k_messed_arr_sort.py ├── pancake_sort.py ├── sales_path.py ├── bracket_match.py ├── decrypt_message.py ├── validate_ip_address.py ├── shortest_word_edit_path.py ├── absolute_value_sort.py ├── root_of_number.py ├── smallest_substr_all_chars.py ├── basic_regex_parser.py ├── find_duplicates.py ├── time_planner.py ├── h_tree_construction.py ├── array_quadruplet.py ├── flatten_dictionary.py ├── busiest_time_in_mall.py ├── pairs_w_specific_diff.py ├── arr_i_and_element_equality.py ├── drone_flight_planner.py ├── word_count_engine.py ├── award_budget_cuts.py ├── island_count.py ├── bst_successor_search.py ├── number_of_paths.py ├── diff_btw_two_strs.py ├── shifted_array_search.py ├── decode_variations.py ├── matrix_spiral_copy.py ├── deletion_distance.py └── sudoku_solver.py └── README.md /code/move_zeros.py: -------------------------------------------------------------------------------- 1 | """ 2 | Move Zeros To End 3 | 4 | Given a static-sized array of integers arr, move all zeroes in the array to the end of the array. 5 | You should preserve the relative order of items in the array. 6 | 7 | We should implement a solution that is more efficient than a naive brute force. 8 | 9 | input: arr = [1, 10, 0, 2, 8, 3, 0, 0, 6, 4, 0, 5, 7, 0] 10 | output: [1, 10, 2, 8, 3, 6, 4, 5, 7, 0, 0, 0, 0, 0] 11 | 12 | """ 13 | 14 | # O(n) time 15 | # O(1) space 16 | def moveZerosToEnd(arr): 17 | left_pointer = 0 # everything before left_pointer is not a zero 18 | for i in range(len(arr)): 19 | if arr[i] != 0: 20 | arr[i], arr[left_pointer] = arr[left_pointer], arr[i] 21 | left_pointer += 1 22 | 23 | return arr 24 | -------------------------------------------------------------------------------- /code/toeplitz_matrix.py: -------------------------------------------------------------------------------- 1 | """ 2 | Toeplitz Matrix 3 | 4 | A Toeplitz matrix is a matrix where every left-to-right-descending diagonal has the same element. 5 | Given a non-empty matrix arr, write a function that returns true if and only if it is a Toeplitz matrix. 6 | The matrix can be any dimensions, not necessarily square. 7 | 8 | For example, 9 | 10 | [[1,2,3,4], 11 | [5,1,2,3], 12 | [6,5,1,2]] 13 | is a Toeplitz matrix, so we should return true, while 14 | 15 | [[1,2,3,4], 16 | [5,1,9,3], 17 | [6,5,1,2]] 18 | isn’t a Toeplitz matrix, so we should return false. 19 | """ 20 | 21 | # O(mn) time 22 | # O(1) space 23 | def isToeplitz(matrix): 24 | m, n = len(matrix), len(matrix[0]) 25 | for i in range(1, m): 26 | for j in range(1, n): 27 | if matrix[i][j] != matrix[i - 1][j - 1]: 28 | return False 29 | 30 | return True 31 | -------------------------------------------------------------------------------- /code/getting_diff_num.py: -------------------------------------------------------------------------------- 1 | """ 2 | Getting a Different Number 3 | 4 | Given an array arr of unique nonnegative integers, implement a function getDifferentNumber that finds the smallest nonnegative integer that is NOT in the array. 5 | 6 | input: arr = [0, 1, 2, 3] 7 | output: 4 8 | 9 | input: arr = [0, 2, 3] 10 | output: 1 11 | """ 12 | 13 | # O(n) time 14 | # O(n) space 15 | def get_different_number(arr): 16 | n = len(arr) 17 | arr_set = set(arr) 18 | 19 | for i in range(n): 20 | if i not in arr_set: 21 | return i 22 | 23 | return n 24 | 25 | 26 | # O(n) time 27 | # O(1) space 28 | def get_different_number(arr): 29 | n = len(arr) 30 | 31 | for i in range(n): 32 | curr = arr[i] 33 | while curr != i and curr < n: 34 | arr[i], arr[curr] = arr[curr], arr[i] 35 | curr = arr[i] 36 | 37 | for i in range(n): 38 | if arr[i] != i: 39 | return i 40 | 41 | return n 42 | -------------------------------------------------------------------------------- /code/merging_2_packages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Merging 2 Packages 3 | 4 | Given a package with a weight limit limit and an array arr of item weights, 5 | implement a function getIndicesOfItemWeights that finds two items whose sum of weights equals the weight limit limit. 6 | Your function should return a pair [i, j] of the indices of the item weights, ordered such that i > j. 7 | If such a pair doesn’t exist, return an empty array. 8 | 9 | 10 | input: arr = [4, 6, 10, 15, 16], lim = 21 11 | 12 | output: [3, 1] # since these are the indices of the 13 | # weights 6 and 15 whose sum equals to 21 14 | 15 | """ 16 | 17 | # O(n) time 18 | # O(n) space 19 | def get_indices_of_item_weights(arr, limit): 20 | if len(arr) < 2: 21 | return [] 22 | 23 | complements = {} 24 | 25 | for i, num in enumerate(arr): 26 | complement = limit - num 27 | if complement in complements: 28 | j = complements[complement] 29 | return [i, j] 30 | complements[num] = i 31 | 32 | return [] 33 | 34 | -------------------------------------------------------------------------------- /code/largest_smaller_bst_key.py: -------------------------------------------------------------------------------- 1 | """ 2 | Largest Smaller BST Key 3 | 4 | Given a root of a Binary Search Tree (BST) and a number num, implement an efficient function findLargestSmallerKey that finds the largest key in the tree 5 | that is smaller than num. If such a number doesn’t exist, return -1. Assume that all keys in the tree are nonnegative. 6 | 7 | 8 | For num = 17 and the binary search tree below: 9 | 10 | 20 11 | / \ 12 | 9 25 13 | / \ 14 | 5 12 15 | / \ 16 | 11 14 17 | 18 | Your function would return: 14 since it’s the largest key in the tree that is still smaller than 17. 19 | 20 | """ 21 | 22 | # O(logn) time complexity if the tree is balanced, O(N) otherwise 23 | # O(1) space 24 | def find_largest_smaller_key(self, num): 25 | predecessor = -1 26 | node = self.root 27 | 28 | while node: 29 | if node.key >= num: 30 | node = node.left 31 | else: 32 | predecessor = node.key 33 | node = node.right 34 | 35 | return predecessor 36 | -------------------------------------------------------------------------------- /code/array_products.py: -------------------------------------------------------------------------------- 1 | """ 2 | Array of Array Products 3 | 4 | Given an array of integers arr, you’re asked to calculate for each index i the product of all integers except the integer at that index (i.e. except arr[i]). 5 | Implement a function arrayOfArrayProducts that takes an array of integers and returns an array of the products. 6 | 7 | 8 | input: arr = [8, 10, 2] 9 | output: [20, 16, 80] # by calculating: [10*2, 8*2, 8*10] 10 | 11 | input: arr = [2, 7, 3, 4] 12 | output: [84, 24, 56, 42] # by calculating: [7*3*4, 2*3*4, 2*7*4, 2*7*3] 13 | 14 | """ 15 | 16 | # O(n) time 17 | # O(n) space 18 | def array_of_array_products(arr): 19 | n = len(arr) 20 | if n < 2: # no values to multiply if n equals to 0 or 1 21 | return [] 22 | 23 | products_arr = [None for _ in range(n)] 24 | 25 | product_before_i = 1 26 | for i in range(n): 27 | products_arr[i] = product_before_i 28 | product_before_i *= arr[i] 29 | 30 | product_after_j = 1 31 | for j in range(n - 1, -1, -1): 32 | products_arr[j] *= product_after_j 33 | product_after_j *= arr[j] 34 | 35 | return products_arr 36 | -------------------------------------------------------------------------------- /code/sentence_reverse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sentence Reverse 3 | 4 | You are given an array of characters arr that consists of sequences of characters separated by space characters. 5 | Each space-delimited sequence of characters defines a word. 6 | 7 | Implement a function reverseWords that reverses the order of the words in the array in the most efficient manner. 8 | 9 | 10 | input: arr = [ 'p', 'e', 'r', 'f', 'e', 'c', 't', ' ', 11 | 'm', 'a', 'k', 'e', 's', ' ', 12 | 'p', 'r', 'a', 'c', 't', 'i', 'c', 'e' ] 13 | 14 | output: [ 'p', 'r', 'a', 'c', 't', 'i', 'c', 'e', ' ', 15 | 'm', 'a', 'k', 'e', 's', ' ', 16 | 'p', 'e', 'r', 'f', 'e', 'c', 't' ] 17 | 18 | """ 19 | 20 | # O(n) time 21 | # O(1) space 22 | def reverse_helper(arr, start, end): 23 | while start < end: 24 | arr[start], arr[end] = arr[end], arr[start] 25 | start += 1 26 | end -= 1 27 | 28 | def reverse_words(arr): 29 | n = len(arr) 30 | reverse_helper(arr, 0, n - 1) 31 | 32 | word_start = 0 33 | for i in range(n + 1): 34 | if i == n or arr[i] == " ": 35 | reverse_helper(arr, word_start, i - 1) 36 | word_start = i + 1 37 | 38 | return arr 39 | -------------------------------------------------------------------------------- /code/k_messed_arr_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | K-Messed Array Sort 3 | 4 | Given an array of integers arr where each element is at most k places away from its sorted position, code an efficient function sortKMessedArray that sorts arr. 5 | For instance, for an input array of size 10 and k = 2, an element belonging to index 6 in the sorted array 6 | will be located at either index 4, 5, 6, 7 or 8 in the input array. 7 | 8 | 9 | input: arr = [1, 4, 5, 2, 3, 7, 8, 6, 10, 9], k = 2 10 | 11 | output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 12 | 13 | """ 14 | 15 | # O(nlogk) time 16 | # O(k) space 17 | import heapq 18 | def sort_k_messed_array(arr, k): 19 | n = len(arr) 20 | # Build min-heap for first k elements 21 | heap = arr[:k + 1] 22 | heapq.heapify(heap) 23 | 24 | sorted_pointer = 0 25 | for i in range(k + 1, n): 26 | # Assign top element to correct index in array 27 | arr[sorted_pointer] = heapq.heappop(heap) 28 | sorted_pointer += 1 29 | # Push next element in array into the min-heap 30 | heapq.heappush(heap, arr[i]) 31 | 32 | # Extract remaining elements from the min-heap 33 | while heap: 34 | arr[sorted_pointer] = heapq.heappop(heap) 35 | sorted_pointer += 1 36 | 37 | return arr 38 | -------------------------------------------------------------------------------- /code/pancake_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pancake Sort 3 | 4 | Given an array of integers arr: 5 | 1. Write a function flip(arr, k) that reverses the order of the first k elements in the array arr. 6 | 2. Write a function pancakeSort(arr) that sorts and returns the input array. 7 | You are allowed to use only the function flip you wrote in the first step in order to make changes in the array. 8 | 9 | 10 | input: arr = [1, 5, 4, 3, 2] 11 | 12 | output: [1, 2, 3, 4, 5] # to clarify, this is pancakeSort's output 13 | 14 | """ 15 | 16 | 17 | def flip(arr, k): 18 | low = 0 19 | high = k 20 | while low < high: 21 | arr[low], arr[high] = arr[high], arr[low] 22 | low += 1 23 | high -= 1 24 | 25 | 26 | def find_max_index(arr, last_index): 27 | max_num_index = 0 28 | for i in range(1, last_index + 1): 29 | if arr[i] > arr[max_num_index]: 30 | max_num_index = i 31 | return max_num_index 32 | 33 | 34 | # O(n^2) time 35 | # O(1) space 36 | def pancake_sort(arr): 37 | n = len(arr) 38 | for i in range(n - 1, -1, -1): 39 | max_num_index = find_max_index(arr, i) 40 | flip(arr, max_num_index) 41 | flip(arr, i) # i is the last index of the unsorted portion of the array 42 | return arr 43 | -------------------------------------------------------------------------------- /code/sales_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sales Path 3 | 4 | The car manufacturer Honda holds their distribution system in the form of a tree (not necessarily binary). 5 | The root is the company itself, and every node in the tree represents a car distributor that receives cars from the parent node 6 | and ships them to its children nodes. The leaf nodes are car dealerships that sell cars direct to consumers. 7 | In addition, every node holds an integer that is the cost of shipping a car to it. 8 | 9 | Take for example the tree below: 10 | 0 11 | / | \ 12 | 5 3 6 13 | / / \ / \ 14 | 4 2 0 1 5 15 | / / 16 | 1 10 17 | \ 18 | 1 19 | 20 | 21 | """ 22 | 23 | # O(n) time 24 | # O(n) space 25 | def get_cheapest_cost(rootNode): 26 | stack = [(rootNode, rootNode.cost)] 27 | min_cost = float('inf') 28 | 29 | while stack: 30 | curr_node, curr_cost = stack.pop() 31 | if not curr_node.children: 32 | min_cost = min(min_cost, curr_cost) 33 | else: 34 | for child_node in curr_node.children: 35 | stack.append((child_node, curr_cost + child_node.cost)) 36 | 37 | return min_cost 38 | -------------------------------------------------------------------------------- /code/bracket_match.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bracket Match 3 | 4 | A string of brackets is considered correctly matched if every opening bracket in the string can be paired up with a later closing bracket, and vice versa. 5 | For instance, “(())()” is correctly matched, whereas “)(“ and “((” aren’t. For instance, “((” could become correctly matched by adding two closing brackets 6 | at the end, so you’d return 2. 7 | 8 | Given a string that consists of brackets, write a function bracketMatch that takes a bracket string as an input and returns the minimum number of brackets 9 | you’d need to add to the input in order to make it correctly matched. 10 | 11 | 12 | input: text = “(()” 13 | output: 1 14 | 15 | input: text = “(())” 16 | output: 0 17 | 18 | input: text = “())(” 19 | output: 2 20 | """ 21 | 22 | # O(n) time 23 | # O(1) space 24 | def bracket_match(text): 25 | count_missing_closing = 0 26 | count_missing_opening = 0 27 | n = len(text) 28 | 29 | for char in text: 30 | if char == "(": 31 | count_missing_closing += 1 32 | elif char == ")": 33 | count_missing_closing -= 1 34 | if count_missing_closing < 0: 35 | count_missing_closing += 1 36 | count_missing_opening += 1 37 | 38 | return count_missing_closing + count_missing_opening 39 | -------------------------------------------------------------------------------- /code/decrypt_message.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decrypt Message 3 | 4 | Every word is encrypted as follows: 5 | - Convert every letter to its ASCII value. 6 | - Add 1 to the first letter, and then for every letter from the second one to the last one, add the value of the previous letter. 7 | - Subtract 26 from every letter until it is in the range of lowercase letters a-z in ASCII. 8 | - Convert the values back to letters. 9 | 10 | For instance, to encrypt the word “crime” 11 | 12 | Decrypted message: c r i m e 13 | Step 1: 99 114 105 109 101 14 | Step 2: 100 214 319 428 529 15 | Step 3: 100 110 111 116 113 16 | Encrypted message: d n o t q 17 | 18 | Write a function named decrypt(word) that receives a string that consists of small latin letters only, and returns the decrypted word. 19 | 20 | input: word = "dnotq" 21 | output: "crime" 22 | 23 | input: word = "flgxswdliefy" 24 | output: "encyclopedia" 25 | 26 | """ 27 | 28 | # O(n) time 29 | # O(n) space 30 | def decrypt(word): 31 | decryption = "" 32 | prev_letter_val = 1 33 | 34 | for letter in word: 35 | letter_ascii_val = ord(letter) 36 | letter_ascii_val -= prev_letter_val 37 | 38 | while letter_ascii_val < ord('a'): 39 | letter_ascii_val += 26 40 | 41 | decryption += chr(letter_ascii_val) 42 | prev_letter_val += letter_ascii_val 43 | 44 | return decryption 45 | -------------------------------------------------------------------------------- /code/validate_ip_address.py: -------------------------------------------------------------------------------- 1 | """ 2 | Validate IP Address 3 | 4 | Validate an IP address (IPv4). An address is valid if and only if it is in the form "X.X.X.X", where each X is a number from 0 to 255. 5 | 6 | For example, "12.34.5.6", "0.23.25.0", and "255.255.255.255" are valid IP addresses, 7 | while "12.34.56.oops", "1.2.3.4.5", and "123.235.153.425" are invalid IP addresses. 8 | 9 | 10 | ip = '192.168.0.1' 11 | output: true 12 | 13 | ip = '0.0.0.0' 14 | output: true 15 | 16 | ip = '123.24.59.99' 17 | output: true 18 | 19 | ip = '192.168.123.456' 20 | output: false 21 | 22 | """ 23 | 24 | # O(n) time 25 | # O(n) space 26 | def validateIP(ip): 27 | nums = ip.split(".") 28 | if len(nums) != 4: 29 | return False 30 | 31 | for num in nums: 32 | if not is_valid_num(num): 33 | return False 34 | 35 | return True 36 | 37 | 38 | def is_valid_num(num): 39 | if not num: 40 | return False 41 | 42 | # Check that each character in num is a digit 0-9 43 | for digit in num: 44 | if digit < '0' or digit > '9': # if not digit.isdigit(): 45 | return False 46 | 47 | # Check for leading zeros 48 | # E.g. '4' should return True, '04' and '004' should return False. 49 | if len(num) > 1 and num[0] == '0': 50 | return False 51 | 52 | # Check that num is in the range 0-255 53 | if int(num) < 0 or int(num) > 255: 54 | return False 55 | 56 | return True 57 | -------------------------------------------------------------------------------- /code/shortest_word_edit_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shortest Word Edit Path 3 | 4 | Given two words source and target, and a list of words words, find the length of the shortest series of edits that transforms source to target. 5 | 6 | Each edit must change exactly one letter at a time, and each intermediate word (and the final target word) must exist in words. 7 | 8 | If the task is impossible, return -1. 9 | 10 | 11 | source = "bit", target = "dog" 12 | words = ["but", "put", "big", "pot", "pog", "dog", "lot"] 13 | 14 | output: 5 15 | explanation: bit -> but -> put -> pot -> pog -> dog has 5 transitions. 16 | source = "no", target = "go" 17 | words = ["to"] 18 | 19 | output: -1 20 | """ 21 | 22 | # O(n * m^2) time where n is the len(words) and m is the longest word 23 | # O(nm) space 24 | import string 25 | from collections import deque 26 | 27 | def shortestWordEditPath(source, target, words): 28 | if target not in words: 29 | return -1 30 | 31 | alphabet = string.ascii_lowercase 32 | words_set = set(words) 33 | queue = deque([(source, 0)]) 34 | seen = set() 35 | 36 | while queue: 37 | curr_word, depth = queue.popleft() 38 | if curr_word == target: 39 | return depth 40 | for i in range(len(curr_word)): 41 | for char in alphabet: 42 | next_word = curr_word[:i] + char + curr_word[i+1:] 43 | if next_word in words_set and next_word not in seen: 44 | queue.append((next_word, depth+1)) 45 | seen.add(next_word) 46 | 47 | return -1 48 | -------------------------------------------------------------------------------- /code/absolute_value_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Absolute Value Sort 3 | 4 | Given an array of integers arr, write a function absSort(arr), that sorts the array according to the absolute values of the numbers in arr. 5 | If two numbers have the same absolute value, sort them according to sign, where the negative numbers come before the positive numbers. 6 | 7 | Examples: 8 | 9 | input: arr = [2, -7, -2, -2, 0] 10 | output: [0, -2, -2, 2, -7] 11 | """ 12 | 13 | 14 | # O(n^2) time 15 | # O(1) space 16 | def absSort(arr): 17 | for i in range(len(arr)): 18 | for j in range(i, len(arr)): 19 | if swap(arr[i], arr[j]): 20 | arr[i], arr[j] = arr[j], arr[i] 21 | return arr 22 | 23 | def swap(num1, num2): 24 | if abs(num1) < abs(num2): 25 | return False 26 | elif abs(num1) > abs(num2): 27 | return True 28 | else: # Case where abs values of numbers are equal, e.g. -2 and 2 29 | return num1 > num2 30 | 31 | 32 | # O(nlogn) the time complexity of sorting 33 | # O(n) the space typically used by compilers in their implementation of sorting operations 34 | def absSort(arr): 35 | return sorted(arr, cmp = compare) 36 | 37 | def compare(a,b): 38 | # Compare abs values 39 | if abs(a) < abs(b): 40 | return -1 41 | elif abs(a) > abs(b): 42 | return 1 43 | # Compare signs 44 | elif a < b: 45 | return -1 46 | elif a > b: 47 | return 1 48 | else: 49 | return 0 50 | 51 | 52 | # Using python 53 | def absSort(arr): 54 | return sorted(arr, key = lambda x: (abs(x), x)) 55 | -------------------------------------------------------------------------------- /code/root_of_number.py: -------------------------------------------------------------------------------- 1 | """ 2 | Root of Number 3 | 4 | Many times, we need to re-implement basic functions without using any standard library functions already implemented. 5 | For example, when designing a chip that requires very little memory space. 6 | 7 | In this question we’ll implement a function root that calculates the n’th root of a number. 8 | The function takes a nonnegative number x and a positive integer n, and returns the positive n’th root of x within an error of 0.001 9 | (i.e. suppose the real root is y, then the error is: |y-root(x,n)| and must satisfy |y-root(x,n)| < 0.001). 10 | 11 | Don’t be intimidated by the question. While there are many algorithms to calculate roots that require prior knowledge in numerical analysis 12 | (some of them are mentioned here), there is also an elementary method which doesn’t require more than guessing-and-checking. 13 | Try to think more in terms of the latter. 14 | 15 | Make sure your algorithm is efficient, and analyze its time and space complexities. 16 | 17 | 18 | input: x = 7, n = 3 19 | output: 1.913 20 | 21 | input: x = 9, n = 2 22 | output: 3 23 | """ 24 | 25 | # O(log(x)) time 26 | # O(1) space 27 | def root(x, n): 28 | if n == 0: 29 | raise ValueError("Can't take 0'th root of a number.") 30 | 31 | low = 0 32 | high = max(1, x) 33 | 34 | while high - low > 0.001: 35 | mid = (low + high) / 2.0 36 | if mid ** n < x: 37 | low = mid 38 | elif mid ** n > x: 39 | high = mid 40 | else: # mid ** n == x: 41 | return mid 42 | 43 | return mid 44 | -------------------------------------------------------------------------------- /code/smallest_substr_all_chars.py: -------------------------------------------------------------------------------- 1 | """ 2 | Smallest Substring of All Characters (Minimum Window Substring) 3 | 4 | Given an array of unique characters arr and a string str, Implement a function getShortestUniqueSubstring that finds the smallest substring of str 5 | containing all the characters in arr. Return "" (empty string) if such a substring doesn’t exist. 6 | 7 | input: arr = ['x','y','z'], str = "xyyzyzyx" 8 | 9 | output: "zyx" 10 | 11 | """ 12 | 13 | # O(m + n) time 14 | # O(m) space 15 | def get_shortest_unique_substring(arr, string): 16 | start_idx = 0 17 | substring = "" 18 | expected_char_num = len(arr) 19 | actual_char_num = 0 20 | char_to_count = {key: 0 for key in arr} 21 | 22 | for end_idx in range(len(string)): 23 | add_char = string[end_idx] 24 | if add_char not in char_to_count: 25 | continue 26 | if char_to_count[add_char] == 0: 27 | actual_char_num += 1 28 | char_to_count[add_char] += 1 29 | 30 | # if substring satisfies, make window smaller by incrementing start_idx 31 | while actual_char_num == expected_char_num: 32 | curr_window_len = end_idx - start_idx + 1 33 | if curr_window_len == expected_char_num: 34 | # case: we won't find a shorter substring that satisfies 35 | return string[start_idx:end_idx + 1] 36 | if substring == "" or curr_window_len < len(substring): 37 | substring = string[start_idx:end_idx + 1] 38 | remove_char = string[start_idx] 39 | if remove_char in char_to_count: 40 | char_to_count[remove_char] -= 1 41 | if char_to_count[remove_char] == 0: 42 | actual_char_num -= 1 43 | start_idx += 1 44 | 45 | return substring 46 | -------------------------------------------------------------------------------- /code/basic_regex_parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic Regex Parser 3 | 4 | Implement a regular expression function isMatch that supports the '.' and '*' symbols. The function receives two strings - text and pattern - 5 | and should return true if the text matches the pattern as a regular expression. 6 | 7 | In case you aren’t familiar with regular expressions, the function determines if the text and pattern are the equal, where the '.' 8 | is treated as a single a character wildcard (see third example), and '*' is matched for a zero or more sequence of the previous letter. 9 | 10 | 11 | input: text = "aa", pattern = "a" 12 | output: false 13 | 14 | input: text = "aa", pattern = "aa" 15 | output: true 16 | 17 | input: text = "abc", pattern = "a.c" 18 | output: true 19 | 20 | input: text = "abbb", pattern = "ab*" 21 | output: true 22 | 23 | input: text = "acd", pattern = "ab*c." 24 | output: true 25 | 26 | """ 27 | 28 | def is_match(text, pattern): 29 | m, n = len(text), len(pattern) 30 | if not m and not n: 31 | return True 32 | if not pattern: # e.g. text = "a" pattern = "" 33 | return False 34 | 35 | dp = [[False for j in range(n + 1)] for i in range(m + 1)] 36 | dp[0][0] = True 37 | for j in range(1, n + 1): 38 | if pattern[j - 1] == "*": 39 | dp[0][j] = dp[0][j-1] or (dp[0][j - 2] if j > 1 else False) 40 | 41 | for i in range(1, m + 1): 42 | for j in range(1, n + 1): 43 | if text[i - 1] == pattern[j - 1] or pattern[j - 1] == ".": 44 | dp[i][j] = dp[i - 1][j - 1] 45 | elif pattern[j - 1] == "*": 46 | dp[i][j] = dp[i][j - 1] or (dp[i][j - 2] if j > 1 else False) 47 | if j > 1 and (text[i - 1] == pattern[j - 2] or pattern[j - 2] == "."): 48 | dp[i][j] = dp[i][j] or dp[i - 1][j] 49 | 50 | return dp[-1][-1] 51 | -------------------------------------------------------------------------------- /code/find_duplicates.py: -------------------------------------------------------------------------------- 1 | "" 2 | Find The Duplicates 3 | 4 | Given two sorted arrays arr1 and arr2 of passport numbers, implement a function findDuplicates that returns an array of all passport numbers 5 | that are both in arr1 and arr2. Note that the output array should be sorted in an ascending order. 6 | 7 | Let N and M be the lengths of arr1 and arr2, respectively. Solve for two cases and analyze the time & space complexities of your solutions: 8 | M ≈ N - the array lengths are approximately the same 9 | M ≫ N - arr2 is much bigger than arr1. 10 | 11 | input: arr1 = [1, 2, 3, 5, 6, 7], arr2 = [3, 6, 7, 8, 20] 12 | output: [3, 6, 7] 13 | 14 | "" 15 | 16 | 17 | # O(n + m) time 18 | # O(n) space 19 | def find_duplicates(arr1, arr2): 20 | duplicates = [] 21 | 22 | m, n = len(arr1), len(arr2) 23 | i, j = 0, 0 24 | 25 | while i < m and j < n: 26 | if arr2[j] > arr1[i]: 27 | i += 1 28 | elif arr1[i] > arr2[j]: 29 | j += 1 30 | else: # arr1[i] == arr2[j] 31 | duplicates.append(arr1[i]) 32 | i += 1 33 | j += 1 34 | 35 | return duplicates 36 | 37 | 38 | # O(mlogn) time where m = len(arr1) and n = len(arr2) 39 | # O(n) space 40 | def find_duplicates(arr1, arr2): 41 | # Make arr1 the shorter array 42 | if arr2 < arr1: 43 | arr1, arr2 = arr2, arr1 44 | duplicates = [] 45 | 46 | # Traverse the shorter array 47 | for num in arr1: 48 | if binary_search(arr2, num): 49 | duplicates.append(num) 50 | 51 | return duplicates 52 | 53 | 54 | def binary_search(arr, num): 55 | left = 0 56 | right = len(arr) - 1 57 | 58 | while left <= right: 59 | mid = (left + right) // 2 60 | if arr[mid] < num: 61 | left = mid + 1 62 | elif arr[mid] > num: 63 | right = mid - 1 64 | else: # arr[mid] == num 65 | return True 66 | 67 | return False 68 | -------------------------------------------------------------------------------- /code/time_planner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Time Planner 3 | 4 | Implement a function meetingPlanner that given the availability, slotsA and slotsB, of two people and a meeting duration dur, 5 | returns the earliest time slot that works for both of them and is of duration dur. 6 | If there is no common time slot that satisfies the duration requirement, return an empty array. 7 | 8 | Each person’s availability is represented by an array of pairs. Each pair is an epoch array of size two. 9 | The first epoch in a pair represents the start time of a slot. The second epoch is the end time of that slot. 10 | The input variable dur is a positive integer that represents the duration of a meeting in seconds. 11 | The output is also a pair represented by an epoch array of size two. 12 | 13 | In your implementation assume that the time slots in a person’s availability are disjointed, i.e, 14 | time slots in a person’s availability don’t overlap. Further assume that the slots are sorted by slots’ start time. 15 | 16 | 17 | input: slotsA = [[10, 50], [60, 120], [140, 210]] 18 | slotsB = [[0, 15], [60, 70]] 19 | dur = 8 20 | output: [60, 68] 21 | 22 | input: slotsA = [[10, 50], [60, 120], [140, 210]] 23 | slotsB = [[0, 15], [60, 70]] 24 | dur = 12 25 | output: [] # since there is no common slot whose duration is 12 26 | 27 | """ 28 | 29 | # O(m + n) time 30 | # O(1) space 31 | 32 | def meeting_planner(slotsA, slotsB, dur): 33 | A_idx, B_idx = 0, 0 34 | m, n = len(slotsA), len(slotsB) 35 | 36 | while A_idx < m and B_idx < n: 37 | A_start, A_end = slotsA[A_idx] 38 | B_start, B_end = slotsB[B_idx] 39 | 40 | start = max(A_start, B_start) 41 | end = min(A_end, B_end) 42 | overlap = end - start 43 | if overlap >= dur: 44 | return [start, start + dur] 45 | 46 | if A_end < B_end: 47 | A_idx += 1 48 | else: 49 | B_idx += 1 50 | 51 | return [] 52 | -------------------------------------------------------------------------------- /code/h_tree_construction.py: -------------------------------------------------------------------------------- 1 | """ 2 | H-Tree Construction 3 | 4 | An H-tree is a geometric shape that consists of a repeating pattern resembles the letter “H”. 5 | 6 | It can be constructed by starting with a line segment of arbitrary length, drawing two segments of the same length 7 | at right angles to the first through its endpoints, and continuing in the same vein, 8 | reducing (dividing) the length of the line segments drawn at each stage by √2. 9 | 10 | Write a function drawHTree that constructs an H-tree, given its center (x and y coordinates), a starting length, and depth. 11 | Assume that the starting line is parallel to the X-axis. 12 | 13 | Use the function drawLine provided to implement your algorithm. 14 | In a production code, a drawLine function would render a real line between two points. 15 | However, this is not a real production environment, so to make things easier, implement drawLine such that it simply prints its arguments 16 | (the print format is left to your discretion). 17 | 18 | """ 19 | 20 | # O(4^depth) 21 | # O(depth) 22 | def drawHTree(x, y, length, depth): 23 | if depth == 0: 24 | return 25 | 26 | x0 = x - length / 2.0 27 | x1 = x + length / 2.0 28 | y0 = y - length / 2.0 29 | y1 = y + length / 2.0 30 | 31 | # Draw 3 line segments of the H-tree 32 | drawLine(x0, y0, x0, y1) # left segment 33 | drawLine(x1, y0, x1, y1) # right segment 34 | drawLine(x0, y, x1, y) # connecting segment 35 | 36 | # The length decreases at each depth 37 | new_length = length / (2 ** (1/2)) 38 | 39 | # Decrement depth by 1 and draw an H-tree 40 | # at each of the tips of the current H 41 | drawHTree(x0, y0, new_length, depth - 1) # lower left H-tree 42 | drawHTree(x0, y1, new_length, depth - 1) # upper left H-tree 43 | drawHTree(x1, y0, new_length, depth - 1) # lower right H-tree 44 | drawHTree(x1, y1, new_length, depth - 1) # upper right H-tree 45 | 46 | def drawLine(x1, y1, x2, y2): 47 | print([x1, y1], [x2, y2]) 48 | -------------------------------------------------------------------------------- /code/array_quadruplet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Array Quadruplet (meaning 4Sum) 3 | 4 | Given an unsorted array of integers arr and a number s, write a function findArrayQuadruplet that finds four numbers (quadruplet) in arr that sum up to s. 5 | Your function should return an array of these numbers in an ascending order. If such a quadruplet doesn’t exist, return an empty array. 6 | 7 | Note that there may be more than one quadruplet in arr whose sum is s. You’re asked to return the first one you encounter (considering the results are sorted). 8 | 9 | Explain and code the most efficient solution possible, and analyze its time and space complexities. 10 | 11 | 12 | input: arr = [2, 7, 4, 0, 9, 5, 1, 3], s = 20 13 | 14 | output: [0, 4, 7, 9] # The ordered quadruplet of (7, 4, 0, 9) 15 | # whose sum is 20. Notice that there 16 | # are two other quadruplets whose sum is 20: 17 | # (7, 9, 1, 3) and (2, 4, 9, 5), but again you’re 18 | # asked to return the just one quadruplet (in an 19 | # ascending order) 20 | 21 | """ 22 | 23 | # O(n^3) time 24 | # O(1) space 25 | def find_array_quadruplet(nums, target): 26 | n = len(nums) 27 | if n < 4: 28 | return [] 29 | 30 | nums.sort() 31 | 32 | for i in range(n - 3): 33 | if i != 0 and nums[i] == nums[i - 1]: 34 | continue 35 | for j in range(i + 1, n - 2): 36 | if j != 1 and nums[j] == nums[j - 1]: 37 | continue 38 | curr_sum = nums[i] + nums[j] 39 | target_complement = target - curr_sum 40 | low = j + 1 41 | high = n - 1 42 | 43 | while low < high: 44 | curr_complement = nums[low] + nums[high] 45 | if curr_complement > target_complement: 46 | high -= 1 47 | elif curr_complement < target_complement: 48 | low += 1 49 | else: 50 | return [nums[i], nums[j], nums[low], nums[high]] 51 | 52 | return [] 53 | -------------------------------------------------------------------------------- /code/flatten_dictionary.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flatten a Dictionary 3 | 4 | Given a dictionary dict, write a function flattenDictionary that returns a flattened version of it. 5 | If a certain key is empty, it should be excluded from the output (see e in the example below). 6 | 7 | 8 | input: dict = { 9 | "Key1" : "1", 10 | "Key2" : { 11 | "a" : "2", 12 | "b" : "3", 13 | "c" : { 14 | "d" : "3", 15 | "e" : { 16 | "" : "1" 17 | } 18 | } 19 | } 20 | } 21 | 22 | output: { 23 | "Key1" : "1", 24 | "Key2.a" : "2", 25 | "Key2.b" : "3", 26 | "Key2.c.d" : "3", 27 | "Key2.c.e" : "1" 28 | } 29 | 30 | """ 31 | 32 | # O(n) time, where n is the number of keys in the input dictionary 33 | # O(n) space 34 | def flatten_dictionary(nested_dict): 35 | flat_dict = {} 36 | flat_dict_recurse(nested_dict, flat_dict) 37 | return flat_dict 38 | 39 | def flat_dict_recurse(nested_dict, flat_dict, path=""): 40 | for key, value in nested_dict.items(): 41 | include_dot = 1 if path and key else 0 42 | updated_path = path + "." * include_dot + key 43 | if not isinstance(value, dict): 44 | flat_dict[updated_path] = value 45 | else: 46 | flat_dict_recurse(value, flat_dict, updated_path) 47 | 48 | 49 | # Another way of solving the problem 50 | def flatten_dictionary(nested_dict): 51 | if not isinstance(nested_dict, dict): 52 | return {"": nested_dict} 53 | 54 | flat_dict = {} 55 | 56 | for key, value in nested_dict.items(): 57 | flat_items = flatten_dictionary(value) 58 | for key_of_flat_item, value_of_flat_item in flat_items.items(): 59 | add_dot = 1 if key and key_of_flat_item else 0 60 | concat_key_of_flat_item = key + ("." * add_dot) + key_of_flat_item 61 | flat_dict[concat_key_of_flat_item] = value_of_flat_item 62 | 63 | return flat_dict 64 | -------------------------------------------------------------------------------- /code/busiest_time_in_mall.py: -------------------------------------------------------------------------------- 1 | """ 2 | Busiest Time in The Mall 3 | 4 | The Westfield Mall management is trying to figure out what the busiest moment at the mall was last year. 5 | You’re given data extracted from the mall’s door detectors. Each data point is represented as an integer array whose size is 3. 6 | The values at indices 0, 1 and 2 are the timestamp, the count of visitors, and whether the visitors entered or exited the mall (0 for exit and 1 for entrance), 7 | respectively. Here’s an example of a data point: [ 1440084737, 4, 0 ]. 8 | 9 | Given an array, data, of data points, write a function findBusiestPeriod that returns the time at which the mall reached its busiest moment last year. 10 | The return value is the timestamp, e.g. 1480640292. Note that if there is more than one period with the same visitor peak, return the earliest one. 11 | 12 | Assume that the array data is sorted in an ascending order by the timestamp. 13 | 14 | 15 | input: data = [ [1487799425, 14, 1], 16 | [1487799425, 4, 0], 17 | [1487799425, 2, 0], 18 | [1487800378, 10, 1], 19 | [1487801478, 18, 0], 20 | [1487801478, 18, 1], 21 | [1487901013, 1, 0], 22 | [1487901211, 7, 1], 23 | [1487901211, 7, 0] ] 24 | 25 | output: 1487800378 26 | 27 | """ 28 | 29 | # O(n) time 30 | # O(1) space 31 | def find_busiest_period(data): 32 | n = len(data) 33 | count = 0 34 | max_count = 0 35 | timestamp = None 36 | 37 | for i in range(n): 38 | curr_timestamp, visitors, entered = data[i] 39 | if entered: # entered == 1 means visitors entered 40 | count += visitors 41 | else: # entered == 0 means visitors exited 42 | count -= visitors 43 | 44 | # Check if there are more data points with same timestamp 45 | if i < n - 1 and curr_timestamp == data[i + 1][0]: 46 | continue 47 | 48 | # Update max 49 | if count > max_count: 50 | max_count = count 51 | timestamp = curr_timestamp 52 | 53 | return timestamp 54 | -------------------------------------------------------------------------------- /code/pairs_w_specific_diff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pairs with Specific Difference 3 | 4 | Given an array arr of distinct integers and a nonnegative integer k, 5 | write a function findPairsWithGivenDifference that returns an array of all pairs [x,y] in arr, such that x - y = k. 6 | If no such pairs exist, return an empty array. 7 | 8 | Note: the order of the pairs in the output array should maintain the order of the y element in the original array. 9 | 10 | 11 | input: arr = [0, -1, -2, 2, 1], k = 1 12 | output: [[1, 0], [0, -1], [-1, -2], [2, 1]] 13 | 14 | input: arr = [1, 7, 5, 3, 32, 17, 12], k = 17 15 | output: [] 16 | 17 | """ 18 | 19 | 20 | # all solutions are O(n) time and O(n) space complexity 21 | 22 | 23 | # one pass solution 24 | # however, doesn't return output in required order 25 | def find_pairs_with_given_difference(arr, k): 26 | output = [] 27 | complements = set() 28 | 29 | for num in arr: 30 | # if num is y 31 | x = k + num 32 | # if num is x 33 | y = num - k 34 | if x in complements: 35 | output.append([x, num]) 36 | if y in complements: 37 | output.append([num, y]) 38 | complements.add(num) 39 | 40 | return output 41 | 42 | 43 | # two pass solution 44 | # pass once through the array to create a set for constant lookups 45 | # pass a second time through the array to see if we have a pair that satisfies x - y = k 46 | def find_pairs_with_given_difference(arr, k): 47 | output = [] 48 | arr_set = set(arr) 49 | 50 | for y in arr: 51 | x = k + y 52 | if x in arr_set: 53 | output.append([x, y]) 54 | 55 | return output 56 | 57 | 58 | # solution provided by pramp 59 | # also requires two passes 60 | # on the first pass: create a y_to_x hashmap 61 | # on the second pass: check if y is in the y_to_x hashmap 62 | def find_pairs_with_given_difference(arr, k): 63 | output = [] 64 | y_to_x = {} 65 | 66 | for x in arr: 67 | y = x - k 68 | y_to_x[y] = x 69 | 70 | for y in arr: 71 | if y in y_to_x: 72 | x = y_to_x[y] 73 | output.append([x, y]) 74 | 75 | return output 76 | -------------------------------------------------------------------------------- /code/arr_i_and_element_equality.py: -------------------------------------------------------------------------------- 1 | """ 2 | Array Index & Element Equality 3 | 4 | Given a sorted array arr of distinct integers, write a function indexEqualsValueSearch that returns the lowest index i for which arr[i] == i. 5 | Return -1 if there is no such index. Analyze the time and space complexities of your solution and explain its correctness. 6 | 7 | 8 | input: arr = [-8,0,2,5] 9 | output: 2 # since arr[2] == 2 10 | 11 | input: arr = [-1,0,3,6] 12 | output: -1 # since no index in arr satisfies arr[i] == i. 13 | 14 | """ 15 | 16 | 17 | # O(logn) time 18 | # O(1) space 19 | def index_equals_value_search(arr): 20 | low = 0 21 | high = len(arr) - 1 22 | 23 | while low <= high: 24 | mid = low + (high - low) // 2 25 | if arr[mid] == mid: 26 | # check if the previous index also equals its element 27 | while mid > 0 and arr[mid - 1] == mid - 1: 28 | mid -= 1 29 | return mid 30 | elif arr[mid] < mid: 31 | low = mid + 1 32 | else: 33 | high = mid - 1 34 | 35 | return -1 36 | 37 | 38 | def index_equals_value_search(arr): 39 | low = 0 40 | high = len(arr) - 1 41 | 42 | while low <= high: 43 | mid = low + (high - low) // 2 44 | if arr[mid] == mid and (mid == 0 or arr[mid - 1] != mid - 1): 45 | return mid 46 | elif arr[mid] < mid: 47 | low = mid + 1 48 | else: 49 | # includes case where arr[mid] > mid 50 | # or arr[mid - 1] == mid - 1 51 | high = mid - 1 52 | 53 | return -1 54 | 55 | 56 | def binary_search(arr): 57 | low = 0 58 | high = len(arr) - 1 59 | 60 | while low <= high: 61 | mid = low + (high - low) // 2 62 | if arr[mid] == mid: 63 | return mid 64 | elif arr[mid] < mid: 65 | low = mid + 1 66 | else: 67 | high = mid - 1 68 | 69 | return -1 70 | 71 | def index_equals_value_search(arr): 72 | low = 0 73 | high = len(arr) - 1 74 | 75 | ans = binary_search(arr) 76 | # check if the previous index also equals its element 77 | while ans > 0 and arr[ans - 1] == ans - 1: 78 | ans -= 1 79 | 80 | return ans 81 | -------------------------------------------------------------------------------- /code/drone_flight_planner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Drone Flight Planner 3 | 4 | You’re an engineer at a disruptive drone delivery startup and your CTO asks you to come up with an efficient algorithm that calculates 5 | the minimum amount of energy required for the company’s drone to complete its flight. 6 | You know that the drone burns 1 kWh (kilowatt-hour is an energy unit) for every mile it ascends, and it gains 1 kWh for every mile it descends. 7 | Flying sideways neither burns nor adds any energy. 8 | 9 | Given an array route of 3D points, implement a function calcDroneMinEnergy that computes and returns the minimal amount of energy the drone would need 10 | to complete its route. Assume that the drone starts its flight at the first point in route. That is, no energy was expended to place the drone at the starting point. 11 | 12 | For simplicity, every 3D point will be represented as an integer array whose length is 3. 13 | Also, the values at indexes 0, 1, and 2 represent the x, y and z coordinates in a 3D point, respectively. 14 | 15 | 16 | input: route = [ [0, 2, 10], 17 | [3, 5, 0], 18 | [9, 20, 6], 19 | [10, 12, 15], 20 | [10, 10, 8] ] 21 | 22 | output: 5 # less than 5 kWh and the drone would crash before the finish 23 | # line. More than `5` kWh and it’d end up with excess e 24 | 25 | """ 26 | 27 | 28 | # O(n) time 29 | # O(1) space 30 | def calc_drone_min_energy(route): 31 | energy_balance = 0 32 | energy_deficit = 0 33 | 34 | prev_altitude = route[0][2] 35 | for i in range(1, len(route)): 36 | curr_altitude = route[i][2] 37 | energy_balance += prev_altitude - curr_altitude 38 | if energy_balance < 0: 39 | energy_deficit += abs(energy_balance) 40 | energy_balance = 0 41 | prev_altitude = curr_altitude 42 | 43 | return energy_deficit 44 | 45 | 46 | def calc_drone_min_energy(route): 47 | max_altitude = route[0][2] 48 | 49 | for i in range(1, len(route)): 50 | if route[i][2] > max_altitude: 51 | max_altitude = route[i][2] 52 | 53 | return max_altitude - route[0][2] 54 | -------------------------------------------------------------------------------- /code/word_count_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Word Count Engine 3 | 4 | Implement a document scanning function wordCountEngine, which receives a string document and returns a list of all unique words in it 5 | and their number of occurrences, sorted by the number of occurrences in a descending order. If two or more words have the same count, 6 | they should be sorted according to their order in the original sentence. Assume that all letters are in english alphabet. 7 | You function should be case-insensitive, so for instance, the words “Perfect” and “perfect” should be considered the same word. 8 | 9 | The engine should strip out punctuation (even in the middle of a word) and use whitespaces to separate words. 10 | 11 | 12 | input: document = "Practice makes perfect. you'll only 13 | get Perfect by practice. just practice!" 14 | 15 | output: [ ["practice", "3"], ["perfect", "2"], 16 | ["makes", "1"], ["youll", "1"], ["only", "1"], 17 | ["get", "1"], ["by", "1"], ["just", "1"] ] 18 | Important: please convert the occurrence integers in the output list to strings (e.g. "3" instead of 3). 19 | 20 | """ 21 | 22 | # Let N be the number of words in document and M the number of unique words in it (M ≤ N) 23 | # O(N) time 24 | # O(M) space 25 | from collections import OrderedDict 26 | 27 | def word_count_engine(document): 28 | word_to_count = OrderedDict() 29 | words = document.split() 30 | max_count = 0 31 | 32 | for word in words: 33 | tidy_word = "" 34 | for char in word: 35 | if char.isalpha(): 36 | tidy_word += char.lower() 37 | 38 | if tidy_word: 39 | curr_count = word_to_count.get(tidy_word, 0) + 1 40 | word_to_count[tidy_word] = curr_count 41 | max_count = max(max_count, curr_count) 42 | 43 | # Bucket sort 44 | buckets = [[] for _ in range(max_count + 1)] 45 | for key, value in word_to_count.items(): 46 | buckets[value].append(key) 47 | 48 | # Iterate through buckets 49 | output = [] 50 | for i in range(len(buckets) - 1, 0, -1): 51 | for word in buckets[i]: 52 | output.append([word, str(i)]) # i is the count 53 | return output 54 | -------------------------------------------------------------------------------- /code/award_budget_cuts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Award Budget Cuts 3 | 4 | The awards committee of your alma mater (i.e. your college/university) asked for your assistance with a budget allocation problem they’re facing. 5 | Originally, the committee planned to give N research grants this year. However, due to spending cutbacks, the budget was reduced to newBudget dollars 6 | and now they need to reallocate the grants. The committee made a decision that they’d like to impact as few grant recipients as possible by applying 7 | a maximum cap on all grants. Every grant initially planned to be higher than cap will now be exactly cap dollars. 8 | Grants less or equal to cap, obviously, won’t be impacted. 9 | 10 | Given an array grantsArray of the original grants and the reduced budget newBudget, 11 | write a function findGrantsCap that finds in the most efficient manner a cap such that the least number of recipients is impacted 12 | and that the new budget constraint is met (i.e. sum of the N reallocated grants equals to newBudget). 13 | 14 | 15 | input: grantsArray = [2, 100, 50, 120, 1000], newBudget = 190 16 | 17 | output: 47 # and given this cap the new grants array would be 18 | # [2, 47, 47, 47, 47]. Notice that the sum of the 19 | # new grants is indeed 190 20 | 21 | """ 22 | 23 | 24 | # O(nlogn) time 25 | # O(1) space 26 | def find_grants_cap(grantsArray, newBudget): 27 | n = len(grantsArray) 28 | grantsArray.sort() 29 | 30 | # Initiate variables to track how much of the budget is left 31 | # and how many grants left to cover 32 | amount_budget_left = float(newBudget) 33 | count_grants_left = n 34 | for i in range(n): 35 | money_req = grantsArray[i] * count_grants_left 36 | if money_req >= amount_budget_left: 37 | # Case 1: we'd need more money than we have left 38 | # meaning we need to set the cap at this point 39 | cap = amount_budget_left / count_grants_left 40 | return cap 41 | # Case 2: we have more money left to allocate 42 | # so we don't set the cap yet 43 | amount_budget_left -= grantsArray[i] 44 | count_grants_left -= 1 45 | 46 | return newBudget 47 | -------------------------------------------------------------------------------- /code/island_count.py: -------------------------------------------------------------------------------- 1 | """ 2 | Island Count 3 | 4 | Given a 2D array binaryMatrix of 0s and 1s, implement a function getNumberOfIslands that returns the number of islands of 1s in binaryMatrix. 5 | 6 | An island is defined as a group of adjacent values that are all 1s. A cell in binaryMatrix is considered adjacent to another cell if they are next 7 | to each either on the same row or column. Note that two values of 1 are not part of the same island if they’re sharing only a mutual “corner” 8 | (i.e. they are diagonally neighbors). 9 | 10 | 11 | input: binaryMatrix = [ [0, 1, 0, 1, 0], 12 | [0, 0, 1, 1, 1], 13 | [1, 0, 0, 1, 0], 14 | [0, 1, 1, 0, 0], 15 | [1, 0, 1, 0, 1] ] 16 | 17 | output: 6 # since this is the number of islands in binaryMatrix. 18 | # See all 6 islands color-coded below. 19 | 20 | """ 21 | 22 | # Recursive 23 | # O(mn) time 24 | # O(mn) space 25 | def get_number_of_islands(binaryMatrix): 26 | m, n = len(binaryMatrix), len(binaryMatrix[0]) 27 | islands = 0 28 | for i in range(m): 29 | for j in range(n): 30 | if binaryMatrix[i][j] == 1: 31 | dfs(binaryMatrix, i, j, m, n) 32 | islands += 1 33 | return islands 34 | 35 | def dfs(matrix, i, j, m, n): 36 | if i < 0 or i >= m or j < 0 or j >= n or matrix[i][j] != 1: 37 | return 38 | matrix[i][j] = -1 39 | dfs(matrix, i - 1, j, m, n) 40 | dfs(matrix, i + 1, j, m, n) 41 | dfs(matrix, i, j - 1, m, n) 42 | dfs(matrix, i, j + 1, m, n) 43 | 44 | 45 | # Iterative 46 | # O(mn) time 47 | # O(mn) space 48 | def get_number_of_islands(binaryMatrix): 49 | m, n = len(binaryMatrix), len(binaryMatrix[0]) 50 | islands = 0 51 | for i in range(m): 52 | for j in range(n): 53 | if binaryMatrix[i][j] == 1: 54 | dfs(binaryMatrix, i, j, m, n) 55 | islands += 1 56 | return islands 57 | 58 | def dfs(matrix, x, y, m, n): 59 | stack = [[x, y]] 60 | while stack: 61 | i, j = stack.pop() 62 | if i >= 0 and i < m and j >= 0 and j < n and matrix[i][j] == 1: 63 | matrix[i][j] = -1 64 | stack.append([i - 1, j]) 65 | stack.append([i + 1, j]) 66 | stack.append([i, j - 1]) 67 | stack.append([i, j + 1]) 68 | return 69 | -------------------------------------------------------------------------------- /code/bst_successor_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | BST Successor Search 3 | 4 | In a Binary Search Tree (BST), an Inorder Successor of a node is defined as the node with the smallest key greater than the key of the input node 5 | (see examples below). Given a node inputNode in a BST, you’re asked to write a function findInOrderSuccessor that returns the Inorder Successor of inputNode. 6 | If inputNode has no Inorder Successor, return null. 7 | 8 | 20 9 | / \ 10 | 9 25 11 | / \ 12 | 5 12 13 | / \ 14 | 11 14 15 | 16 | input = node 11 17 | output = node 12 18 | 19 | input = node 9 20 | output = node 11 21 | 22 | input = node 14 23 | output = node 20 24 | 25 | """ 26 | 27 | # O(n) time 28 | # O(1) space 29 | 30 | # Starting at the root 31 | def find_in_order_successor(self, inputNode): 32 | successor = None 33 | node = self.root 34 | 35 | while node: 36 | if node.key > inputNode.key: 37 | successor = node 38 | node = node.left 39 | else: 40 | node = node.right 41 | 42 | return successor 43 | 44 | 45 | # Starting at the node 46 | def find_in_order_successor(self, inputNode): 47 | curr_node = inputNode 48 | 49 | if curr_node.right: 50 | # Return left-most node in the right subtree 51 | curr_node = curr_node.right 52 | while curr_node.left: 53 | curr_node = curr_node.left 54 | return curr_node 55 | 56 | else: 57 | # Return first ancestor whose key is larger than inputNode's key 58 | while curr_node.parent: 59 | if curr_node.parent.key < curr_node.key: 60 | curr_node = curr_node.parent 61 | else: 62 | return curr_node.parent 63 | 64 | # Succesor doesn't exist 65 | return None 66 | 67 | 68 | # Starting at the node, modular 69 | def find_in_order_successor(self, inputNode): 70 | if inputNode.right: 71 | return self.find_min_key(inputNode.right) 72 | 73 | ancestor = inputNode.parent 74 | child = inputNode 75 | while ancestor and child == ancestor.right: 76 | child = ancestor 77 | ancestor = child.parent 78 | return ancestor 79 | 80 | def find_min_key(self, inputNode): 81 | while inputNode.left: 82 | inputNode = inputNode.left 83 | return inputNode 84 | -------------------------------------------------------------------------------- /code/number_of_paths.py: -------------------------------------------------------------------------------- 1 | """ 2 | Number of Paths 3 | 4 | You’re testing a new driverless car that is located at the Southwest (bottom-left) corner of an n×n grid. 5 | The car is supposed to get to the opposite, Northeast (top-right), corner of the grid. 6 | Given n, the size of the grid’s axes, write a function numOfPathsToDest that returns the number of the possible paths the driverless car can take. 7 | 8 | For convenience, let’s represent every square in the grid as a pair (i,j). 9 | The first coordinate in the pair denotes the east-to-west axis, and the second coordinate denotes the south-to-north axis. 10 | The initial state of the car is (0,0), and the destination is (n-1,n-1). 11 | 12 | The car must abide by the following two rules: it cannot cross the diagonal border. 13 | In other words, in every step the position (i,j) needs to maintain i >= j. 14 | In every step, it may go one square North (up), or one square East (right), but not both. E.g. if the car is at (3,1), it may go to (3,2) or (4,1). 15 | 16 | input: n = 4 17 | output: 5 18 | 19 | """ 20 | 21 | 22 | # Recursive 23 | # O(n^2) time 24 | # O(n^2) space 25 | def num_of_paths_to_dest(n): 26 | memo = [[0 for _ in range(n)] for _ in range(n)] 27 | return dfs(0, 0, memo, n) 28 | 29 | def dfs(i, j, memo, n): 30 | # Base case 31 | if i == n - 1 and j == n - 1: 32 | return 1 33 | if memo[i][j]: 34 | return memo[i][j] 35 | 36 | number_of_paths = 0 37 | # Going up 38 | if i < n - 1 and i < j: 39 | number_of_paths += dfs(i + 1, j, memo, n) 40 | # Going right 41 | if j < n - 1: 42 | number_of_paths += dfs(i, j + 1, memo, n) 43 | 44 | memo[i][j] = number_of_paths 45 | return number_of_paths 46 | 47 | 48 | # Iterative 49 | # O(n^2) time 50 | # O(n^2) space 51 | def num_of_paths_to_dest(n): 52 | dp = [[0 for j in range(n)] for i in range(n)] 53 | dp[0] = [1 for j in range(n)] 54 | 55 | for i in range(1, n): 56 | for j in range(1, n): 57 | if i <= j: 58 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 59 | 60 | return dp[-1][-1] 61 | 62 | 63 | # Iterative 64 | # O(n^2) time 65 | # O(n) space 66 | def num_of_paths_to_dest(n): 67 | prev = [1 for j in range(n)] 68 | curr = [0 for j in range(n)] 69 | 70 | for i in range(1, n): 71 | for j in range(1, n): 72 | if i <= j: 73 | curr[j] = prev[j] + curr[j - 1] 74 | prev = curr 75 | 76 | return prev[-1] 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pramp Python Solutions 2 | [Pramp](http://pramp.com) is a peer-2-peer platform for practicing technical interviews. This repository is a collection of questions I've seen and their solutions in Python 🐍. 3 | 4 | ### Array and Strings 5 | * [Bracket Match](code/bracket_match.py) 6 | * [Move Zeros To End](code/move_zeros.py) 0️⃣ 7 | * [Find The Duplicates](code/find_duplicates.py) 8 | * [Drone Flight Planner](code/drone_flight_planner.py) ✈️ 9 | * [Validate IP Address](code/validate_ip_address.py) 🌐📍 10 | * [Array of Array Products](code/array_products.py) 11 | * [Busiest Time in The Mall](code/busiest_time_in_mall.py) ⌚🛍️ 12 | * [Award Budget Cuts](code/award_budget_cuts.py) 💵✂️ 13 | * [Array Index & Element Equality](code/arr_i_and_element_equality.py) 14 | * [Getting a Different Number](code/getting_diff_num.py) 15 | * [Sentence Reverse](code/sentence_reverse.py) 16 | * [Merging 2 Packages](code/merging_2_packages.py) 📦📦 17 | * [Pairs with Specific Difference](code/pairs_w_specific_diff.py) 18 | * [Toeplitz Matrix](code/toeplitz_matrix.py) 19 | * [Word Count Engine](code/word_count_engine.py) 20 | * [Time Planner](code/time_planner.py) ⏰📓 21 | * [Matrix Spiral Copy](code/matrix_spiral_copy.py) 🌀 22 | * [Smallest Substring of All Characters](code/smallest_substr_all_chars.py) 23 | * [Array Quadruplet](code/array_quadruplet.py) 4️⃣ 24 | 25 | ### Trees and Graphs 26 | * [Sales Path](code/sales_path.py) 27 | * [BST Successor Search](bst_successor_search.py) 28 | * [Largest Smaller BST Key](code/largest_smaller_bst_key.py) 29 | * [Shortest Word Edit Path](code/shortest_word_edit_path.py) 30 | 31 | ### Sorting and Searching 32 | * [Absolute Value Sort](code/absolute_value_sort.py) 33 | * [K-Messed Array Sort](code/k_messed_arr_sort.py) 34 | * [Shifted Array Search](code/shifted_array_search.py) 35 | * [Pancake Sort](code/pancake_sort.py) 🥞 36 | 37 | ### Recursion and DP 38 | * [Island Count](code/island_count.py) 🏝️ 39 | * [Decode Variations](code/decode_variations.py) 40 | * [Flatten a Dictionary](code/flatten_dictionary.py) 📖 41 | * [Number of Paths](code/number_of_paths.py) 🚗 42 | * [Sudoku Solver](code/sudoku_solver.py) 43 | * [Deletion Distance](code/deletion_distance.py) ❌📏 44 | * [Basic Regex Parser](code/basic_regex_parser.py) 45 | * [Diff Between Two Strings](code/diff_btw_two_strs.py) 46 | * [H-Tree Construction](code/h_tree_construction.py) 47 | 48 | ### Math 49 | * [Root of Number](code/root_of_number.py) 50 | * [Decrypt Message](code/decrypt_message.py) ✉️ 51 | 52 | -------------------------------------------------------------------------------- /code/diff_btw_two_strs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Diff Between Two Strings 3 | 4 | Given two strings of uppercase letters source and target, list (in string form) a sequence of edits to convert from source to target 5 | that uses the least edits possible. 6 | 7 | For example, with strings source = "ABCDEFG", and target = "ABDFFGH" we might return: ["A", "B", "-C", "D", "-E", "F", "+F", "G", "+H" 8 | 9 | More formally, for each character C in source, we will either write the token C, which does not count as an edit; 10 | or write the token -C, which counts as an edit. 11 | 12 | Additionally, between any token that we write, we may write +D where D is any letter, which counts as an edit. 13 | 14 | At the end, when reading the tokens from left to right, and not including tokens prefixed with a minus-sign, the letters should spell out target 15 | (when ignoring plus-signs.) 16 | 17 | In the example, the answer of A B -C D -E F +F G +H has total number of edits 4 (the minimum possible), a 18 | nd ignoring subtraction-tokens, spells out A, B, D, F, +F, G, +H which represents the string target. 19 | 20 | If there are multiple answers, use the answer that favors removing from the source first. 21 | 22 | """ 23 | 24 | # O(mn) time 25 | # O(mn) space 26 | # Doesn't return correct order in case of multiple answers 27 | def diffBetweenTwoStrings(source, target): 28 | m, n = len(target), len(source) 29 | dp = [[0 for j in range(n + 1)] for i in range(m + 1)] 30 | 31 | # Build dp 32 | for i in range(m + 1): 33 | for j in range(n + 1): 34 | if i == 0: 35 | dp[i][j] = j 36 | elif j == 0: 37 | dp[i][j] = i 38 | else: 39 | if target[i - 1] == source[j - 1]: 40 | dp[i][j] = dp[i - 1][j - 1] 41 | else: 42 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1 43 | 44 | # Reconstruct path 45 | path = [] 46 | i, j = m, n 47 | 48 | while i > 0 and j > 0: 49 | if target[i - 1] == source[j - 1]: 50 | # Write char with no edits 51 | path.append(source[j - 1]) 52 | i -= 1 53 | j -= 1 54 | else: 55 | # We must either subtract source[j - 1] or add target[i - 1] 56 | if dp[i][j - 1] < dp[i - 1][j]: 57 | path.append("-" + source[j - 1]) 58 | j -= 1 59 | else: 60 | path.append("+" + target[i - 1]) 61 | i -= 1 62 | 63 | while i > 0: 64 | path.append("+" + target[i - 1]) 65 | i -= 1 66 | 67 | while j > 0: 68 | path.append("-" + source[j - 1]) 69 | j -= 1 70 | 71 | path.reverse() 72 | return path 73 | -------------------------------------------------------------------------------- /code/shifted_array_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shifted Array Search 3 | 4 | A sorted array of distinct integers shiftArr is shifted to the left by an unknown offset and you don’t have a pre-shifted copy of it. 5 | For instance, the sequence 1, 2, 3, 4, 5 becomes 3, 4, 5, 1, 2, after shifting it twice to the left. 6 | 7 | Given shiftArr and an integer num, implement a function shiftedArrSearch that finds and returns the index of num in shiftArr. 8 | If num isn’t in shiftArr, return -1. Assume that the offset can be any value between 0 and arr.length - 1. 9 | 10 | Explain your solution and analyze its time and space complexities. 11 | 12 | 13 | input: shiftArr = [9, 12, 17, 2, 4, 5], num = 2 # shiftArr is the 14 | # outcome of shifting 15 | # [2, 4, 5, 9, 12, 17] 16 | # three times to the left 17 | 18 | output: 3 # since it’s the index of 2 in arr 19 | """ 20 | 21 | # one pass solution 22 | # O(logn) time 23 | # O(1) space 24 | def shifted_arr_search(arr, num): 25 | low, high = 0, len(arr) - 1 26 | 27 | while low <= high: 28 | mid = (low + high) // 2 29 | # Case 0: we found the target num at index mid 30 | if arr[mid] == num: 31 | return mid 32 | # Case 1: no pivot in low-mid range, nums are ordered 33 | elif arr[low] <= [mid]: 34 | if arr[low] <= num < arr[mid]: 35 | high = mid - 1 36 | else: 37 | low = mid + 1 38 | # Case 2: pivot in low-mid range, nums are unordered 39 | else: 40 | if arr[mid] < num <= arr[high]: 41 | low = mid + 1 42 | else: 43 | high = mid - 1 44 | 45 | return -1 46 | 47 | 48 | # two pass solution 49 | def shifted_arr_search(arr, num): 50 | n = len(arr) 51 | pivot = find_pivot(arr, 0, n - 1) 52 | 53 | if pivot == 0 or arr[0] > num: 54 | return binary_search(arr, pivot, n - 1, num) 55 | 56 | return binary_search(arr, 0, pivot - 1, num) 57 | 58 | def find_pivot(arr, low, high): 59 | while low <= high: 60 | mid = low + (high - low) // 2 61 | if mid == 0 or arr[mid - 1] > arr[mid]: 62 | return mid 63 | elif arr[mid] > arr[0]: 64 | low = mid + 1 65 | else: 66 | high = mid - 1 67 | 68 | return 0 69 | 70 | def binary_search(arr, low, high, num): 71 | while low <= high: 72 | mid = low + (high - low) // 2 73 | if arr[mid] == num: 74 | return mid 75 | elif arr[mid] < num: 76 | low = mid + 1 77 | else: 78 | high = mid - 1 79 | 80 | return -1 81 | -------------------------------------------------------------------------------- /code/decode_variations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decode Variations 3 | 4 | A letter can be encoded to a number in the following way: 5 | 6 | 'A' -> '1', 'B' -> '2', 'C' -> '3', ..., 'Z' -> '26' 7 | A message is a string of uppercase letters, and it is encoded first using this scheme. For example, 'AZB' -> '1262' 8 | 9 | Given a string of digits S from 0-9 representing an encoded message, return the number of ways to decode it. 10 | 11 | 12 | input: S = '1262' 13 | output: 3 14 | explanation: There are 3 messages that encode to '1262': 'AZB', 'ABFB', and 'LFB'. 15 | 16 | """ 17 | 18 | 19 | # Recursive 20 | # O(n) time 21 | # O(n) space 22 | def decodeVariations(S): 23 | return decode_variations_dfs(S, 0, {}) 24 | 25 | def decode_variations_dfs(S, i, memo): 26 | if i == len(S): 27 | return 1 28 | if i in memo: 29 | return memo[i] 30 | 31 | count = 0 32 | if S[i] != "0": 33 | count += decode_variations_dfs(S, i+1, memo) 34 | if 10 <= int(S[i:i+2]) <= 26: 35 | count += decode_variations_dfs(S, i+2, memo) 36 | 37 | memo[i] = count 38 | return count 39 | 40 | 41 | # DP 42 | def decodeVariations(S): 43 | dp = [0 for i in range(len(S))] 44 | dp[0] = 1 45 | 46 | for i in range(1, len(S)): 47 | if S[i] != "0": 48 | dp[i] += dp[i - 1] 49 | if 10 <= int(S[i-1:i+1]) <= 26: 50 | dp[i] += dp[i - 2] if i - 2 >= 0 else 1 51 | 52 | return dp[-1] 53 | 54 | 55 | # Iterative (DP) 56 | # O(n) time 57 | # O(1) space 58 | def decodeVariations(S): 59 | if not S or S[0] == "0": 60 | return 0 61 | 62 | prev = 1 63 | curr = 1 64 | 65 | for i in range(1, len(S)): 66 | temp = curr 67 | if S[i] == "0": 68 | if S[i-1] == "1" or S[i-1] == "2": 69 | curr = prev 70 | else: 71 | return 0 72 | elif 10 <= int(S[i-1:i+1]) <= 26: 73 | curr += prev 74 | prev = temp 75 | 76 | return curr 77 | 78 | 79 | # Iterative 80 | def decodeVariations(S): 81 | if not S or S[0] == "0": 82 | return 0 83 | 84 | prev_prev = 1 85 | prev = 1 86 | 87 | for i in range(1, len(S)): 88 | curr = 0 89 | if S[i] != "0": 90 | curr += prev 91 | if 10 <= int(S[i-1:i+1]) <= 26: 92 | curr += prev_prev 93 | prev_prev = prev 94 | prev = curr 95 | 96 | return prev 97 | 98 | 99 | 100 | # This will work IRL 101 | # but doesn't pass test cases because memo gets cached 102 | def decodeVariations(string, i=0, memo={}): 103 | if i in memo: 104 | return memo[i] 105 | if i == len(string): 106 | return 1 107 | 108 | count = 0 109 | 110 | if string[i] != "0": 111 | count += decodeVariations(string, i + 1, memo) 112 | if 10 <= int(string[i:i+2]) <= 26: 113 | count += decodeVariations(string, i + 2, memo) 114 | 115 | memo[i] = count 116 | return count 117 | -------------------------------------------------------------------------------- /code/matrix_spiral_copy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Matrix Spiral Copy 3 | 4 | Given a 2D array (matrix) inputMatrix of integers, create a function spiralCopy that copies inputMatrix’s values into a 1D array in a spiral order, clockwise. 5 | Your function then should return that array. Analyze the time and space complexities of your solution. 6 | 7 | 8 | input: inputMatrix = [ [1, 2, 3, 4, 5], 9 | [6, 7, 8, 9, 10], 10 | [11, 12, 13, 14, 15], 11 | [16, 17, 18, 19, 20] ] 12 | 13 | output: [1, 2, 3, 4, 5, 10, 15, 20, 19, 18, 17, 16, 11, 6, 7, 8, 9, 14, 13, 12] 14 | 15 | """ 16 | 17 | 18 | # Simulation 19 | # O(mn) time 20 | # O(mn) space 21 | def spiral_copy(matrix): 22 | m, n = len(matrix), len(matrix[0]) 23 | top, bottom = 0, m - 1 24 | left, right = 0, n - 1 25 | path = [] 26 | 27 | while top <= bottom and left <= right: 28 | # Top row, going left to right. 29 | for j in range(left, right + 1): 30 | path.append(matrix[top][j]) 31 | top += 1 32 | 33 | # Right column, going top to bottom. 34 | for i in range(top, bottom + 1): 35 | path.append(matrix[i][right]) 36 | right -= 1 37 | 38 | # Bottom row, going right to left. 39 | if top <= bottom: 40 | for j in range(right, left - 1, -1): 41 | path.append(matrix[bottom][j]) 42 | bottom -= 1 43 | 44 | # Left column, going bottom to top. 45 | if left <= right: 46 | for i in range(bottom, top - 1, -1): 47 | path.append(matrix[i][left]) 48 | left += 1 49 | 50 | return path 51 | 52 | 53 | # Layer-by-layer 54 | # O(mn) time 55 | # O(mn) space 56 | def copy_layer(matrix, top, bottom, left, right, path): 57 | # Copy top row, going left to right. 58 | for j in range(left, right + 1): 59 | path.append(matrix[top][j]) 60 | 61 | if top == bottom: # case where we don't have a right column to copy (like a matrix with only one row) 62 | return 63 | # Copy right column, going top to bottom. 64 | for i in range(top + 1, bottom + 1): 65 | path.append(matrix[i][right]) 66 | 67 | if right == left: # case where we don't have a bottom row to copy (like a matrix with only one column) 68 | return 69 | # Copy bottom row, going right to left. 70 | for j in range(right - 1, left - 1, -1): 71 | path.append(matrix[bottom][j]) 72 | 73 | # Copy left column, going bottom to top. 74 | for i in range(bottom - 1, top, -1): 75 | path.append(matrix[i][left]) 76 | 77 | def spiral_copy(matrix): 78 | m, n = len(matrix), len(matrix[0]) 79 | top, bottom = 0, m - 1 80 | left, right = 0, n - 1 81 | path = [] 82 | 83 | while top <= bottom and left <= right: 84 | copy_layer(matrix, top, bottom, left, right, path) 85 | # Move edges to next layer 86 | top += 1 87 | bottom -= 1 88 | left += 1 89 | right -= 1 90 | 91 | return path 92 | -------------------------------------------------------------------------------- /code/deletion_distance.py: -------------------------------------------------------------------------------- 1 | """ 2 | Deletion Distance 3 | 4 | The deletion distance of two strings is the minimum number of characters you need to delete in the two strings in order to get the same string. 5 | For instance, the deletion distance between "heat" and "hit" is 3: 6 | By deleting 'e' and 'a' in "heat", and 'i' in "hit", we get the string "ht" in both cases. 7 | 8 | Given the strings str1 and str2, write an efficient function deletionDistance that returns the deletion distance between them. 9 | 10 | 11 | input: str1 = "dog", str2 = "frog" 12 | output: 3 13 | 14 | input: str1 = "some", str2 = "some" 15 | output: 0 16 | 17 | input: str1 = "some", str2 = "thing" 18 | output: 9 19 | 20 | input: str1 = "", str2 = "" 21 | output: 0 22 | 23 | """ 24 | 25 | # O(mn) time 26 | # O(mn) space 27 | def deletion_distance(str1, str2): 28 | if str1 == str2: 29 | return 0 30 | 31 | m, n = len(str1), len(str2) 32 | dp = [[0 for j in range(n + 1)] for i in range(m + 1)] 33 | 34 | # Update first row 35 | for i in range(m + 1): 36 | dp[i][0] = i 37 | 38 | # Update first col 39 | for j in range(n + 1): 40 | dp[0][j] = j 41 | 42 | # Update the rest of the matrix 43 | for i in range(1, m + 1): 44 | for j in range(1, n + 1): 45 | if str1[i - 1] != str2[j - 1]: 46 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1 47 | else: 48 | dp[i][j] = dp[i - 1][j - 1] 49 | 50 | return dp[-1][-1] 51 | 52 | 53 | # Same as previous 54 | def deletion_distance(str1, str2): 55 | if str1 == str2: 56 | return 0 57 | 58 | m, n = len(str1), len(str2) 59 | dp = [[0 for j in range(n + 1)] for i in range(m + 1)] 60 | 61 | for i in range(m + 1): 62 | for j in range(n + 1): 63 | if i == 0: 64 | dp[i][j] = j 65 | elif j == 0: 66 | dp[i][j] = i 67 | elif str1[i - 1] != str2[j - 1]: 68 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1 69 | else: 70 | dp[i][j] = dp[i - 1][j - 1] 71 | 72 | return dp[-1][-1] 73 | 74 | 75 | # O(mn) time 76 | # O(min(n,m)) space 77 | """ 78 | Pseudocode: 79 | 80 | function deletionDistance(str1, str2): 81 | # make sure the length of str2 isn't 82 | # longer than the length of str1 83 | if (str1.length < str2.length) 84 | tmpStr = str1 85 | str1 = str2 86 | str2 = tmpStr 87 | 88 | str1Len = str1.length 89 | str2Len = str2.length 90 | prevMemo = new Array(str2Len + 1) 91 | currMemo = new Array(str2Len + 1) 92 | 93 | for i from 0 to str1Len: 94 | for j from 0 to str2Len: 95 | if (i == 0): 96 | currMemo[j] = j 97 | else if (j == 0): 98 | currMemo[j] = i 99 | else if (str1[i-1] == str2[j-1]): 100 | currMemo[j] = prevMemo[j-1] 101 | else: 102 | currMemo[j] = 1 + min(prevMemo[j], currMemo[j-1]) 103 | 104 | prevMemo = currMemo 105 | currMemo = new Array(str2Len + 1); 106 | 107 | return prevMemo[str2Len] 108 | """ 109 | -------------------------------------------------------------------------------- /code/sudoku_solver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sudoku Solver 3 | 4 | Write the function sudokuSolve that checks whether a given sudoku board (i.e. sudoku puzzle) is solvable. If so, the function will returns true. 5 | Otherwise (i.e. there is no valid solution to the given sudoku board), returns false. 6 | 7 | In sudoku, the objective is to fill a 9x9 board with digits so that each column, each row, and each of the nine 3x3 sub-boards 8 | that compose the board contains all of the digits from 1 to 9. The board setter provides a partially completed board, 9 | which for a well-posed board has a unique solution. As explained above, for this problem, it suffices to calculate whether a given sudoku board has a solution. 10 | No need to return the actual numbers that make up a solution. 11 | 12 | A sudoku board is represented as a two-dimensional 9x9 array of the characters ‘1’,‘2’,…,‘9’ and the '.' character, which represents a blank space. 13 | The function should fill the blank spaces with characters such that the following rules apply: 14 | 1) In every row of the array, all characters ‘1’,‘2’,…,‘9’ appear exactly once. 15 | 2) In every column of the array, all characters ‘1’,‘2’,…,‘9’ appear exactly once. 16 | 3) In every 3x3 sub-board that is illustrated below, all characters ‘1’,‘2’,…,‘9’ appear exactly once. 17 | 18 | A solved sudoku is a board with no blank spaces, i.e. all blank spaces are filled with characters that abide to the constraints above. 19 | If the function succeeds in solving the sudoku board, it’ll return true (false, otherwise). 20 | 21 | """ 22 | 23 | # O(9^(n*n)) time 24 | # O(1) space 25 | def sudoku_solve(board): 26 | # Find an empty cell 27 | empty_cell_loc = find_empty_cell(board) 28 | if not empty_cell_loc: 29 | return True # no empty cells means we solved the sudoku 30 | i, j = empty_cell_loc 31 | 32 | # Fill empty cell with a number between 1-9 33 | for guess in range(1, 10): 34 | # Check that our guess is a valid number that won't break game rules 35 | if is_valid(board, guess, i, j): 36 | # Fill empty cell with the number we guessed 37 | board[i][j] = guess 38 | # Fill the next empty cell 39 | if sudoku_solve(board): # return True if there are no more empty cells 40 | return True 41 | board[i][j] = "." # backtrack 42 | 43 | # Return False if we can't fill empty cell with a valid number 44 | return False 45 | 46 | 47 | def find_empty_cell(board): 48 | for i in range(9): 49 | for j in range(9): 50 | if board[i][j] == ".": 51 | return i, j 52 | return None 53 | 54 | 55 | def is_valid(board, guess, i, j): 56 | # Check that the number guessed doesn't already appear in current row i 57 | for num in board[i]: 58 | if num != "." and guess == int(num): 59 | return False 60 | 61 | # Check that it doesn't appear in current column j 62 | for row in range(9): 63 | num = board[row][j] 64 | if num != "." and guess == int(num): 65 | return False 66 | 67 | # Check each 3x3 sub-board 68 | row_start = i // 3 * 3 69 | col_start = j // 3 * 3 70 | for row in range(row_start, row_start + 3): 71 | for col in range(col_start, col_start + 3): 72 | num = board[row][col] 73 | if num != "." and guess == int(num): 74 | return False 75 | 76 | # This is a valid guess 77 | return True 78 | --------------------------------------------------------------------------------