├── .gitignore ├── Arrays ├── container_with_most_water.py ├── count_triplets_with_sum_k.py ├── find_busiest_interval.py ├── find_el_smaller_left_bigger_right.py ├── find_el_where_k_greater_or_equal.py ├── find_element_range_sorted_array.py ├── find_first_missing_positive.py ├── find_missing_number_in_second_array.py ├── find_one_missing_number.py ├── find_peak_element.py ├── find_two_missing_numbers.py ├── find_unpaired.py ├── flatten_deep_list.py ├── jump_game.py ├── k_closest_points.py ├── kth_smallest.py ├── longest_increasing_subarray.py ├── majority_element.py ├── max_profit.py ├── merge_intervals.py ├── min_swaps.py ├── product_of_array_except_self.py ├── random_sample.py ├── reverse_array.py ├── reverse_ascending_sublists.py ├── rotate_array.py ├── search_rotated_sorted_array.py ├── secret_santa.py ├── shuffle_array.py ├── sort_rgb_array.py ├── subarray_with_sum_k.py ├── top_k_frequent_elements.py └── trapped_watter.py ├── Dynamic Programming ├── climbing_staircase.py ├── coin_change.py ├── count_ip_addresses.py ├── create_palindrom.py ├── interleaving_strings.py ├── jump_game_2.py ├── longest_common_subsequence.py ├── longest_common_substring.py ├── longest_increasing_subsequence.py ├── max_profit_k_transactions.py ├── max_subarray_sum.py ├── min_cost_coloring.py ├── number_of_decodings.py ├── number_of_smses.py ├── ordered_digits.py ├── split_coins.py ├── sum_non-adjecent.py ├── transform_number_ascending_digits.py └── word_break.py ├── Hashing DS ├── anagram_indices.py ├── count_positives.py ├── find_duplicates.py ├── find_pairs_with_sum_k.py ├── group_anagrams.py ├── longest_consecutive_sequence.py ├── longest_substring_with_k_distinct_characters.py ├── longest_substring_without_repeating_characters.py └── perfect_rectangle.py ├── LICENSE ├── Linked Lists ├── add_two_numbers.py ├── intersecting_ll.py ├── is_ascending_ll.py ├── ll_helpers.py ├── max_difference_subll.py ├── merge_k_sorted_ll.py ├── merge_sorted_ll.py ├── odd_even_ll.py ├── remove_duplicates_sorted_ll.py ├── remove_element_ll.py ├── remove_nth_ll.py └── reverse_ll.py ├── Math ├── calculate_area_of_polygon.py ├── check_if_point_inside_polygon.py ├── check_if_two_rectangles_overlap.py ├── count_divisibles_in_range.py ├── estimate_pi.py ├── factorial_trailing_zeroes.py ├── odd_sum.py ├── prime_factors.py ├── smallest_multiple.py ├── sum_of_multiples.py ├── total_divisible_numbers.py └── unique_paths.py ├── Other ├── basic_calculator.py ├── count_consecutive_sums.py ├── fancy_sequence.py ├── find_min_path.py ├── generate_parentheses.py ├── jumping_numbers.py ├── letter_combinations.py ├── nth_fibonacci_number.py ├── number_of_islands.py ├── palindrome_integer.py ├── permutations.py ├── postfix_evaluate.py ├── power.py ├── power_set.py ├── queens_problem.py ├── reverse_all_lists.py ├── reverse_integer.py ├── river_sizes.py ├── running_median.py ├── safe_squares_rooks.py ├── search_2d_matrix.py ├── set_matrix_zeroes.py ├── sliding_window_maximum.py ├── spiral_matrix.py └── valid_parentheses.py ├── README.md ├── Strings ├── encoding_string.py ├── longest_common_prefix.py ├── longest_palindromic_substring.py ├── reverse_string.py ├── reverse_vowels.py ├── reverse_words_in_sentence.py ├── strong_password_checker.py ├── swap_first_and_last_word.py └── zigzag_conversion.py └── Trees ├── diameter_of_binary_tree.py ├── find_kth_smallest_node_bst.py ├── find_max_branch_sum.py ├── find_max_path_sum.py ├── find_second_largest_node.py ├── find_second_largest_node_bst.py ├── populating_next_pointers_tree.py ├── same_tree.py ├── tree_helpers.py ├── unival_trees.py ├── valid_bst.py └── zigzag_level_order_traversal.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Arrays/container_with_most_water.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Container With Most Water 3 | 4 | Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). 5 | n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). 6 | Find two lines, which together with x-axis forms a container, such that the container contains the most water. 7 | 8 | Input: [1,8,6,2,5,4,8,3,7] 9 | Output: 49 10 | 11 | ========================================= 12 | Playing with pointers from both sides, eliminate smaller heights and search for a bigger height. 13 | Time Complexity: O(N) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def max_area(height): 23 | l = 0 24 | r = len(height) - 1 25 | max_height = 0 26 | 27 | while l < r: 28 | left = height[l] 29 | right = height[r] 30 | 31 | current_height = min(left, right) * (r - l) 32 | max_height = max(max_height, current_height) 33 | 34 | # take the smaller side and search for a bigger height on that side 35 | if left < right: 36 | while (l < r) and (left >= height[l]): 37 | l += 1 38 | else: 39 | while (l < r) and (right >= height[r]): 40 | r -= 1 41 | 42 | return max_height 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Test 1 50 | # Correct result => 49 51 | print(max_area([1, 8, 6, 2, 5, 4, 8, 3, 7])) -------------------------------------------------------------------------------- /Arrays/count_triplets_with_sum_k.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Count Triplets with Sum K 3 | 4 | Given an array (sorted in ascending order) and a value, count how many triplets 5 | exist in array whose sum is equal to the given value. 6 | 7 | Input: [1, 2, 3, 4, 5], 9 8 | Output: 2 9 | Output explanation: (1, 3, 5) and (2, 3, 4) 10 | 11 | ========================================= 12 | Fix the first element (i), move the second element (j) and search into the hashset. 13 | (similar approach to find_pairs_with_sum_k.py) 14 | Time Complexity: O(N^2) 15 | Space Complexity: O(N) 16 | Fix the first element (i), and play with 2 pointers from the left (i+1) and right (n-1) side. 17 | If the current sum is smaller than K then increase the left pointer, otherwise decrease the right pointer. 18 | * This solution works only for elements in sorted ascending order. If the elements aren't sorted, first sort 19 | them and after that use this algorithm, the time complexity will be same O(NLogN + N^2) = O(N^2). 20 | Time Complexity: O(N^2) 21 | Space Complexity: O(1) 22 | ''' 23 | 24 | 25 | ############## 26 | # Solution 1 # 27 | ############## 28 | 29 | def count_triplets_1(arr, k): 30 | count = 0 31 | n = len(arr) 32 | 33 | for i in range(n - 2): 34 | elements = set() 35 | curr_sum = k - arr[i] 36 | 37 | for j in range(i + 1, n): 38 | if (curr_sum - arr[j]) in elements: 39 | count += 1 40 | elements.add(arr[j]) 41 | 42 | return count 43 | 44 | 45 | ############## 46 | # Solution 2 # 47 | ############## 48 | 49 | def count_triplets_2(arr, k): 50 | count = 0 51 | n = len(arr) 52 | 53 | for i in range(n - 2): 54 | left = i + 1 55 | right = n - 1 56 | 57 | while left < right: 58 | curr_sum = arr[i] + arr[left] + arr[right] 59 | if curr_sum == k: 60 | count += 1 61 | right -= 1 62 | elif curr_sum < k: 63 | left += 1 64 | else: 65 | right -= 1 66 | 67 | return count 68 | 69 | 70 | ########### 71 | # Testing # 72 | ########### 73 | 74 | # Test 1 75 | # Correct result => 1 76 | arr = [10, 11, 16, 18, 19] 77 | k = 40 78 | print(count_triplets_1(arr, k)) 79 | print(count_triplets_2(arr, k)) 80 | 81 | # Test 2 82 | # Correct result => 2 83 | arr = [1, 2, 3, 4, 5] 84 | k = 9 85 | print(count_triplets_1(arr, k)) 86 | print(count_triplets_2(arr, k)) -------------------------------------------------------------------------------- /Arrays/find_busiest_interval.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find the Busiest Interval 3 | 4 | Given a list of arriving time and leaving time for each celebrity. 5 | Celebrity I, arrives at arriving[I] time and leaves at leaving[I] time. 6 | Output is the time interval that you want to go the party when the maximum number of celebrities are still there. 7 | 8 | Input: arriving=[30, 0, 60], leaving=[75, 50, 150] 9 | Output: (30, 50) or (60, 75) 10 | 11 | ========================================= 12 | Just sort the lists, don't care about pairs ordering. 13 | And use a counter, when arriving counter++, when leaving counter--. 14 | Time Complexity: O(N LogN) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def bussiest_interval(arriving, leaving): 24 | # sort both arrays (don't care about pairs) 25 | arriving.sort() 26 | leaving.sort() 27 | 28 | n = len(arriving) 29 | i, j = 0, 0 30 | start, end = 0, 0 31 | overlapping = 0 32 | max_overlapping = 0 33 | 34 | # both arrays have same number of elements 35 | # but the biggest time is from the leaving array 36 | # becayse of that you're sure that 'i' will reach the end before 'j' 37 | while i < n: 38 | if arriving[i] <= leaving[j]: 39 | overlapping += 1 40 | if max_overlapping <= overlapping: 41 | max_overlapping = overlapping 42 | # save the start time if max_overlapping 43 | start = arriving[i] 44 | i += 1 45 | else: 46 | if max_overlapping == overlapping: 47 | # save the exit time if max_overlapping 48 | end = leaving[j] 49 | overlapping -= 1 50 | j += 1 51 | 52 | # check again this to close the result interval because 'i' is completed and not 'j' 53 | if max_overlapping == overlapping: 54 | end = leaving[j] 55 | 56 | # return start&end or max_overlapping 57 | return (start, end) 58 | 59 | 60 | ########### 61 | # Testing # 62 | ########### 63 | 64 | # Test 1 65 | # Correct result => (30, 50) or (60, 75) 66 | print(bussiest_interval([30, 0, 60], [75, 50, 150])) 67 | 68 | # Test 2 69 | # Correct result => (5, 5) 70 | print(bussiest_interval([1, 2, 10, 5, 5], [4, 5, 12, 9, 12])) -------------------------------------------------------------------------------- /Arrays/find_el_smaller_left_bigger_right.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find the element before which all the elements are smaller than it, and after which all are greater 3 | 4 | Given an unsorted array of size N. Find the first element in array such that all of its left elements are smaller and all right elements to it are greater than it. 5 | Note: Left and right side elements can be equal to required element. And extreme elements cannot be required element. 6 | 7 | Input: [5, 1, 4, 3, 6, 8, 10, 7, 9] 8 | Output: 6 9 | 10 | ========================================= 11 | Traverse the array starting and check if the current element is smaller than the wanted one. If it's smaller then reset the result. 12 | In meantime keep track of the maximum value till the current position. This maximum value will be used for finding a new "middle" element. 13 | If the maximum value 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def find_element_smaller_left_bigger_right(arr): 24 | n = len(arr) 25 | curr_max = arr[0] 26 | result = -1 27 | 28 | for i in range(1, n): 29 | curr_el = arr[i] 30 | 31 | if result == -1 and curr_el >= curr_max and i != n - 1: 32 | result = curr_el 33 | elif curr_el < result: 34 | result = -1 35 | 36 | if curr_el > curr_max: 37 | curr_max = curr_el 38 | 39 | return result 40 | 41 | ########### 42 | # Testing # 43 | ########### 44 | 45 | # Test 1 46 | # Correct result => 6 47 | print(find_element_smaller_left_bigger_right([5, 1, 4, 3, 6, 8, 10, 7, 9])) 48 | 49 | # Test 2 50 | # Correct result => -1 51 | print(find_element_smaller_left_bigger_right([5, 1, 4, 4])) 52 | 53 | # Test 3 54 | # Correct result => 7 55 | print(find_element_smaller_left_bigger_right([5, 1, 4, 6, 4, 7, 14, 8, 19])) 56 | 57 | # Test 4 58 | # Correct result => 5 59 | print(find_element_smaller_left_bigger_right([4, 2, 5, 7])) 60 | 61 | # Test 5 62 | # Correct result => -1 63 | print(find_element_smaller_left_bigger_right([11, 9, 12])) 64 | 65 | # Test 6 66 | # Correct result => 234 67 | print(find_element_smaller_left_bigger_right([177, 234, 236, 276, 519, 606, 697, 842, 911, 965, 1086, 1135, 1197, 1273, 1392, 1395, 1531, 1542, 1571, 1682, 2007, 2177, 2382, 2410, 2432, 2447, 2598, 2646, 2672, 2826, 2890, 2899, 2916, 2955, 3278, 3380, 3623, 3647, 3690, 4186, 4300, 4395, 4468, 4609, 4679, 4712, 4725, 4790, 4851, 4912, 4933, 4942, 5156, 5186, 5188, 5244, 5346, 5538, 5583, 5742, 5805, 5830, 6010, 6140, 6173, 6357, 6412, 6414, 6468, 6582, 6765, 7056, 7061, 7089, 7250, 7275, 7378, 7381, 7396, 7410, 7419, 7511, 7625, 7639, 7655, 7776, 7793, 8089, 8245, 8622, 8758, 8807, 8969, 9022, 9149, 9150, 9240, 9273, 9573, 9938])) 68 | -------------------------------------------------------------------------------- /Arrays/find_el_where_k_greater_or_equal.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find an Element Which is Smaller or Equal to Exactly K Numbers 3 | 4 | You have to find some number X greater than 0 where exactly K elements in that list are greater than or equal to the number X. 5 | If there are multiple such values return the smallest possible. 6 | If there is no such X, return (-1). 7 | 8 | Input: [3,8,5,1,10,3,20,24], 2 9 | Output: 11 10 | Output explanation: Only 20 and 24 are equal or smaller from 11 (11 is the smallest solution, also 12, 13...20 are solutions). 11 | 12 | ========================================= 13 | Sort the array and check the Kth element from the end. 14 | Time Complexity: O(NLogN) 15 | Space Complexity: O(1) 16 | QuickSelect can be used (find the K+1th number + 1). https://en.wikipedia.org/wiki/Quickselect 17 | See kth_smallest.py, very similar solution. 18 | Time Complexity: O(N) 19 | Space Complexity: O(1) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def get_minimum_X(arr, k): 28 | n = len(arr) 29 | 30 | if n == 0 or k > n: 31 | return -1 32 | 33 | if k == n: 34 | return 1 35 | 36 | arr.sort() 37 | 38 | if k == 0: 39 | return arr[-1] + 1 40 | 41 | if arr[-k] == arr[-(k + 1)]: 42 | return -1 43 | 44 | return arr[-(k + 1)] + 1 45 | 46 | 47 | ########### 48 | # Testing # 49 | ########### 50 | 51 | # Test 1 52 | # Correct result => 11 53 | print(get_minimum_X([3, 8, 5, 1, 10, 3, 20, 24], 2)) 54 | -------------------------------------------------------------------------------- /Arrays/find_element_range_sorted_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find First and Last Position of Element in Sorted Array 3 | 4 | Given an array of integers nums sorted in ascending order, 5 | find the starting and ending position of a given target value. 6 | If the target is not found in the array, return [-1, -1]. 7 | 8 | Input: [5, 7, 7, 8, 8, 10], 8 9 | Output: [3, 4] 10 | 11 | Input: [5, 7, 7, 8, 8, 10], 6 12 | Output: [-1, -1] 13 | 14 | ========================================= 15 | Use binary search twice to find the range. 16 | Time Complexity: O(LogN) 17 | Space Complexity: O(1) 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | def search_range(nums, target): 26 | left_idx = binary_search(nums, target, True) 27 | if (left_idx == len(nums)) or (nums[left_idx] != target): 28 | return [-1, -1] 29 | 30 | right_idx = binary_search(nums, target, False) - 1 31 | return [left_idx, right_idx] 32 | 33 | def binary_search(nums, target, equal=True): 34 | left = 0 35 | right = len(nums) 36 | 37 | while left < right: 38 | mid = (left + right) // 2 39 | 40 | if (nums[mid] > target) or (equal and nums[mid] == target): 41 | right = mid 42 | else: 43 | left = mid + 1 44 | 45 | return left 46 | 47 | 48 | ########### 49 | # Testing # 50 | ########### 51 | 52 | # Test 1 53 | # Correct result => [3, 4] 54 | print(search_range([5, 7, 7, 8, 8, 10], 8)) 55 | 56 | # Test 2 57 | # Correct result => [-1, -1] 58 | print(search_range([5, 7, 7, 8, 8, 10], 6)) -------------------------------------------------------------------------------- /Arrays/find_missing_number_in_second_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find missing number in second array 3 | 4 | Given 2 arrays, first array with N elements and second array with N-1 elements. 5 | All elements from the first array exist in the second array, except one. Find the missing number. 6 | 7 | Sample input: [1, 2, 3, 4, 5], [1, 2, 3, 4] 8 | Sample output: 5 9 | 10 | Sample input: [2131, 2122221, 64565, 33333333, 994188129, 865342234], 11 | [994188129, 2122221, 865342234, 2131, 64565] 12 | Sample output: 33333333 13 | 14 | ========================================= 15 | The simplest solution is to substract the sum of the second array from the sum of the first array: 16 | missing_number = sum(arr1) - sum(arr2) 17 | But what if we have milions of elements and all elements are with 8-9 digits values? 18 | In this case we'll need to use modulo operation. Make two sums, the first one from MODULO of each element 19 | and the second one from the DIVIDE of each element. In the end use these 2 sums to compute the missing number. 20 | Time Complexity: O(N) 21 | Space Complexity: O(1) 22 | The second solution is XOR soulution, make XOR to each element from the both arrays (same as find_unpaired.py). 23 | Time Complexity: O(N) 24 | Space Complexity: O(1) 25 | ''' 26 | 27 | 28 | ############## 29 | # Solution 1 # 30 | ############## 31 | 32 | def find_missing_number(arr1, arr2): 33 | n = len(arr2) 34 | mod = 10000 # this can be every number, this should be true max_length * mod < max_integer 35 | sum_diff = 0 36 | mod_diff = 0 37 | i = 0 38 | 39 | while i < n: 40 | # this is in case if we have too big numbers and to big arrays 41 | sum_diff += arr1[i] % mod - arr2[i] % mod 42 | mod_diff += arr1[i] // mod - arr2[i] // mod 43 | i += 1 44 | 45 | # don't forget the last element from the first array! 46 | sum_diff += arr1[n] % mod 47 | mod_diff += arr1[n] // mod 48 | 49 | return mod * mod_diff + sum_diff 50 | 51 | 52 | ############## 53 | # Solution 2 # 54 | ############## 55 | 56 | def find_missing_number_2(arr1, arr2): 57 | n = len(arr2) 58 | missing = 0 59 | i = 0 60 | 61 | while i < n: 62 | missing ^= arr1[i] ^ arr2[i] 63 | i += 1 64 | 65 | # don't forget the last element from the first array! 66 | missing ^= arr1[n] 67 | 68 | return missing 69 | 70 | 71 | ########### 72 | # Testing # 73 | ########### 74 | 75 | # Test 1 76 | # Correct result => 33333333 77 | arr1 = [2131, 2122221, 64565, 33333333, 994188129, 865342234] 78 | arr2 = [994188129, 2122221, 865342234, 2131, 64565] 79 | print(find_missing_number(arr1, arr2)) 80 | print(find_missing_number_2(arr1, arr2)) 81 | 82 | # Test 2 83 | # Correct result => 5 84 | arr1 = [1, 2, 3, 4, 5] 85 | arr2 = [1, 2, 3, 4] 86 | print(find_missing_number(arr1, arr2)) 87 | print(find_missing_number_2(arr1, arr2)) -------------------------------------------------------------------------------- /Arrays/find_one_missing_number.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find the missing number in a sequence 3 | 4 | Find the only missing integer in a sequence, 5 | all numbers are integers and they're smaller or equal to N+1 (N is length of the array). 6 | 7 | Input: [2, 1, 4] 8 | Output: 3 9 | 10 | ========================================= 11 | Searching for 1 unknown, math problem. 12 | Use the sum formula for the first N numbers to compute the whole sum of the sequence. 13 | After that sum all elements from the array, and when you subtract those 2 numbers, you'll get the missing number. 14 | Sum formula = N*(N+1)/2 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def missing_number(nums): 24 | s = sum(nums) 25 | n = len(nums) + 1 26 | # sum formula (sum of the first n numbers) = (N*(N+1))/2 27 | return n * (n + 1) // 2 - s 28 | 29 | 30 | ########### 31 | # Testing # 32 | ########### 33 | 34 | # Test 1 35 | # Correct result => 4 36 | print(missing_number([2, 3, 1])) 37 | 38 | # Test 2 39 | # Correct result => 3 40 | print(missing_number([2, 1, 4])) -------------------------------------------------------------------------------- /Arrays/find_peak_element.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find Peak Element 3 | 4 | A peak element is an element that is greater than its neighbors. 5 | Given an input array nums, where nums[i] ≠ nums[i+1], find a peak element and return its index. 6 | The array may contain multiple peaks, in that case return the index to any one of the peaks is fine. 7 | You may imagine that nums[-1] = nums[n] = -∞. 8 | 9 | Input: [1, 2, 3, 1] 10 | Output: 2 11 | Output explanation: 3 is a peak element and your function should return the index number 2. 12 | 13 | Input: [1, 2, 1, 3, 5, 6, 4] 14 | Output: 1 or 5 15 | Output explanation: Your function can return either index number 1 where the peak element is 2, or index number 5 where the peak element is 6. 16 | 17 | ========================================= 18 | Binary search (more description in the code). 19 | Time Complexity: O(LogN) 20 | Space Complexity: O(1) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def find_peak_element(nums): 29 | l = 0 30 | r = len(nums) - 1 31 | 32 | while l < r: 33 | mid = (l + r) // 2 34 | if nums[mid] > nums[mid + 1]: 35 | # go left if the current value is smaller than the next one 36 | # in this moment you're sure that there is a peak element left from this one 37 | r = mid 38 | else: 39 | # go right if the current value is smaller than the next one 40 | # if the l comes to the end and all elements were in ascending order, then the last one is peak (because nums[n] is negative infinity) 41 | l = mid + 1 42 | 43 | return l 44 | 45 | 46 | ########### 47 | # Testing # 48 | ########### 49 | 50 | # Test 1 51 | # Correct result => 2 52 | print(find_peak_element([1, 2, 3, 1])) 53 | 54 | # Test 2 55 | # Correct result => 1 or 5 56 | print(find_peak_element([1, 2, 1, 3, 5, 6, 4])) -------------------------------------------------------------------------------- /Arrays/find_two_missing_numbers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find two missing numbers in a sequence 3 | 4 | Find two missing numbers in a sequence, 5 | all numbers are integers and they're smaller or equal to N+2 (N is length of the array). 6 | 7 | Input: [2, 1, 4] 8 | Output: [3, 5] 9 | 10 | ========================================= 11 | Searching for 2 unknowns, math problem. 12 | This solution is extension of 'find_one_missing_number.py'. 13 | But in this case we also need the sum formula for the first squared N numbers (1^2 + 2^2 + ... + N^2). 14 | And using those 2 formulas, we'll solve equations with 2 unknowns. 15 | a + b = diff_sum (diff_sum = formula_sum + list_sum) 16 | a^2 + b^2 = diff_squared_sum (diff_squared_sum = formula_squared_sum + list_squared_sum) 17 | But in the end we'll need quadratic formula to find those values. 18 | b1,2 = (diff_sum +- sqrt(2*diff_squared_sum - diff_sum^2)) / 2 19 | Sum formula = N*(N+1)/2 20 | Squared sum formula = N*(N+1)*(2*N+1)/6 21 | Time Complexity: O(N) 22 | Space Complexity: O(1) 23 | 24 | Note: this idea also could be used when more than 2 numbers are missing, 25 | but you'll need more computations/equations, because you'll have K unknowns. 26 | ''' 27 | 28 | ############ 29 | # Solution # 30 | ############ 31 | 32 | import math 33 | 34 | def missing_numbers(nums): 35 | # find sums from the array 36 | s = 0 37 | s_2 = 0 38 | for i in nums: 39 | s += i 40 | s_2 += i * i 41 | 42 | n = len(nums) + 2 43 | 44 | # using formulas, compute the sums of the sequence 45 | f_s = n * (n + 1) // 2 46 | f_s_2 = n * (n + 1) * (2 * n + 1) // 6 47 | 48 | # difference of sums 49 | d = f_s - s 50 | d_2 = f_s_2 - s_2 51 | 52 | # using quadratic formula find the values 53 | r = int(math.sqrt(2 * d_2 - d * d)) 54 | 55 | a = (d - r) // 2 56 | b = (d + r) // 2 57 | 58 | return [a, b] 59 | 60 | 61 | ########### 62 | # Testing # 63 | ########### 64 | 65 | # Test 1 66 | # Correct result => [4, 5] 67 | print(missing_numbers([2, 3, 1])) 68 | 69 | # Test 2 70 | # Correct result => [1, 2] 71 | print(missing_numbers([5, 3, 4])) 72 | 73 | # Test 3 74 | # Correct result => [1, 5] 75 | print(missing_numbers([2, 3, 4])) -------------------------------------------------------------------------------- /Arrays/find_unpaired.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find unpaired element 3 | 4 | Given an array with odd number of elements, where (N - 1)/2 elements have duplicates and ONLY 1 is unique. 5 | 6 | Input: [1, 5, 3, 1, 5] 7 | Output: 3 8 | 9 | ========================================= 10 | Using XOR find the unique element. 11 | * Example: 13 XOR 13 = 1101 XOR 1101 = 0. 12 | Time Complexity: O(N) 13 | Space Complexity: O(1) 14 | ''' 15 | 16 | 17 | ############ 18 | # Solution # 19 | ############ 20 | 21 | def find_unpaired_element(arr): 22 | unique = 0 23 | 24 | for el in arr: 25 | unique ^= el 26 | 27 | return unique 28 | 29 | 30 | ########### 31 | # Testing # 32 | ########### 33 | 34 | # Test 1 35 | # Correct result => 3 36 | print(find_unpaired_element([1, 5, 3, 1, 5])) -------------------------------------------------------------------------------- /Arrays/flatten_deep_list.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Flatten an Arbitrarily Deep List 3 | 4 | Given a list that can contain arbitrary lists as its elements, which in turn can contain arbitrary lists 5 | as elements, and so on, create and return a new list that contains all the atomic (that is, anything 6 | that is not a list) elements listed in a linear sequence without any nesting. 7 | 8 | Input: [1, [2, 3, [4, 'bob', 6], [7]], [8, 9]] 9 | Output: [1, 2, 3, 4, 'bob', 6, 7, 8, 9] 10 | 11 | ========================================= 12 | Recursive solution, extend the result with each sublist. 13 | Time Complexity: O(N) 14 | Space Complexity: O(N) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def flatten_deep_list(arr): 23 | if not isinstance(arr, list): 24 | return [arr] 25 | 26 | result = [] 27 | for a in arr: 28 | result.extend(flatten_deep_list(a)) 29 | 30 | return result 31 | 32 | 33 | ########### 34 | # Testing # 35 | ########### 36 | 37 | # Test 1 38 | # Correct result => [1, 2, 3, 4, 'bob', 6, 7, 8, 9] 39 | print(flatten_deep_list([1, [2, 3, [4, 'bob', 6], [7]], [8, 9]])) 40 | 41 | # Test 2 42 | # Correct result => [89, 85, 72, 84, 65, 88, 31, 64, 11, 60, 42, 57, 55, 16, 79, 34, 82, 94, 36, 89, 26, 39, 94, 47, 72, 30, 72, 3, 73, 18, 37, 51, 75, 83, 94, 57, 37, 10, 62, 62, 13] 43 | print(flatten_deep_list([[], [[[[89, 85, 72, 84, 65], [[88, 31, 64, 11, 60, 42, 57, 55], 16, [79, 34, 82], [], 94, 36, [89, 26, 39, 94, 47, 72, 30], [72, 3, 73]], 18]], [[37, [51, 75, 83], 94, 57]], [37, 10, 62, 62], [[], 13]]])) 44 | 45 | # Test 3 46 | # Correct result => [ ] 47 | print(flatten_deep_list([ [ [ [ [ [ ] ] ] ] ] ])) -------------------------------------------------------------------------------- /Arrays/jump_game.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Jump Game 3 | 4 | Given an array of non-negative integers, you are initially positioned at the first index of the array. 5 | Each element in the array represents your maximum jump length at that position. 6 | Determine if you are able to reach the last index. 7 | 8 | Input: [2, 3, 1, 1, 4] 9 | Output: True 10 | 11 | Input: [3, 2, 1, 0, 4] 12 | Output: False 13 | 14 | ========================================= 15 | Just iterate the array and in each step save the farthest reachable position. 16 | If the current position is smaller than the farthest position, then the end isn't reachable. 17 | Time Complexity: O(N) 18 | Space Complexity: O(1) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def can_jump(nums): 27 | n = len(nums) 28 | if n == 0: 29 | return False 30 | 31 | max_jump = 0 32 | for i in range(n): 33 | # if this field isn't reachable return False 34 | if max_jump < i: 35 | return False 36 | 37 | this_jump = i + nums[i] 38 | max_jump = max(max_jump, this_jump) 39 | 40 | # if the jump is greater or equal to the last element return True 41 | if max_jump >= n - 1: 42 | return True 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Test 1 50 | # Correct result => True 51 | print(can_jump([2, 3, 1, 1, 4])) 52 | 53 | # Test 2 54 | # Correct result => False 55 | print(can_jump([3, 2, 1, 0, 4])) -------------------------------------------------------------------------------- /Arrays/longest_increasing_subarray.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Increasing Subarray 3 | 4 | Find the longest increasing subarray (subarray is when all elements are neighboring in the original array). 5 | 6 | Input: [10, 1, 3, 8, 2, 0, 5, 7, 12, 3] 7 | Output: 4 8 | 9 | ========================================= 10 | Only in one iteration, check if the current element is bigger than the previous and increase the counter if true. 11 | Time Complexity: O(N) 12 | Space Complexity: O(1) 13 | ''' 14 | 15 | 16 | ############ 17 | # Solution # 18 | ############ 19 | 20 | def longest_increasing_subarray(arr): 21 | n = len(arr) 22 | longest = 0 23 | current = 1 24 | i = 1 25 | 26 | while i < n: 27 | if arr[i] < arr[i - 1]: 28 | longest = max(longest, current) 29 | current = 1 30 | else: 31 | current += 1 32 | 33 | i += 1 34 | 35 | # check again for max, maybe the last element is a part of the longest subarray 36 | return max(longest, current) 37 | 38 | 39 | ########### 40 | # Testing # 41 | ########### 42 | 43 | # Test 1 44 | # Correct result => 4 45 | print(longest_increasing_subarray([10, 1, 3, 8, 2, 0, 5, 7, 12, 3])) -------------------------------------------------------------------------------- /Arrays/majority_element.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Majority Element 3 | 4 | Given an array of size n, find the majority element. 5 | The majority element is the element that appears more than ⌊ n/2 ⌋ times. 6 | You may assume that the array is non-empty and the majority element always exist in the array. 7 | 8 | Input: [3, 2, 3] 9 | Output: 3 10 | 11 | Input: [2, 2, 1, 1, 1, 2, 2] 12 | Output: 2 13 | 14 | ========================================= 15 | Sort the array and the result will the middle element. 16 | Time Complexity: O(N LogN) 17 | Space Complexity: O(1) 18 | Use dictionary (hash map) and count the occurrences. 19 | The result will be the one with more than ⌊ n/2 ⌋ occurrences. 20 | Time Complexity: O(N) 21 | Space Complexity: O(N) 22 | Using Boyer-Moore voting algorithm. Choose a potential majority element and for each occurence add +1, but 23 | if the current element isn't same substract -1. 24 | When the counter is 0, the next element becomes the new potential majority element. 25 | Time Complexity: O(N) 26 | Space Complexity: O(1) 27 | ''' 28 | 29 | 30 | ############## 31 | # Solution 1 # 32 | ############## 33 | 34 | def majority_element_1(nums): 35 | nums.sort() 36 | return nums[len(nums) // 2] 37 | 38 | 39 | ############## 40 | # Solution 2 # 41 | ############## 42 | 43 | def majority_element_2(nums): 44 | counter = {} 45 | 46 | for num in nums: 47 | if num in counter: 48 | counter[num] += 1 49 | else: 50 | counter[num] = 1 51 | 52 | half = len(nums) // 2 53 | for num in counter: 54 | if counter[num] > half: 55 | return num 56 | 57 | 58 | ############## 59 | # Solution 3 # 60 | ############## 61 | 62 | def majority_element_3(nums): 63 | majority = 0 64 | count = 0 65 | 66 | for num in nums: 67 | if count == 0: 68 | majority = num 69 | 70 | if num == majority: 71 | count += 1 72 | else: 73 | count -= 1 74 | 75 | return majority 76 | 77 | 78 | ########### 79 | # Testing # 80 | ########### 81 | 82 | # Test 1 83 | # Correct result => 3 84 | arr = [3, 2, 3] 85 | print(majority_element_1(arr)) 86 | print(majority_element_2(arr)) 87 | print(majority_element_3(arr)) 88 | 89 | # Test 2 90 | # Correct result => 2 91 | arr = [2, 2, 1, 1, 1, 2, 2] 92 | print(majority_element_1(arr)) 93 | print(majority_element_2(arr)) 94 | print(majority_element_3(arr)) -------------------------------------------------------------------------------- /Arrays/max_profit.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Max Profit (Best Time to Buy and Sell Stock) 3 | 4 | Say you have an array for which the ith element is the price of a given stock on day i. 5 | Design an algorithm to find the maximum profit. You may complete as many transactions as you like 6 | (i.e., buy one and sell one share of the stock multiple times). 7 | Note: You may not engage in multiple transactions at the same time 8 | (i.e., you must sell the stock before you buy again). 9 | 10 | Input: [7, 1, 5, 3, 6, 4] 11 | Output: 7 12 | Output explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. 13 | Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3. 14 | 15 | Input: [1, 2, 3, 4, 5] 16 | Output: 4 17 | Output explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. 18 | Or buy day 1 -> sell day 2, buy day 2 -> sell day 3, buy day 3 -> sell day 4, buy day 4 -> sell day 5. 19 | Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are 20 | engaging multiple transactions at the same time. You must sell before buying again. 21 | 22 | ========================================= 23 | Sum only the positive differences between neighbouring elements. 24 | Time Complexity: O(N) 25 | Space Complexity: O(1) 26 | ''' 27 | 28 | 29 | ############ 30 | # Solution # 31 | ############ 32 | 33 | def max_profit(prices): 34 | total = 0 35 | 36 | for i in range(1, len(prices)): 37 | total += max(0, prices[i] - prices[i - 1]) 38 | 39 | return total 40 | 41 | 42 | ########### 43 | # Testing # 44 | ########### 45 | 46 | # Test 1 47 | # Correct result => 7 48 | print(max_profit([7, 1, 5, 3, 6, 4])) 49 | 50 | # Test 2 51 | # Correct result => 5 52 | print(max_profit([1, 2, 3, 4, 5])) 53 | 54 | # Test 3 55 | # Correct result => 0 56 | print(max_profit([7, 6, 4, 3, 1])) -------------------------------------------------------------------------------- /Arrays/merge_intervals.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Merge Intervals 3 | 4 | You are given an array of intervals. 5 | Each interval is defined as: (start, end). e.g. (2, 5) 6 | It represents all the integer numbers in the interval, including start and end. (in the example 2, 3, 4 and 5). 7 | Given the array of intervals find the smallest set of unique intervals that contain the same integer numbers, without overlapping. 8 | 9 | 10 | Input: [(1, 5), (2, 6)] 11 | Output: [(1, 6)] 12 | 13 | Input: [(2, 4), (5, 5), (6, 8)] 14 | Output: [(2, 8)] 15 | 16 | Input: [(1, 4), (6, 9), (8, 10)] 17 | Output: [(1, 4), (6, 10)] 18 | 19 | ========================================= 20 | Sort the intervals (using the start), accessing order. After that just iterate the intervals 21 | and check if the current interval belongs to the last created interval. 22 | Time Complexity: O(N LogN) 23 | Space Complexity: O(N) , for the result 24 | ''' 25 | 26 | 27 | ############ 28 | # Solution # 29 | ############ 30 | 31 | def merge_intervals(intervals): 32 | n = len(intervals) 33 | if n == 0: 34 | return [] 35 | 36 | # sort the intervals 37 | intervals.sort(key=lambda interval: interval[0]) 38 | mergedIntervals = [] 39 | mergedIntervals.append(intervals[0]) 40 | 41 | for i in range(1, n): 42 | # check if this interval belongs to the last created interval 43 | if intervals[i][0] <= mergedIntervals[-1][1] + 1: 44 | # only the end can be changed (just copy start it's min, because the array is sorted) 45 | mergedIntervals[-1] = (mergedIntervals[-1][0], max(mergedIntervals[-1][1], intervals[i][1])) 46 | else: 47 | # create a new interval 48 | mergedIntervals.append(intervals[i]) 49 | 50 | return mergedIntervals 51 | 52 | 53 | ########### 54 | # Testing # 55 | ########### 56 | 57 | # Test 1 58 | # Correct result => [(1, 6)] 59 | print(merge_intervals([(1, 5), (2, 6)])) 60 | 61 | # Test 2 62 | # Correct result => [(2, 8)] 63 | print(merge_intervals([(2, 4), (5, 5), (6, 8)])) 64 | 65 | # Test 3 66 | # Correct result => [(1, 4), (6, 10)] 67 | print(merge_intervals([(1, 4), (6, 9), (8, 10)])) -------------------------------------------------------------------------------- /Arrays/min_swaps.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Min Swaps 3 | 4 | You have a list of numbers and you want to sort the list. 5 | The only operation you have is a swap of any two arbitrary numbers. 6 | Find the minimum number of swaps you need to do in order to make the list sorted (ascending order). 7 | - The array will contain N elements 8 | - Each element will be between 1 and N inclusive 9 | - All the numbers will be different 10 | 11 | Input: [4, 1, 3, 2] 12 | Output: 2 13 | Output explanation: swap(4, 1) = [1, 4, 3, 2], swap(4, 2) = [1, 2, 3, 4] 14 | 15 | ========================================= 16 | According to the description, all elements will have their position in the array, 17 | for example, K should be located at K-1 in the array. 18 | Itterate the array and check if each position has the right element, 19 | if not, put that element in the right position and check again. 20 | Time Complexity: O(N) , the solution looks like O(N^2) but that's not possible, at most O(2*N) operations can be done 21 | Space Complexity: O(1) 22 | ''' 23 | 24 | 25 | ############ 26 | # Solution # 27 | ############ 28 | 29 | def min_swaps(a): 30 | n = len(a) 31 | swaps = 0 32 | 33 | for i in range(n): 34 | # swap the elements till the right element isn't found 35 | while a[i] - 1 != i: 36 | swap = a[i] - 1 37 | # swap the elements 38 | a[swap], a[i] = a[i], a[swap] 39 | 40 | swaps += 1 41 | 42 | return swaps 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Test 1 50 | # Correct result => 2 51 | print(min_swaps([4, 1, 3, 2])) 52 | 53 | # Test 2 54 | # Correct result => 3 55 | print(min_swaps([4, 1, 2, 3])) -------------------------------------------------------------------------------- /Arrays/product_of_array_except_self.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Product of Array Except Self 3 | 4 | Given an array nums of n integers where n > 1, 5 | return an array output such that output[i] is equal to the product of all the elements of nums except nums[i]. 6 | Note: Please solve it without division and in O(n). 7 | Follow up: 8 | Could you solve it with constant space complexity? 9 | (The output array does not count as extra space for the purpose of space complexity analysis.) 10 | 11 | Input: [1, 2, 3, 4] 12 | Output: [24, 12, 8, 6] 13 | 14 | ========================================= 15 | 2 iterations, one from front and the second from back. 16 | Make the products as this: from 0 to i-1 and from i-1 to N-1, and in the end only multiply these 2 products. 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) , According to the desciption O(1), the result array is not couted as extra space. 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def product_except_self(nums): 27 | n = len(nums) 28 | if n == 0: 29 | return [] 30 | 31 | mult = 1 32 | res = [1] 33 | i = 0 34 | 35 | # all products from right to left 36 | while i < n - 1: 37 | mult *= nums[i] 38 | res.append(mult) 39 | i += 1 40 | 41 | mult = 1 42 | i = n - 2 43 | 44 | # all products from left to right 45 | while i >= 0: 46 | mult *= nums[i + 1] 47 | res[i] *= mult 48 | i -= 1 49 | 50 | return res 51 | 52 | 53 | ########### 54 | # Testing # 55 | ########### 56 | 57 | # Test 1 58 | # Correct result => [24, 12, 8, 6] 59 | print(product_except_self([1, 2, 3, 4])) -------------------------------------------------------------------------------- /Arrays/reverse_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse array 3 | 4 | Reverse an array, in constant space and linear time complexity. 5 | 6 | Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 7 | Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 8 | 9 | ========================================= 10 | Reverse the whole array by swapping pair letters in-place (first with last, second with second from the end, etc). 11 | Exist 2 more "Pythonic" ways of reversing arrays/strings (but not in-place, they're creating a new list): 12 | - reversed_arr = reversed(arr) 13 | - reversed_arr = arr[::-1] 14 | But I wanted to show how to implement a reverse algorithm step by step so someone will know how to implement it in other languages. 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | def reverse_arr(arr): 25 | start = 0 26 | end = len(arr) - 1 27 | 28 | while start < end: 29 | # reverse the array from the start index to the end index by 30 | # swaping each element with the pair from the other part of the array 31 | swap(arr, start, end) 32 | start += 1 33 | end -= 1 34 | 35 | return arr 36 | 37 | def swap(arr, i, j): 38 | # swapping two elements from a same array 39 | arr[i], arr[j] = arr[j], arr[i] 40 | '''same as 41 | temp = arr[i] 42 | arr[i] = arr[j] 43 | arr[j] = temp 44 | ''' 45 | 46 | 47 | ########### 48 | # Testing # 49 | ########### 50 | 51 | # Test 1 52 | # Correct result => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 53 | print(reverse_arr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) 54 | 55 | # Test 2 56 | # Correct result => [5, 4, 3, 2, 1] 57 | print(reverse_arr([1, 2, 3, 4, 5])) -------------------------------------------------------------------------------- /Arrays/reverse_ascending_sublists.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse Every Ascending Sublist 3 | 4 | Create and return a new list that contains the same elements as the argument list items, but 5 | reversing the order of the elements inside every maximal strictly ascending sublist 6 | 7 | Input: [5, 7, 10, 4, 2, 7, 8, 1, 3] 8 | Output: [10, 7, 5, 4, 8, 7, 2, 3, 1] 9 | Output explanation: 5, 7, 10 => 10, 7, 5 ; 4 => 4; 2, 7, 8 => 8, 7, 2; 1, 3 => 3, 1 10 | 11 | ========================================= 12 | Find the start and end of each sublist and reverse it in-place. 13 | Time Complexity: O(N) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def reverse_ascending_sublists(arr): 23 | n = len(arr) 24 | if n == 0: 25 | return [] 26 | 27 | start = 0 28 | 29 | for i in range(1, n): 30 | # check if this the end of the strictly ascending sublist 31 | if arr[i] < arr[i - 1]: 32 | reverse_arr(arr, start, i - 1) 33 | # a new sublist starts 34 | start = i 35 | 36 | reverse_arr(arr, start, n - 1) 37 | 38 | return arr 39 | 40 | def reverse_arr(arr, start, end): 41 | while start < end: 42 | # reverse the array from the start index to the end index by 43 | # swaping each element with the pair from the other part of the array 44 | arr[start], arr[end] = arr[end], arr[start] 45 | start += 1 46 | end -= 1 47 | 48 | return arr 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | # Correct result => [5, 4, 3, 2, 1] 57 | print(reverse_ascending_sublists([1, 2, 3, 4, 5])) 58 | 59 | # Test 2 60 | # Correct result => [5, 4, 3, 2, 1] 61 | print(reverse_ascending_sublists([5, 4, 3, 2, 1])) 62 | 63 | # Test 3 64 | # Correct result => [10, 7, 5, 4, 8, 7, 2, 3, 1] 65 | print(reverse_ascending_sublists([5, 7, 10, 4, 2, 7, 8, 1, 3])) -------------------------------------------------------------------------------- /Arrays/rotate_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Array rotation/shifting 3 | 4 | Rotate array in right (or left) for K places. 5 | 6 | Input: [1, 2, 3, 4, 5, 6], 1 7 | Output: [6, 1, 2, 3, 4, 5] 8 | 9 | Input: [1, 2, 3, 4, 5, 6], 3 10 | Output: [4, 5, 6, 1, 2, 3] 11 | 12 | ========================================= 13 | The first solution is a simple one, split the array in two parts and swap those parts. 14 | Time Complexity: O(N) 15 | Space Complexity: O(N) 16 | For the second one we need to compute GCD, to decide how many different sets are there. 17 | And after that shift all elements in that set for one position in right/left. 18 | (elements in a set are not neighboring elements) 19 | (A Juggling Algorithm, https://www.geeksforgeeks.org/array-rotation/) 20 | Time Complexity: O(N) 21 | Space Complexity: O(1) 22 | ''' 23 | 24 | 25 | ############## 26 | # Solution 1 # 27 | ############## 28 | 29 | def rotate_array_1(arr, k, right = True): 30 | n = len(arr) 31 | right %= n 32 | 33 | # going right for K places is same like going left for N-K places 34 | if right: 35 | k = n - k 36 | 37 | # the shortest way to swap 2 parts of the array 38 | return arr[k:] + arr[:k] 39 | 40 | 41 | ############## 42 | # Solution 2 # 43 | ############## 44 | 45 | def rotate_array_2(arr, k, right = True): 46 | n = len(arr) 47 | right %= n 48 | 49 | # going right for K places is same like going left for N-K places 50 | if not right: 51 | k = n - k 52 | 53 | # different sets 54 | sets = gcd(n, k) 55 | # elements in each set 56 | elements = n // sets 57 | i = 0 58 | 59 | while i < sets: 60 | j = 1 61 | curr = arr[i] 62 | 63 | while j <= elements: 64 | idx = (i + j * k) % n 65 | j += 1 66 | 67 | # add the previous element on this position 68 | curr, arr[idx] = arr[idx], curr 69 | '''same as 70 | temp = curr 71 | curr = arr[idx] 72 | arr[idx] = temp 73 | ''' 74 | 75 | i += 1 76 | 77 | return arr 78 | 79 | # greatest common divisor 80 | def gcd(a, b): 81 | if b == 0: 82 | return a 83 | return gcd(b, a % b) 84 | 85 | 86 | ########### 87 | # Testing # 88 | ########### 89 | 90 | # Test 1 91 | # Correct result => [4, 5, 6, 7, 8, 9, 10, 1, 2, 3] 92 | arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 93 | k = 7 94 | print(rotate_array_1(arr, k)) 95 | print(rotate_array_2(arr, k)) 96 | 97 | # Test 2 98 | # Correct result => [6, 1, 2, 3, 4, 5] 99 | arr = [1, 2, 3, 4, 5, 6] 100 | k = 1 101 | print(rotate_array_1(arr, k)) 102 | print(rotate_array_2(arr, k)) 103 | 104 | # Test 3 105 | # Correct result => [4, 5, 6, 1, 2, 3] 106 | arr = [1, 2, 3, 4, 5, 6] 107 | k = 3 108 | print(rotate_array_1(arr, k)) 109 | print(rotate_array_2(arr, k)) -------------------------------------------------------------------------------- /Arrays/search_rotated_sorted_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Search in Rotated Sorted Array 3 | 4 | Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. 5 | (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). 6 | You are given a target value to search. If found in the array return its index, otherwise return -1. 7 | You may assume no duplicate exists in the array. 8 | 9 | Input: [4, 5, 6, 7, 0, 1, 2], 0 10 | Output: 4 11 | 12 | Input: [4, 5, 6, 7, 0, 1, 2], 3 13 | Output: -1 14 | 15 | ========================================= 16 | Use binary search twice, first time to find the pivot (index where the array is rotated) 17 | and the second time to find the target. 18 | Time Complexity: O(LogN) 19 | Space Complexity: O(1) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def search_rotated_sorted_array(nums, target): 28 | n = len(nums) 29 | pivot = find_pivot(nums, 0, n) + 1 30 | if pivot > n: 31 | return -1 32 | 33 | if nums[0] <= target: 34 | return find_element(nums, 0, pivot - 1, target) 35 | return find_element(nums, pivot, n - 1, target) 36 | 37 | def find_pivot(nums, left, right): 38 | while left < right - 1: 39 | mid = left + (right - left) // 2 40 | 41 | if nums[left] < nums[mid]: 42 | left = mid 43 | else: 44 | right = mid 45 | 46 | return left 47 | 48 | def find_element(nums, left, right, target): 49 | while left <= right: 50 | mid = left + (right - left) // 2 51 | 52 | if nums[mid] == target: 53 | return mid 54 | 55 | if nums[mid] < target: 56 | left = mid + 1 57 | else: 58 | right = mid - 1 59 | 60 | return -1 61 | 62 | 63 | ########### 64 | # Testing # 65 | ########### 66 | 67 | # Test 1 68 | # Correct result => 4 69 | print(search_rotated_sorted_array([4, 5, 6, 7, 0, 1, 2], 0)) -------------------------------------------------------------------------------- /Arrays/secret_santa.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Secret Santa 3 | 4 | Secret Santa is a game in which a group of friends or colleagues exchange Christmas presents anonymously, 5 | each member of the group being assigned another member for whom to provide a small gift. 6 | You're given a list of names, make a random pairs (each participant should have another name as pair). 7 | Return an array with pairs represented as tuples. 8 | 9 | Input: ['a', 'b', 'c'] 10 | Output: This is a nondeterministic algorithm, more solutions exists, here are 2 possible solutions: 11 | [('a', 'b'), ('b', 'c'), ('c', 'a')], [('a', 'c'), ('c', 'b'), ('b', 'a')] 12 | 13 | ========================================= 14 | Shuffle the array (this algorithm is explained in shuffle_array.py) and pair the current element 15 | with the next element (neighbouring). 16 | Time Complexity: O(N) 17 | Space Complexity: O(N) 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | from random import randint 26 | 27 | def secret_santa(names): 28 | # or use shuffle method from random module (from random import shuffle) 29 | shuffle_array(names) 30 | pairs = [] 31 | 32 | n = len(names) 33 | prev = names[-1] # or names[n - 1] 34 | 35 | for curr in names: 36 | pairs.append((prev, curr)) 37 | prev = curr 38 | 39 | return pairs 40 | 41 | def shuffle_array(arr): 42 | n = len(arr) 43 | 44 | for i in range(n): 45 | rand = randint(i, n - 1) # or randint(0, i) it's same 46 | arr[i], arr[rand] = arr[rand], arr[i] # swap elements 47 | 48 | # the original arr is already changed 49 | return arr 50 | 51 | 52 | ########### 53 | # Testing # 54 | ########### 55 | 56 | # Test 1 57 | # Correct result => nondeterministic algorithm, many solutions exist 58 | print(secret_santa(['a', 'b', 'c'])) -------------------------------------------------------------------------------- /Arrays/shuffle_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Shuffle an Array 3 | 4 | Given an array (array elements could be of any type/kind), 5 | write a program to generate in-place a random permutation of array elements. 6 | This question is also asked as “shuffle a deck of cards” or “randomize a given array”. 7 | Here shuffle means that every permutation of array element should equally likely. 8 | 9 | Input: [1, 2, 3] 10 | Output: This is a nondeterministic algorithm, N! solutions/permutations exist. 11 | In this case 3! = 6. All permutations are a valid solution. 12 | [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] 13 | 14 | ========================================= 15 | Easy in-place solution, choose an random index/position from I (I is the current position) to N-1 16 | (or choose from 0 to I, it's same), swap the element at random position with the element at I position. 17 | Increase I and continue with the same algorithm. 18 | This algorithm is called Fisher–Yates shuffle (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). 19 | Time Complexity: O(N) 20 | Space Complexity: O(1) 21 | 22 | Note: In Python there is already implemented shuffle (in random module "from random import shuffle") method 23 | which works in similar way. But it's easy to be implemented and I wanted to show how to implement it. 24 | ''' 25 | 26 | 27 | ############ 28 | # Solution # 29 | ############ 30 | 31 | from random import randint 32 | 33 | def shuffle_array(arr): 34 | n = len(arr) 35 | 36 | for i in range(n): 37 | rand = randint(i, n - 1) # or randint(0, i) it's same 38 | arr[i], arr[rand] = arr[rand], arr[i] # swap elements 39 | 40 | # the original arr is already changed 41 | return arr 42 | 43 | ########### 44 | # Testing # 45 | ########### 46 | 47 | # Test 1 48 | # Correct result => One of these: [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] 49 | print(shuffle_array([1, 2, 3])) -------------------------------------------------------------------------------- /Arrays/sort_rgb_array.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Sort RGB Array 3 | 4 | Given an array of strictly the characters 'R', 'G', and 'B', segregate 5 | the values of the array so that all the Rs come first, the Gs come second, and the Bs come last. 6 | You can only swap elements of the array. 7 | Do this in linear time and in-place. 8 | 9 | Input: ['G', 'B', 'R', 'R', 'B', 'R', 'G'] 10 | Output: ['R', 'R', 'R', 'G', 'G', 'B', 'B'] 11 | 12 | ========================================= 13 | Play with pointers/indices and swap elements. (only one iteration) 14 | Save the last R, G and B indices, when adding some color, move the rest indices by 1. 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | Count R, G, B and populate the array after that. (2 iterations) 18 | Time Complexity: O(N) 19 | Space Complexity: O(1) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def sort_rgb_array(arr): 28 | n = len(arr) 29 | 30 | # indexes/pointers of the last element of each color 31 | r, g, b = 0, 0, 0 32 | 33 | for i in range(n): 34 | # swap the element and move the pointer 35 | if arr[i] == 'R': 36 | swap(arr, i, r) 37 | r += 1 38 | 39 | # move pointer 40 | if r > g: 41 | g = r 42 | 43 | # swap the element and move the pointer 44 | if arr[i] == 'G': 45 | swap(arr, i, g) 46 | g += 1 47 | 48 | # move pointer 49 | if g > b: 50 | b = g 51 | 52 | # swap the element and move the pointer 53 | if arr[i] == 'B': 54 | swap(arr, i, b) 55 | b += 1 56 | 57 | return arr 58 | 59 | def swap(arr, i, j): 60 | # swaps two elements in an array 61 | arr[i], arr[j] = arr[j], arr[i] 62 | 63 | 64 | ############## 65 | # Solution 2 # 66 | ############## 67 | 68 | def sort_rgb_array_2(arr): 69 | rgb = { 70 | 'R': 0, 71 | 'G': 0, 72 | 'B': 0 73 | } 74 | 75 | # count colors 76 | for c in arr: 77 | rgb[c] += 1 78 | 79 | # adjust the intervals 80 | rgb['G'] += rgb['R'] 81 | rgb['B'] += rgb['G'] 82 | 83 | # assign colors 84 | for i in range(len(arr)): 85 | if i < rgb['R']: 86 | arr[i] = 'R' 87 | elif i < rgb['G']: 88 | arr[i] = 'G' 89 | else: 90 | arr[i] = 'B' 91 | 92 | return arr 93 | 94 | 95 | ########### 96 | # Testing # 97 | ########### 98 | 99 | # Test 1 100 | # Correct result => ['R', 'R', 'R', 'G', 'G', 'B', 'B'] 101 | print(sort_rgb_array(['G', 'B', 'R', 'R', 'B', 'R', 'G'])) 102 | print(sort_rgb_array_2(['G', 'B', 'R', 'R', 'B', 'R', 'G'])) 103 | 104 | # Test 2 105 | # Correct result => ['R', 'R', 'G', 'G', 'B', 'B', 'B'] 106 | print(sort_rgb_array(['B', 'B', 'B', 'G', 'G', 'R', 'R'])) 107 | print(sort_rgb_array_2(['B', 'B', 'B', 'G', 'G', 'R', 'R'])) -------------------------------------------------------------------------------- /Arrays/subarray_with_sum_k.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Subarray with given sum 3 | 4 | Given an unsorted array A of size N of non-negative integers, find a continuous sub-array 5 | which adds to a given number. Find starting and ending positions(1 indexing) of first such 6 | occuring subarray from the left if sum equals to subarray, else print -1. 7 | 8 | Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 15 9 | Output: 1, 5 10 | 11 | ========================================= 12 | Adjust the start and end index, in each step increase start or end idx. 13 | If sum is bigger than K, remove element from the start idx from the sum. 14 | Else add element from the end idx to the sum. 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | def find_subarray(arr, k): 25 | n = len(arr) 26 | 27 | if n == 0: 28 | return -1 29 | 30 | start = 0 31 | end = 0 32 | current_sum = arr[0] 33 | 34 | while end < n: 35 | if current_sum == k: 36 | return (start + 1, end + 1) 37 | 38 | if current_sum < k: 39 | end += 1 40 | current_sum += arr[end] 41 | else: 42 | current_sum -= arr[start] 43 | start += 1 44 | 45 | return -1 46 | 47 | 48 | ########### 49 | # Testing # 50 | ########### 51 | 52 | # Test 1 53 | # Correct result => (1, 5) 54 | print(find_subarray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 15)) 55 | 56 | # Test 2 57 | # Correct result => (2, 4) 58 | print(find_subarray([1, 2, 3, 7, 5], 12)) 59 | 60 | # Test 3 61 | # Correct result => (5, 5) 62 | print(find_subarray([6, 6, 6, 6, 3], 3)) -------------------------------------------------------------------------------- /Arrays/trapped_watter.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Trapped Water 3 | 4 | You are given an array of non-negative integers that represents a two-dimensional elevation map where each element is unit-width wall and the integer is the height. 5 | Suppose it will rain and all spots between two walls get filled up. 6 | Compute how many units of water remain trapped on the map in O(N) time and O(1) space. 7 | 8 | Input: [2, 1, 2] 9 | Output: 1 10 | Output explanation: We can hold 1 unit of water in the middle. 11 | 12 | Input: [3, 0, 1, 3, 0, 5] 13 | Output: 8 14 | Output explanation: We can hold 3 units in the first index, 2 in the second, and 3 in the fourth index (we cannot hold 5 since it would run off to the left), so we can trap 8 units of water. 15 | 16 | ========================================= 17 | The goal is to find the max wall and make 2 iterations starting from front and from back looking for the next bigger wall. 18 | First search for the max wall from front, after that correct the left water starting from the back side 19 | Time Complexity: O(N) 20 | Space Complexity: O(1) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def trapped_water(elevation_map): 29 | n = len(elevation_map) 30 | if n == 0: 31 | return 0 32 | 33 | water = 0 34 | 35 | # start from front of the array 36 | # and look for the max wall 37 | max_idx = 0 38 | max_height = elevation_map[0] 39 | 40 | for i in range(1, n): 41 | if elevation_map[i] >= max_height: 42 | max_idx = i # save the highest wall index for later 43 | max_height = elevation_map[i] 44 | 45 | water += max_height - elevation_map[i] 46 | 47 | # after that start from back and go reverse to the max wall idx 48 | # and correct the result (pour the extra water if there is smaller wall on the right side) 49 | back_max_height = elevation_map[-1] 50 | 51 | for i in range(n - 1, max_idx, -1): 52 | if elevation_map[i] > back_max_height: 53 | back_max_height = elevation_map[i] 54 | 55 | water -= max_height - back_max_height 56 | 57 | return water 58 | 59 | 60 | ########### 61 | # Testing # 62 | ########### 63 | 64 | # Test 1 65 | # Correct result => 1 66 | print(trapped_water([2, 1, 2])) 67 | 68 | # Test 2 69 | # Correct result => 8 70 | print(trapped_water([3, 0, 1, 3, 0, 5])) 71 | 72 | # Test 3 73 | # Correct result => 6 74 | print(trapped_water([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])) -------------------------------------------------------------------------------- /Dynamic Programming/climbing_staircase.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Climbing Staircase 3 | 4 | There exists a staircase with N steps, and you can climb up either X different steps at a time. 5 | Given N, write a function that returns the number of unique ways you can climb the staircase. 6 | The order of the steps matters. 7 | 8 | Input: steps = [1, 2], height = 4 9 | Output: 5 10 | Output explanation: 11 | 1, 1, 1, 1 12 | 2, 1, 1 13 | 1, 2, 1 14 | 1, 1, 2 15 | 2, 2 16 | 17 | ========================================= 18 | Dynamic Programing solution. 19 | Time Complexity: O(N*S) 20 | Space Complexity: O(N) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def climbing_staircase(steps, height): 29 | dp = [0 for i in range(height)] 30 | 31 | # add all steps into dp 32 | for s in steps: 33 | if s <= height: 34 | dp[s - 1] = 1 35 | 36 | # for each position look how you can arrive there 37 | for i in range(height): 38 | for s in steps: 39 | if i - s >= 0: 40 | dp[i] += dp[i - s] 41 | 42 | return dp[height - 1] 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Test 1 50 | # Correct result => 5 51 | print(climbing_staircase([1, 2], 4)) 52 | 53 | # Test 2 54 | # Correct result => 3 55 | print(climbing_staircase([1, 3, 5], 4)) -------------------------------------------------------------------------------- /Dynamic Programming/coin_change.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Coin Change 3 | 4 | You are given coins of different denominations and a total amount of money amount. 5 | Write a function to compute the fewest number of coins that you need to make up that amount. 6 | If that amount of money cannot be made up by any combination of the coins, return -1. 7 | 8 | Input: coins = [1, 2, 5], amount = 11 9 | Output: 3 10 | 11 | Input: coins = [2], amount = 3 12 | Output: -1 13 | 14 | ========================================= 15 | Dynamic programming solution 1 16 | Time Complexity: O(A*C) , A = amount, C = coins 17 | Space Complexity: O(A) 18 | Dynamic programming solution 2 (don't need the whole array, just use modulo to iterate through the partial array) 19 | Time Complexity: O(A*C) , A = amount, C = coins 20 | Space Complexity: O(maxCoin) 21 | ''' 22 | 23 | 24 | ############## 25 | # Solution 1 # 26 | ############## 27 | 28 | def coin_change_1(coins, amount): 29 | if amount == 0: 30 | return 0 31 | if len(coins) == 0: 32 | return -1 33 | 34 | max_value = amount + 1 # use this instead of math.inf 35 | dp = [max_value for i in range(max_value)] 36 | dp[0] = 0 37 | 38 | for i in range(1, max_value): 39 | for c in coins: 40 | if c <= i: 41 | # search on previous positions for min coins needed 42 | dp[i] = min(dp[i], dp[i - c] + 1) 43 | 44 | if (dp[amount] == max_value): 45 | return -1 46 | return dp[amount] 47 | 48 | 49 | ############## 50 | # Solution 2 # 51 | ############## 52 | 53 | def coin_change_2(coins, amount): 54 | if amount == 0: 55 | return 0 56 | if len(coins) == 0: 57 | return -1 58 | 59 | max_value = amount + 1 60 | max_coin = min(max_value, max(coins) + 1) 61 | dp = [max_value for i in range(max_coin)] 62 | dp[0] = 0 63 | 64 | for i in range(1, max_value): 65 | i_mod = i % max_coin 66 | dp[i_mod] = max_value # reset current position 67 | 68 | for c in coins: 69 | if c <= i: 70 | # search on previous positions for min coins needed 71 | dp[i_mod] = min(dp[i_mod], dp[(i - c) % max_coin] + 1) 72 | 73 | if (dp[amount % max_coin] == max_value): 74 | return -1 75 | return dp[amount % max_coin] 76 | 77 | 78 | ########### 79 | # Testing # 80 | ########### 81 | 82 | # Test 1 83 | # Correct result => 3 84 | coins = [1, 2, 5] 85 | amount = 11 86 | print(coin_change_1(coins, amount)) 87 | print(coin_change_2(coins, amount)) 88 | 89 | # Test 2 90 | # Correct result => -1 91 | coins = [2] 92 | amount = 3 93 | print(coin_change_1(coins, amount)) 94 | print(coin_change_2(coins, amount)) 95 | -------------------------------------------------------------------------------- /Dynamic Programming/count_ip_addresses.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Count IP Addresses 3 | 4 | An IP Address (IPv4) consists of 4 numbers which are all between 0 and 255. 5 | In this problem however, we are dealing with 'Extended IP Addresses' which consist of K such numbers. 6 | Given a string S containing only digits and a number K, 7 | your task is to count how many valid 'Extended IP Addresses' can be formed. 8 | An Extended IP Address is valid if: 9 | * it consists of exactly K numbers 10 | * each numbers is between 0 and 255, inclusive 11 | * a number cannot have leading zeroes 12 | 13 | Input: '1234567', 3 14 | Output: 1 15 | Output explanation: Valid IP addresses: '123.45.67'. 16 | 17 | Input: '100111', 3 18 | Output: 1 19 | Output explanation: Valid IP addresses: '100.1.11', '100.11.1', '10.0.111'. 20 | 21 | Input: '345678', 2 22 | Output: 0 23 | Output explanation: It is not possible to form a valid IP Address with two numbers. 24 | 25 | ========================================= 26 | 1D Dynamic programming solution. 27 | Time Complexity: O(N*K) 28 | Space Complexity: O(N) 29 | ''' 30 | 31 | 32 | ############ 33 | # Solution # 34 | ############ 35 | 36 | def count_ip_addresses(S, K): 37 | n = len(S) 38 | if n == 0: 39 | return 0 40 | if n < K: 41 | return 0 42 | 43 | dp = [0] * (n + 1) 44 | dp[0] = 1 45 | 46 | for i in range(K): 47 | # if you want to save just little calculations you can use min(3*(i+1), n) instead of n 48 | for j in range(n, i, -1): 49 | # reset the value 50 | dp[j] = 0 51 | 52 | # use iteration to check all 3 possible numbers (x, xx, xxx), instead of writing 3 IFs 53 | for e in range(max(i, j - 3), j): 54 | if is_valid(S[e : j]): 55 | dp[j] += dp[e] 56 | 57 | return dp[n] 58 | 59 | def is_valid(S): 60 | if (len(S) > 1) and (S[0] == '0'): 61 | return False 62 | return int(S) <= 255 63 | 64 | 65 | ########### 66 | # Testing # 67 | ########### 68 | 69 | # Test 1 70 | # Correct result => 1 71 | print(count_ip_addresses('1234567', 3)) 72 | 73 | # Test 2 74 | # Correct result => 3 75 | print(count_ip_addresses('100111', 3)) 76 | 77 | # Test 3 78 | # Correct result => 0 79 | print(count_ip_addresses('345678', 2)) -------------------------------------------------------------------------------- /Dynamic Programming/jump_game_2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Jump Game 2 3 | 4 | Given an array of non-negative integers, you are initially positioned at the first index of the array. 5 | Each element in the array represents your maximum jump length at that position. 6 | Your goal is to reach the last index in the minimum number of jumps. 7 | 8 | Input: XXX 9 | Output: XXX 10 | Output explanation: XXX 11 | 12 | ========================================= 13 | Classical 1D Dynamic Programming solution. 14 | Time Complexity: O(N) , maybe looks like O(N^2) but that's not possible 15 | Space Complexity: O(N) 16 | If you analyze the previous solution, you'll see that you don't need the whole DP array. 17 | Time Complexity: O(N) 18 | Space Complexity: O(1) 19 | ''' 20 | 21 | 22 | ############## 23 | # Solution 1 # 24 | ############## 25 | 26 | def min_jumps_1(nums): 27 | n = len(nums) 28 | if n <= 1: 29 | return 0 30 | 31 | dp = [-1]*n 32 | dp[0] = 0 33 | 34 | for i in range(n): 35 | this_jump = i + nums[i] 36 | jumps = dp[i] + 1 37 | 38 | if this_jump >= n - 1: 39 | return jumps 40 | 41 | # starging from back, go reverse and 42 | # change all -1 values and break when first positive is found 43 | for j in range(this_jump, i, -1): 44 | if dp[j] != -1: 45 | break 46 | dp[j] = jumps 47 | 48 | 49 | ############## 50 | # Solution 2 # 51 | ############## 52 | 53 | def min_jumps_2(nums): 54 | n = len(nums) 55 | if n <= 1: 56 | return 0 57 | 58 | jumps = 0 59 | max_jump = 0 60 | new_max_jump = 0 61 | 62 | for i in range(n): 63 | if max_jump < i: 64 | max_jump = new_max_jump 65 | jumps += 1 66 | 67 | this_jump = i + nums[i] 68 | if this_jump >= n - 1: 69 | return jumps + 1 70 | 71 | new_max_jump = max(new_max_jump, this_jump) 72 | 73 | 74 | ########### 75 | # Testing # 76 | ########### 77 | 78 | # Test 1 79 | # Correct result => 2 80 | nums = [2, 3, 1, 1, 4] 81 | print(min_jumps_1(nums)) 82 | print(min_jumps_2(nums)) -------------------------------------------------------------------------------- /Dynamic Programming/longest_common_subsequence.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Common Subsequence 3 | 4 | Given 2 strings, find the longest common subseqence - https://en.wikipedia.org/wiki/Longest_common_subsequence_problem 5 | NOT Longest Common Substring, this is a different problem. 6 | Substring is a string composed ONLY of neighboring chars, subsequence could contain non-neighboring chars. 7 | 8 | Input: 'ABAZDC', 'BACBAD' 9 | Output: 'ABAD' 10 | 11 | Input: 'I'm meto', 'I am Meto' 12 | Output: 'Im eto' 13 | 14 | ========================================= 15 | Dynamic programming solution. 16 | Find more details here: https://www.geeksforgeeks.org/printing-longest-common-subsequence/ 17 | Time Complexity: O(N * M) 18 | Space Complexity: O(N * M) , can be O(M) see longest_common_substring.py solution (but you'll need to save subsequences) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def longest_common_subsequence(str1, str2): 27 | n, m = len(str1), len(str2) 28 | # create dp matrix 29 | dp = [[0 for j in range(m + 1)] for i in range(n + 1)] 30 | 31 | # run dp 32 | for i in range(1, n + 1): 33 | for j in range(1, m + 1): 34 | # checks only in 3 directions in the table 35 | # in short: to the current position dp could come from those 3 previous positions 36 | # ^ ^ 37 | # \ | 38 | # <- O 39 | if str1[i - 1] == str2[j - 1]: 40 | # from this position dp could come only if there is a match in the previous chars 41 | dp[i][j] = dp[i - 1][j - 1] + 1 42 | else: 43 | # dp could come from these positions only if there is no much 44 | dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 45 | 46 | # find the subseqence/string 47 | letters = dp[n][m] 48 | # use an array for storing the chars because string manipulation operations are not time and space efficient 49 | result = ['' for i in range(letters)] 50 | i = n 51 | j = m 52 | 53 | while (i != 0) and (j != 0): 54 | # use the inverse logic from upper code (filling the dp table) 55 | if str1[i - 1] == str2[j - 1]: 56 | letters -= 1 57 | result[letters] = str1[i - 1] 58 | j -= 1 59 | i -= 1 60 | elif dp[i - 1][j] < dp[i][j - 1]: 61 | j -= 1 62 | else: 63 | i -= 1 64 | 65 | return ''.join(result) 66 | 67 | 68 | ########### 69 | # Testing # 70 | ########### 71 | 72 | # Test 1 73 | # Correct result => 'ABAD' 74 | print(longest_common_subsequence('ABAZDC', 'BACBAD')) 75 | 76 | # Test 2 77 | # Correct result => 'Im eto' 78 | print(longest_common_subsequence('I\'m meto', 'I am Meto')) 79 | -------------------------------------------------------------------------------- /Dynamic Programming/longest_common_substring.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Common Substring 3 | 4 | Given two strings X and Y, find the of longest common substring. 5 | 6 | Input: 'GeeksforGeeks', 'GeeksQuiz' 7 | Output: 'Geeks' 8 | 9 | ========================================= 10 | Dynamic Programming Solution. 11 | Time Complexity: O(N * M) 12 | Space Complexity: O(M) 13 | * For this problem exists a faster solution, using Suffix tree, Time Complexity O(N + M). 14 | ''' 15 | 16 | 17 | ############ 18 | # Solution # 19 | ############ 20 | 21 | def longest_common_substring(str1, str2): 22 | n, m = len(str1), len(str2) 23 | # instead of creating a whole dp table, use only 2 rows (current and previous row) 24 | curr = [0 for j in range(m + 1)] 25 | prev = [] 26 | max_length = 0 27 | max_idx = 0 28 | 29 | for i in range(1, n + 1): 30 | # save the previous row and create the current row 31 | prev = curr 32 | curr = [0 for j in range(m + 1)] 33 | 34 | for j in range(1, m + 1): 35 | if str1[i - 1] == str2[j - 1]: 36 | # search only for matching chars 37 | curr[j] = prev[j - 1] + 1 38 | 39 | if curr[j] > max_length: 40 | # save the last matching index of the first string 41 | max_length = curr[j] 42 | max_idx = i 43 | 44 | return str1[max_idx - max_length: max_idx] 45 | 46 | 47 | ########### 48 | # Testing # 49 | ########### 50 | 51 | # Test 1 52 | # Correct result => BABC 53 | print(longest_common_substring('ABABC', 'BABCA')) 54 | 55 | # Test 2 56 | # Correct result => Geeks 57 | print(longest_common_substring('GeeksforGeeks', 'GeeksQuiz')) 58 | 59 | # Test 3 60 | # Correct result => abcd 61 | print(longest_common_substring('abcdxyz', 'xyzabcd')) 62 | 63 | # Test 4 64 | # Correct result => abcdez 65 | print(longest_common_substring('zxabcdezy', 'yzabcdezx')) -------------------------------------------------------------------------------- /Dynamic Programming/max_subarray_sum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Maximum subarray sum 3 | 4 | The subarray must be contiguous. 5 | 6 | Sample input: [-2, -3, 4, -1, -2, 1, 5, -3] 7 | Sample output: 7 8 | Output explanation: [4, -1, -2, 1, 5] 9 | 10 | ========================================= 11 | Need only one iteration, in each step add the current element to the current sum. 12 | When the sum is less than 0, reset the sum to 0 and continue with adding. (we care only about non-negative sums) 13 | After each addition, check if the current sum is greater than the max sum. (Called Kadane's algorithm) 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def max_subarray_sum(a): 24 | curr_sum = 0 25 | max_sum = 0 26 | 27 | for val in a: 28 | # extend the current sum with the curren value; 29 | # reset it to 0 if it is smaller than 0, we care only about non-negative sums 30 | curr_sum = max(0, curr_sum + val) 31 | 32 | # check if this is the max sum 33 | max_sum = max(max_sum, curr_sum) 34 | 35 | return max_sum 36 | 37 | 38 | ########### 39 | # Testing # 40 | ########### 41 | 42 | # Test 1 43 | # Correct result => 7 44 | print(max_subarray_sum([-2, -3, 4, -1, -2, 1, 5, -3])) 45 | 46 | # Test 2 47 | # Correct result => 5 48 | print(max_subarray_sum([1, -2, 2, -2, 3, -2, 4, -5])) 49 | 50 | # Test 3 51 | # Correct result => 7 52 | print(max_subarray_sum([-2, -5, 6, -2, -3, 1, 5, -6])) 53 | 54 | # Test 4 55 | # Correct result => 0 56 | print(max_subarray_sum([-6, -1])) -------------------------------------------------------------------------------- /Dynamic Programming/min_cost_coloring.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Min Cost Coloring 3 | 4 | A builder is looking to build a row of N houses that can be of K different colors. 5 | He has a goal of minimizing cost while ensuring that no two neighboring houses are of the same color. 6 | Given an N by K matrix where the nth row and kth column represents the cost to build the 7 | nth house with kth color, return the minimum cost which achieves this goal. 8 | 9 | ========================================= 10 | Dynamic programming, for each house search for the cheapest combination of the previous houses. 11 | But don't search the whole array with combinations (colors), save only the smallest 2 12 | (in this case we're sure that the previous house doesn't have the same color). 13 | Time Complexity: O(N * K) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | ############ 18 | # Solution # 19 | ############ 20 | 21 | import math 22 | 23 | def min_cost_coloring(dp): 24 | # no need from a new dp matrix, you can use the input matrix 25 | n = len(dp) 26 | if n == 0: 27 | return 0 28 | m = len(dp[0]) 29 | if m < 2: 30 | return -1 31 | 32 | # save only the smallest 2 costs instead of searching the whole previous array 33 | prev_min = [(0, -1), (0, -1)] 34 | 35 | for i in range(n): 36 | curr_min = [(math.inf, -1), (math.inf, -1)] 37 | 38 | for j in range(m): 39 | # find result with different color 40 | if j != prev_min[0][1]: 41 | dp[i][j] += prev_min[0][0] 42 | else: 43 | dp[i][j] += prev_min[1][0] 44 | 45 | # save the current result if smaller than the current 2 46 | if curr_min[0][0] > dp[i][j]: 47 | curr_min[1] = curr_min[0] 48 | curr_min[0] = (dp[i][j], j) 49 | elif curr_min[1][0] > dp[i][j]: 50 | curr_min[1] = (dp[i][j], j) 51 | 52 | prev_min = curr_min 53 | 54 | # return the min cost of the last house 55 | return min(dp[n - 1]) 56 | 57 | 58 | ########### 59 | # Testing # 60 | ########### 61 | 62 | # Test 1 63 | # Correct result => 5 64 | print(min_cost_coloring([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [3, 2, 1, 4, 5], [3, 2, 1, 4, 3]])) 65 | 66 | # Test 2 67 | # Correct result => 6 68 | print(min_cost_coloring([[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]])) 69 | -------------------------------------------------------------------------------- /Dynamic Programming/number_of_decodings.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Number of Decodings 3 | 4 | Given the mapping a=1, b=2, ... , z=26, and an encoded message, count the number of ways it can be decoded. 5 | For example, the message "111" would give 3, since it could be decoded as "aaa", "ka" and "ak". 6 | All of the messages are decodable! 7 | 8 | ========================================= 9 | The easiest solution is Brute-Force (building a tree and making all combinations), 10 | and in the worst case there will be Fibbionaci(N) combinations, so the worst Time Complexity will be O(Fib(N)) 11 | 12 | Dynamic programming solution. Similar to number_of_smses.py. 13 | Time Complexity: O(N) 14 | Space Complexity: O(N) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def num_decodings(code): 23 | n = len(code) 24 | dp = [0 for i in range(n)] 25 | 26 | if n == 0: 27 | return 0 28 | dp[0] = 1 29 | if n == 1: 30 | return dp[0] 31 | dp[1] = (code[1] != '0') + is_valid(code[0:2]) 32 | 33 | for i in range(2, n): 34 | if code[i] != '0': 35 | # looking for how many combinations are there till now if this is a single digit 36 | dp[i] += dp[i-1] 37 | if is_valid(code[i-1 : i+1]): 38 | # looking for how many combinations are there till now if this is a number of 2 digits 39 | dp[i] += dp[i-2] 40 | 41 | return dp[n-1] 42 | 43 | def is_valid(code): 44 | k = int(code) 45 | return (k < 27) and (k > 9) 46 | 47 | 48 | ########### 49 | # Testing # 50 | ########### 51 | 52 | # Test 1 53 | # Correct result => 5 54 | print(num_decodings('12151')) 55 | 56 | # Test 2 57 | # Correct result => 5 58 | print(num_decodings('1111')) 59 | 60 | # Test 3 61 | # Correct result => 3 62 | print(num_decodings('111')) 63 | 64 | # Test 4 65 | # Correct result => 1 66 | print(num_decodings('1010')) 67 | 68 | # Test 5 69 | # Correct result => 4 70 | print(num_decodings('2626')) 71 | 72 | # Test 6 73 | # Correct result => 1 74 | print(num_decodings('1')) 75 | 76 | # Test 7 77 | # Correct result => 2 78 | print(num_decodings('11')) 79 | 80 | # Test 8 81 | # Correct result => 3 82 | print(num_decodings('111')) 83 | 84 | # Test 9 85 | # Correct result => 5 86 | print(num_decodings('1111')) 87 | 88 | # Test 10 89 | # Correct result => 8 90 | print(num_decodings('11111')) 91 | 92 | # Test 11 93 | # Correct result => 13 94 | print(num_decodings('111111')) 95 | 96 | # Test 12 97 | # Correct result => 21 98 | print(num_decodings('1111111')) 99 | 100 | # Test 13 101 | # Correct result => 34 102 | print(num_decodings('11111111')) -------------------------------------------------------------------------------- /Dynamic Programming/number_of_smses.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Number of SMSes 3 | 4 | Given the number sequence that is being typed in order to write and SMS message, return the count 5 | of all the possible messages that can be constructed. 6 | 7 | 1 2 3 8 | abc def 9 | 10 | 4 5 6 11 | ghi jkl mno 12 | 13 | 7 8 9 14 | pqrs tuv wxyz 15 | 16 | The blank space character is constructed with a '0'. 17 | 18 | Input: '222' 19 | Output: 4 20 | Output explanation: '222' could mean: 'c', 'ab','ba' or 'aaa'. That makes 4 possible messages. 21 | 22 | ========================================= 23 | Dynamic programming solution. Similar to number_of_decodings.py. 24 | Time Complexity: O(N) 25 | Space Complexity: O(N) 26 | ''' 27 | 28 | 29 | ############ 30 | # Solution # 31 | ############ 32 | 33 | def num_smses(sequence): 34 | n = len(sequence) 35 | dp = [0] * n 36 | 37 | # dp starting values, check all 4 possible starting combinations 38 | for i in range(min(4, n)): 39 | if is_valid(sequence[0 : i+1]): 40 | dp[i] = 1 41 | 42 | # run dp 43 | for i in range(1, n): 44 | # check all 4 possible combinations (x, xx, xxx, xxxx) 45 | for j in range(min(4, i)): 46 | if is_valid(sequence[i-j : i+1]): 47 | dp[i] += dp[i - j - 1] 48 | 49 | return dp[n - 1] 50 | 51 | def is_valid(sequence): 52 | ch = sequence[0] 53 | 54 | for c in sequence: 55 | if c != ch: 56 | return False 57 | 58 | if sequence == '0': 59 | return True 60 | 61 | if ((ch >= '2' and ch <= '6') or ch == '8') and (len(sequence) < 4): 62 | return True 63 | 64 | if (ch == '7') or (ch == '9'): 65 | return True 66 | 67 | return False 68 | 69 | 70 | ########### 71 | # Testing # 72 | ########### 73 | 74 | # Test 1 75 | # Correct result => 4 76 | print(num_smses('222')) 77 | 78 | # Test 2 79 | # Correct result => 14 80 | print(num_smses('2202222')) 81 | 82 | # Test 3 83 | # Correct result => 274 84 | print(num_smses('2222222222')) 85 | -------------------------------------------------------------------------------- /Dynamic Programming/ordered_digits.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ordered Digits 3 | 4 | We are given a number and we need to transform to a new number where all its digits are ordered in a non descending order. 5 | We are allowed to increase or decrease a digit by 1, and each of those actions counts as one operation. 6 | We are also allowed to over/underflow a number meaning from '9' we can change to '0' and also from '0' to '9', also costing only one operation. 7 | One same digit can be changed multiple times. 8 | Find the minimum number of operations we need to do do to create a new number with its ordered digits. 9 | 10 | Input: 301 11 | Output: 3 12 | Output explanation: 301 -> 201 -> 101 -> 111, in this case 3 operations are required to get an ordered number. 13 | 14 | Input: 901 15 | Output: 1 16 | Output explanation: 901 -> 001, in this case 1 operation is required to get an ordered number. 17 | 18 | Input: 5982 19 | Output: 4 20 | Output explanation: 5982 -> 5981 -> 5980 -> 5989 -> 5999, in this case 4 operations are required to get an ordered number. 21 | 22 | ========================================= 23 | Dynamic programming solution. For each position, calculate the cost of transformation to each possible digit (0-9). 24 | And take the minimum value from the previous position (but smaller than the current digit). 25 | Time Complexity: O(N) , O(N*10) = O(N), N = number of digits 26 | Space Complexity: O(N) , same O(N*2) = O(N) 27 | ''' 28 | 29 | 30 | ############ 31 | # Solution # 32 | ############ 33 | 34 | def ordered_digits(number): 35 | n = len(number) 36 | dp = [[0 for j in range(10)] for i in range(2)] 37 | 38 | for i in range(n): 39 | min_prev = float('inf') 40 | for j in range(10): 41 | # find the min value from the previous digit and add it to the current value 42 | min_prev = min(min_prev, dp[(i - 1) % 2][j]) 43 | # compute diff between the current digit and wanted digit 44 | diff = abs(j - int(number[i])) 45 | dp[i % 2][j] = min(diff, 10 - diff) + min_prev 46 | 47 | # min value from the last digit 48 | return min(dp[(n - 1) % 2]) 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | # Correct result => 3 57 | print(ordered_digits('301')) 58 | 59 | # Test 2 60 | # Correct result => 1 61 | print(ordered_digits('901')) 62 | 63 | # Test 3 64 | # Correct result => 4 65 | print(ordered_digits('5982')) -------------------------------------------------------------------------------- /Dynamic Programming/split_coins.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Split Coins 3 | 4 | You have a number of coins with various amounts. 5 | You need to split the coins in two groups so that the difference between those groups in minimal. 6 | 7 | Input: [1, 1, 1, 3, 5, 10, 18] 8 | Output: 1 9 | Output explanation: First group 1, 3, 5, 10 (or 1, 1, 3, 5, 10) and second group 1, 1, 18 (or 1, 18). 10 | 11 | ========================================= 12 | Simple dynamic programming solution. Find the closest sum to the half of the sum of all coins. 13 | Time Complexity: O(C*HS) , C = number of coins, HS = half of the sum of all coins 14 | Space Complexity: O(HS) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def split_coins(coins): 23 | if len(coins) == 0: 24 | return -1 25 | 26 | full_sum = sum(coins) 27 | half_sum = full_sum // 2 + 1 28 | 29 | dp = [False]*half_sum 30 | dp[0] = True 31 | 32 | for c in coins: 33 | for i in range(half_sum - 1, -1, -1): 34 | if (i >= c) and dp[i - c]: 35 | # if you want to find coins, save the coin here dp[i] = c 36 | dp[i] = True 37 | 38 | for i in range(half_sum - 1, -1, -1): 39 | if dp[i]: 40 | # if you want to print coins, while i>0: print(dp[i]) i -= dp[i] 41 | return full_sum - 2 * i 42 | 43 | # not possible 44 | return -1 45 | 46 | 47 | ########### 48 | # Testing # 49 | ########### 50 | 51 | # Test 1 52 | # Correct result => 1 53 | print(split_coins([1, 1, 1, 3, 5, 10, 18])) -------------------------------------------------------------------------------- /Dynamic Programming/sum_non-adjecent.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Sum of non-adjacent numbers 3 | 4 | Given a list of integers, write a function that returns the largest sum of non-adjacent numbers. 5 | Numbers can be 0 or negative. 6 | 7 | Input: [2, 4, 6, 2, 5] 8 | Output: 13 9 | Output explanation: We pick 2, 6, and 5. 10 | 11 | Input: [5, 1, 1, 5] 12 | Output: 10 13 | Output explanation: We pick 5 and 5. 14 | 15 | ========================================= 16 | Dynamic programming solution, but don't need the whole DP array, only the last 3 sums (DPs) are needed. 17 | Time Complexity: O(N) 18 | Space Complexity: O(1) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def sum_non_adjacent(arr): 27 | n = len(arr) 28 | # from the dp matrix you only need the last 3 sums 29 | sums = [0, 0, 0] 30 | 31 | # TODO: refactor these if-elses, those are to skip using of DP matrix 32 | if n == 0: 33 | return 0 34 | 35 | # if negative or zero, the sum will be 0 36 | sums[0] = max(arr[0], 0) 37 | 38 | if n == 1: 39 | return sums[0] 40 | 41 | sums[1] = arr[1] 42 | # if the second number is negative or zero, then jump it 43 | if sums[1] <= 0: 44 | sums[1] = sums[0] 45 | 46 | if n == 2: 47 | return max(sums[0], sums[1]) 48 | 49 | sums[2] = arr[2] 50 | # if the third number is negative or zero, then jump it 51 | if sums[2] <= 0: 52 | sums[2] = max(sums[0], sums[1]) 53 | else: 54 | sums[2] += sums[0] 55 | 56 | # THE SOLUTION 57 | for i in range(3, n): 58 | temp = 0 59 | 60 | if arr[i] > 0: 61 | # take this number, because it's positive and the sum will be bigger 62 | temp = max(sums[0], sums[1]) + arr[i] 63 | else: 64 | # don't take this number, because the sum will be same or smaller 65 | temp = max(sums) 66 | 67 | # remove the first sum 68 | sums = sums[1:] + [temp] 69 | 70 | # return the max sum 71 | return max(sums) 72 | 73 | 74 | ########### 75 | # Testing # 76 | ########### 77 | 78 | # Test 1 79 | # Correct result => 13 80 | print(sum_non_adjacent([2, 4, 6, 2, 5])) 81 | 82 | # Test 2 83 | # Correct result => 15 84 | print(sum_non_adjacent([2, 4, 2, 6, 2, -3, -2, 0, -3, 5])) 85 | 86 | # Test 3 87 | # Correct result => 10 88 | print(sum_non_adjacent([5, 1, 1, 5])) 89 | 90 | # Test 4 91 | # Correct result => 10 92 | print(sum_non_adjacent([5, 1, -1, 1, 5])) -------------------------------------------------------------------------------- /Dynamic Programming/transform_number_ascending_digits.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Transform Number Ascending Digits 3 | 4 | Given a number and we need to transform to a new number where all its digits are ordered in a non descending order. 5 | All digits can be increased, decreased, over/underflow are allowed. 6 | Find the minimum number of operations we need to do to create a new number with its ordered digits. 7 | 8 | Input: '5982' 9 | Output: 4 10 | Output explanation: 5999, 1 operation to transform 8 to 9, 3 operations to transform 2 to 9. 11 | 12 | ========================================= 13 | Dynamic programming solution. 14 | Time Complexity: O(N) , O(N * 10 * 10) = O(100 N) = O(N) 15 | Space Complexity: O(1) , O(10 * 10) = O(100) = O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def operations(number): 24 | n = len(number) 25 | diff = lambda i, j: abs(j - int(number[i])) 26 | # compute diff between the current digit and wanted digit, and fill the dp 27 | prev_dp = [min(diff(0, i), 10 - diff(0, i)) for i in range(10)] 28 | 29 | # go through all digits and see all possible combinations using dynamic programming 30 | for i in range(1, n): 31 | curr_dp = [min(diff(i, j), 10 - diff(i, j)) for j in range(10)] 32 | for j in range(10): 33 | # find the min value for the previous digit and add it to the current value 34 | curr_dp[j] += min(prev_dp[0 : j + 1]) 35 | prev_dp = curr_dp 36 | 37 | # min value from the last digit 38 | min_dist = min(prev_dp) 39 | 40 | return min_dist 41 | 42 | 43 | ########### 44 | # Testing # 45 | ########### 46 | 47 | # Test 1 48 | # Correct result => 1 49 | print(operations('901')) 50 | 51 | # Test 2 52 | # Correct result => 3 53 | print(operations('301')) 54 | 55 | # Test 3 56 | # Correct result => 4 57 | print(operations('5982')) -------------------------------------------------------------------------------- /Hashing DS/count_positives.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Count Positives 3 | 4 | Given several numbers, count how many different results bigger or equal than 0 can you produce by only 5 | using addition (+) and substraction (-). All the numbers must be used. 6 | 7 | Input: [2, 3, 1] 8 | Output: 4 9 | Output explanation: 10 | 2+3+1 = 6 11 | 2+3-1 = 4 12 | 2-3+1 = 0 13 | 2-3-1 = -2 (negative) 14 | -2+3+1 = 2 15 | -2+3-1 = 0 (double) 16 | -2-3+1 = -4 (negative) 17 | -2-3-1 = - 6 (negative) 18 | 19 | ========================================= 20 | Use hashset and make all combinations. 21 | Time Complexity: O(2^N) , I'm not sure how to compute the real complexity, but it's TOO MUCH faster than 2^N 22 | Space Complexity: O(2^N) 23 | ''' 24 | 25 | 26 | ############ 27 | # Solution # 28 | ############ 29 | 30 | def count_positives(numbers): 31 | results = set() 32 | results.add(0) 33 | 34 | # make all combinations 35 | for num in numbers: 36 | temp = set() # use a temporary hashset for the newest results 37 | for res in results: 38 | temp.add(res + num) 39 | temp.add(res - num) 40 | results = temp # replace the results 41 | 42 | # count unique positives 43 | count = 0 44 | for res in results: 45 | if res >= 0: 46 | count += 1 47 | 48 | return count 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | # Correct result => 4 57 | print(count_positives([2, 3, 1])) -------------------------------------------------------------------------------- /Hashing DS/find_duplicates.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find duplicates 3 | 4 | Find all duplicates in an array where all elements are positive (>0) 5 | and the biggest element in the array could be equal to the length of array. 6 | Note: solve it in one iteration. 7 | 8 | ========================================= 9 | Each value has its own position/index in the array, 10 | mark the value on that position as negative when the element is found for the first time. 11 | Time Complexity: O(N) 12 | Space Complexity: O(D) , array (in this case set) to save all duplicates 13 | In the second solution 2 hashsets are used, one to check if already exists element like current and 14 | the other has same functionality as the hashset in the first solution. 15 | * This solution is for all kind of numbers 16 | (not as the previous solution, only for positive numbers or smaller elements than the length of array). 17 | Time Complexity: O(N) 18 | Space Complexity: O(D) 19 | ''' 20 | 21 | 22 | ############## 23 | # Solution 1 # 24 | ############## 25 | 26 | def find_duplicates(arr): 27 | n = len(arr) 28 | duplicates = set() 29 | 30 | for i in range(n): 31 | idx = abs(arr[i]) - 1 32 | val = arr[idx] 33 | 34 | if val > 0: 35 | # mark element as found for the first time 36 | arr[idx] = -val 37 | else: 38 | # this element is a duplicate 39 | duplicates.add(idx + 1) 40 | 41 | return duplicates 42 | 43 | 44 | ############## 45 | # Solution 2 # 46 | ############## 47 | 48 | def find_duplicates_2(arr): 49 | n = len(arr) 50 | duplicates = set() 51 | elements = set() 52 | 53 | for i in range(n): 54 | if arr[i] in duplicates: 55 | # this element is already found as duplicate 56 | continue 57 | 58 | if arr[i] in elements: 59 | # a duplicate is found 60 | duplicates.add(arr[i]) 61 | elements.remove(arr[i]) 62 | else: 63 | # a new number 64 | elements.add(arr[i]) 65 | 66 | return duplicates 67 | 68 | 69 | ########### 70 | # Testing # 71 | ########### 72 | 73 | # Test 1 74 | # Correct result => [1] 75 | print(find_duplicates([1, 1, 1, 1])) 76 | print(find_duplicates_2([1, 1, 1, 1])) 77 | 78 | # Test 2 79 | # Correct result => [4, 2] 80 | print(find_duplicates([4, 2, 4, 2, 1, 4])) 81 | print(find_duplicates_2([4, 2, 4, 2, 1, 4])) -------------------------------------------------------------------------------- /Hashing DS/find_pairs_with_sum_k.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find pairs with sum K 3 | 4 | Given an array, find all pairs which sum is equal to K. 5 | 6 | Input: [1, 2, 3, 4, 5, 5, 6, 7, 8, 9], 5 7 | Output: [(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)] 8 | 9 | ========================================= 10 | Save numbers as complements in a hashset and for each number search for the pair complement (K-number). 11 | Time Complexity: O(N) 12 | Space Complexity: O(N) 13 | ''' 14 | 15 | 16 | ############ 17 | # Solution # 18 | ############ 19 | 20 | def find_pairs(arr, K): 21 | # set to save all complements 22 | complements = set() 23 | # set to save all unique complements that form a pair 24 | pair_complements = set() 25 | 26 | for el in arr: 27 | c = K - el 28 | 29 | # if complement exists, then a pair is found 30 | if c in complements: 31 | pair_complements.add(c) 32 | 33 | # save this number as complement 34 | complements.add(el) 35 | 36 | # find all unique pairs 37 | pairs = [] 38 | for c in pair_complements: 39 | pairs.append((c, K - c)) 40 | 41 | return pairs 42 | 43 | 44 | ########### 45 | # Testing # 46 | ########### 47 | 48 | # Test 1 49 | # Correct result => [(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)] 50 | print(find_pairs([1, 2, 2, 3, 4, 5, 5, 5, 6, 7, 8, 8, 9], 10)) -------------------------------------------------------------------------------- /Hashing DS/group_anagrams.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Group Anagrams 3 | 4 | Given an array of strings, group anagrams together. 5 | (An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once) 6 | 7 | Input: ['eat', 'tea', 'tan', 'ate', 'nat', 'bat'] 8 | Output: [['eat', 'ate', 'tea'], ['tan', 'nat'], ['bat']] 9 | 10 | ========================================= 11 | This problem can be solved using a dictionary (hash map), but in order to use a dictinary you'll need to find 12 | a way to calculate the keys for all strings. This is a same solution but 2 different hash functions. 13 | 14 | Sort the letters from the strings, and use the sorted letters as key. 15 | Time Complexity: O(N * KLogK) , N = number of strings, K = number of characters (chars in the string with most chars) 16 | Space Complexity: O(N) 17 | Use a letter counter (some kind of counting sort). 18 | Time Complexity: O(N * K) , O(N * K * 26) = O(N * K), if all of the strings have several chars (less than ~8) the first hash function is better. 19 | Space Complexity: O(N) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def group_anagrams(strs): 28 | anagrams = {} 29 | 30 | for st in strs: 31 | # or hashable_object = hash_1(st) 32 | hashable_object = hash_2(st) 33 | 34 | if hashable_object not in anagrams: 35 | anagrams[hashable_object] = [] 36 | anagrams[hashable_object].append(st) 37 | 38 | return [anagrams[res] for res in anagrams] 39 | 40 | def hash_1(st): 41 | chars = list(st) 42 | chars.sort() 43 | # or you can use a string as hash, ''.join(chars) 44 | return tuple(chars) 45 | 46 | def hash_2(st): 47 | all_letters = [0]*26 48 | ord_a = 97 # ord('a') 49 | for c in st: 50 | all_letters[ord(c) - ord_a] += 1 51 | # or you can use a string as hash, ''.join(all_letters), example: ' '.join(all_letters) 52 | return tuple(all_letters) 53 | 54 | 55 | ########### 56 | # Testing # 57 | ########### 58 | 59 | # Test 1 60 | # Correct result => [['eat', 'ate', 'tea'], ['tan', 'nat'], ['bat']] 61 | print(group_anagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])) -------------------------------------------------------------------------------- /Hashing DS/longest_consecutive_sequence.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Consecutive Sequence (Largest Range) 3 | 4 | Short explanation: Given an unsorted array of integers, find first and last element 5 | of the longest consecutive elements sequence. 6 | 7 | Long explanation: Write a function that takes in an array of integers and returns an array of 8 | length 2 representing the largest range of numbers contained in that array. 9 | The first number in the output array should be the first number in the range while the second number 10 | should be the last number in the range. A range of numbers is defined as a set of numbers 11 | that come right after each other in the set of real integers. 12 | For instance, the output array [2, 6] represents the range {2, 3, 4, 5, 6}, which is a range of length 5. 13 | Note that numbers do not need to be ordered or adjacent in the array in order to form a range. 14 | 15 | Input: [1, 11, 3, 0, 15, 5, 2, 4, 10, 7, 12, 6] 16 | Output: [0, 7] 17 | 18 | ========================================= 19 | The simplest solution is to sort the array, that's O(N LogN) time complexity. 20 | 21 | But this solution is faster, it uses a dictionary (HashMap). 22 | For each number tries to find the smaller and the bigger consequence numbers, and marks them as visited. 23 | Time Complexity: O(N) 24 | Space Complexity: O(N) 25 | ''' 26 | 27 | 28 | ############ 29 | # Solution # 30 | ############ 31 | 32 | def largest_range(array): 33 | visited = {} 34 | for el in array: 35 | visited[el] = False 36 | 37 | max_range = [array[0], array[0]] 38 | for el in array: 39 | if visited[el]: 40 | # this element is visited in another range, no need from searching again for this range 41 | continue 42 | 43 | visited[el] = True 44 | 45 | # go left 46 | left_border = el - 1 47 | while left_border in visited: 48 | visited[left_border] = True 49 | left_border -= 1 50 | # update the left_border because that number doesn't exist 51 | left_border += 1 52 | 53 | # go right 54 | right_border = el + 1 55 | while right_border in visited: 56 | visited[right_border] = True 57 | right_border += 1 58 | # update the right_border because that number doesn't exist 59 | right_border -= 1 60 | 61 | if (max_range[1] - max_range[0]) < (right_border - left_border): 62 | max_range = [left_border, right_border] 63 | 64 | return max_range 65 | 66 | 67 | ########### 68 | # TESTING # 69 | ########### 70 | 71 | # Test 1 72 | # Correct result => [-1, 19] 73 | print(largest_range([0, 9, 19, -1, 18, 17, 2, 10, 3, 12, 5, 16, 4, 11, 8, 7, 6, 15, 12, 12, 2, 1, 6, 13, 14])) 74 | 75 | # Test 2 76 | # Correct result => [-7, 7] 77 | print(largest_range([0, -5, 9, 19, -1, 18, 17, 2, -4, -3, 10, 3, 12, 5, 16, 4, 11, 7, -6, -7, 6, 15, 12, 12, 2, 1, 6, 13, 14, -2])) 78 | 79 | # Test 3 80 | # Correct result => [-8, 19] 81 | print(largest_range([-7, -7, -7, -7, 8, -8, 0, 9, 19, -1, -3, 18, 17, 2, 10, 3, 12, 5, 16, 4, 11, -6, 8, 7, 6, 15, 12, 12, -5, 2, 1, 6, 13, 14, -4, -2])) -------------------------------------------------------------------------------- /Hashing DS/longest_substring_with_k_distinct_characters.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Substring With k Distinct Characters 3 | 4 | Given an integer k and a string s, find the length of the longest substring that contains at most k distinct characters. 5 | 6 | Input: s = 'abcba', k = 2 7 | Output: 'bcb' 8 | 9 | ========================================= 10 | Simple solution (like sliding window or queue, add to the end and remove from the front). 11 | Time Complexity: O(N) 12 | Space Complexity: O(N) 13 | ''' 14 | 15 | 16 | ############ 17 | # Solution # 18 | ############ 19 | 20 | def longest_substring_with_distinct_characters(s, k): 21 | letters = {} 22 | longest = 0 23 | length = 0 24 | 25 | for i in range(len(s)): 26 | if s[i] in letters: 27 | # if this letter exists then only increase the counter and length 28 | letters[s[i]] += 1 29 | length += 1 30 | else: 31 | # if this letter doesn't exist then remove all distinct letters from the front 32 | # so the count of distinct letters will be k-1 33 | while len(letters) == k: 34 | firstLetter = s[i - length] 35 | letters[firstLetter] -= 1 # decrease the counter 36 | if letters[firstLetter] == 0: 37 | # remove this letter from the dictionary because 38 | # in the susbtring there are no letters like this 39 | del letters[firstLetter] 40 | length -= 1 41 | 42 | # add the new letter in the dictionary 43 | letters[s[i]] = 1 44 | length += 1 45 | 46 | # check if this length is the longest one 47 | longest = max(longest, length) 48 | 49 | return longest 50 | 51 | 52 | ########### 53 | # Testing # 54 | ########### 55 | 56 | # Test 1 57 | # Correct result => 3 58 | print(longest_substring_with_distinct_characters('abcba', 2)) 59 | 60 | # Test 2 61 | # Correct result => 8 62 | print(longest_substring_with_distinct_characters('abcbcbcbba', 2)) -------------------------------------------------------------------------------- /Hashing DS/longest_substring_without_repeating_characters.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Substring Without Repeating Characters 3 | 4 | Given a string, find the length of the longest substring without repeating characters. 5 | 6 | Input: 'abcabcbb' 7 | Output: 3 8 | Output explanation: The answer is 'abc', with the length of 3. 9 | 10 | Input: 'bbbbb' 11 | Output: 1 12 | Output explanation: The answer is 'b', with the length of 1. 13 | 14 | ========================================= 15 | Simple string iteration, use hashset to save unique characters. 16 | If the current character exists in the set then move the left index till the one 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def length_of_longest_substring(s): 27 | unique_chars = set() 28 | max_length = 0 29 | left = 0 30 | n = len(s) 31 | 32 | for i in range(n): 33 | while s[i] in unique_chars: 34 | # remove till the current char is unique 35 | unique_chars.remove(s[left]) 36 | left += 1 37 | 38 | # in this moment you're sure that the current char is unique 39 | unique_chars.add(s[i]) 40 | max_length = max(max_length, i - left + 1) 41 | 42 | return max_length 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Test 1 50 | # Correct result => 3 51 | print(length_of_longest_substring('abcabcbb')) 52 | 53 | # Test 2 54 | # Correct result => 1 55 | print(length_of_longest_substring('bbbbb')) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Meto Trajkovski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Linked Lists/add_two_numbers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Add Two Numbers 3 | 4 | You are given two non-empty linked lists representing two non-negative integers. 5 | The digits are stored in reverse order and each of their nodes contain a single digit. 6 | Add the two numbers and return it as a linked list. 7 | You may assume the two numbers do not contain any leading zero, except the number 0 itself. 8 | 9 | Input: 2 -> 4 -> 3, 5 -> 6 -> 4 10 | Output: 7 -> 0 -> 8 11 | Output explanation: 342 + 465 = 807 12 | 13 | ========================================= 14 | Iterate LL and add values on same position (just like adding real numbers). 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | # import ListNode class from ll_helpers.py 25 | from ll_helpers import ListNode 26 | 27 | def add_two_numbers(l1, l2): 28 | start = ListNode(None) 29 | # use the same linked list as result so the Space complexity will be O(1) 30 | start.next = l1 31 | pointer = start 32 | transfer = 0 33 | 34 | while (l1 is not None) or (l2 is not None) or (transfer != 0): 35 | v1 = 0 36 | if l1 is not None: 37 | v1 = l1.val 38 | l1 = l1.next 39 | 40 | v2 = 0 41 | if l2 is not None: 42 | v2 = l2.val 43 | l2 = l2.next 44 | 45 | total = transfer + v1 + v2 46 | transfer = total // 10 47 | 48 | if l1 is None: 49 | # if the first list is shorter than the second, add new elements at the end 50 | pointer.next = ListNode(None) 51 | pointer = pointer.next 52 | pointer.val = total % 10 53 | 54 | return start.next 55 | 56 | 57 | ########### 58 | # Testing # 59 | ########### 60 | 61 | # import build_ll and print_ll methods from ll_helpers.py 62 | from ll_helpers import build_ll, print_ll 63 | 64 | # Test 1 65 | # Correct result => 7 -> 0 -> 8 66 | ll1 = build_ll([2, 4, 3]) 67 | ll2 = build_ll([5, 6, 4]) 68 | print_ll(add_two_numbers(ll1, ll2)) 69 | 70 | # Test 2 71 | # Correct result => 8 -> 9 -> 0 -> 0 -> 1 72 | ll1 = build_ll([9, 9, 9, 9]) 73 | ll2 = build_ll([9, 9]) 74 | print_ll(add_two_numbers(ll1, ll2)) -------------------------------------------------------------------------------- /Linked Lists/intersecting_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Intersecting Linked Lists 3 | 4 | Given two singly linked lists that intersect at some point, find the intersecting node. The lists are non-cyclical. 5 | In this example, assume nodes with the same value are the exact same node objects. 6 | 7 | Input: 3 -> 7 -> 8 -> 10, 99 -> 1 -> 8 -> 10 8 | Output: 8 9 | 10 | ========================================= 11 | Find the longer linked list and move the pointer (now both list will have same number of elements). 12 | After that move both pointers from the both lists and compare elements. 13 | Time Complexity: O(N + M) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | # import ListNode class from ll_helpers.py 23 | from ll_helpers import ListNode 24 | 25 | def find_intersecting_node(ll1, ll2): 26 | # count how many nodes contains the first ll 27 | count1 = 0 28 | temp1 = ll1 29 | while (temp1 is not None): 30 | count1 += 1 31 | temp1 = temp1.next 32 | 33 | # count how many nodes contains the second ll 34 | count2 = 0 35 | temp2 = ll2 36 | while (temp2 is not None): 37 | count2 += 1 38 | temp2 = temp2.next 39 | 40 | # move only one of the lls for the difference 41 | m = min(count1, count2) 42 | 43 | for i in range(count1 - m): 44 | ll1 = ll1.next 45 | 46 | for i in range(count2 - m): 47 | ll2 = ll2.next 48 | 49 | # find the intersecting node 50 | intersect = None 51 | while ll1 is not None: 52 | # if the values are different, this is not the intersecting node 53 | if (ll1.val != ll2.val): 54 | intersect = None 55 | else: 56 | # if the values are equal and there is no an intersecting node from before 57 | # then this is the intersecting node 58 | if (intersect == None): 59 | intersect = ll1 60 | 61 | ll1 = ll1.next 62 | ll2 = ll2.next 63 | 64 | return intersect 65 | 66 | 67 | ########### 68 | # Testing # 69 | ########### 70 | 71 | # import build_ll method from ll_helpers.py 72 | from ll_helpers import build_ll 73 | 74 | # Test 1 75 | # Correct result => 8 76 | ll1 = build_ll([3, 7, 8, 10]) 77 | ll2 = build_ll([1, 8, 10]) 78 | print(find_intersecting_node(ll1, ll2).val) -------------------------------------------------------------------------------- /Linked Lists/is_ascending_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ascending Linked List 3 | 4 | Determine whether the sequence of items is ascending so that its each element is strictly larger 5 | than (and not merely equal to) the element that precedes it. Return True if that is the case, and 6 | return False otherwise. 7 | 8 | Input: -5 -> 10 -> 99 -> 123456 9 | Output: True 10 | 11 | ========================================= 12 | Iterate node by node and compare the current value with the next value. 13 | If the next node is smaller or equal return false. 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | # import ListNode class from ll_helpers.py 24 | from ll_helpers import ListNode 25 | 26 | def is_ascending_ll(ll): 27 | while ll.next != None: 28 | if ll.val >= ll.next.val: 29 | return False 30 | ll = ll.next 31 | 32 | return True 33 | 34 | 35 | ########### 36 | # Testing # 37 | ########### 38 | 39 | # import build_ll method from ll_helpers.py 40 | from ll_helpers import build_ll 41 | 42 | # Test 1 43 | # Correct result => True 44 | print(is_ascending_ll(build_ll([-5, 10, 99, 123456]))) 45 | 46 | # Test 2 47 | # Correct result => False 48 | print(is_ascending_ll(build_ll([2, 3, 3, 4, 5]))) -------------------------------------------------------------------------------- /Linked Lists/ll_helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers used in all linked list solutions. 3 | This file is created to avoid duplicate code in all linked list solutions. 4 | ''' 5 | 6 | class ListNode: 7 | def __init__(self, x, n = None): 8 | '''Definition for singly-linked list.''' 9 | self.val = x 10 | self.next = n 11 | 12 | def build_ll(arr): 13 | '''Builds a linked list from array. Used for testing.''' 14 | res = ListNode(None) 15 | pt = res 16 | 17 | for num in arr: 18 | pt.next = ListNode(num) 19 | pt = pt.next 20 | 21 | return res.next 22 | 23 | def print_ll(head): 24 | '''Prints a linked list in this format: x -> y -> z. Used for testing.''' 25 | res = [] 26 | 27 | while head != None: 28 | res.append(str(head.val)) 29 | head = head.next 30 | 31 | print(' -> '.join(res)) -------------------------------------------------------------------------------- /Linked Lists/max_difference_subll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Maximum Difference Sub-Linked List 3 | 4 | Given a linked list of integers, find and return the sub-linked list of k consecutive elements where 5 | the difference between the smallest element and the largest element is the largest possible. 6 | If there are several sub-linked lists of k elements in items so that all these sub-linked list have 7 | the same largest possible difference, return the sub-linked list that occurs first. 8 | 9 | Input: 42 -> 17 -> 99 -> 12 -> 65 -> 77 -> 11 -> 26, 5 10 | Output: 99 -> 12 -> 65 -> 77 -> 11 11 | 12 | ========================================= 13 | Using 2 pointers (start and end), traverse the linked list and compare the results. 14 | But first, move the end pointer for k places. 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | # import ListNode class from ll_helpers.py 25 | from ll_helpers import ListNode 26 | 27 | def max_diference_subll(ll, k): 28 | if ll is None: 29 | return None 30 | 31 | start, end = ll, ll 32 | 33 | # move the end pointer for k-1 places 34 | for i in range(1, k): 35 | end = end.next 36 | if end is None: 37 | return None 38 | 39 | result_start, result_end = start, end 40 | 41 | while end is not None: 42 | # compare the result with the current sub-linked list 43 | if abs(result_start.val - result_end.val) < abs(start.val - end.val): 44 | result_start, result_end = start, end 45 | 46 | # move the both pointers 47 | start = start.next 48 | end = end.next 49 | 50 | # cut the original linked list 51 | result_end.next = None 52 | return result_start 53 | 54 | 55 | ########### 56 | # Testing # 57 | ########### 58 | 59 | # import build_ll and print_ll methods from ll_helpers.py 60 | from ll_helpers import build_ll, print_ll 61 | 62 | # Test 1 63 | # Correct result => 99 -> 12 -> 65 -> 77 -> 11 64 | print_ll(max_diference_subll(build_ll([42, 17, 99, 12, 65, 77, 11, 26]), 5)) 65 | 66 | # Test 2 67 | # Correct result => 14 -> 58 -> 11 -> 63 -> 77 68 | print_ll(max_diference_subll(build_ll([36, 14, 58, 11, 63, 77, 46, 32, 87]), 5)) -------------------------------------------------------------------------------- /Linked Lists/merge_sorted_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Merge Sorted Linked Lists 3 | 4 | Input: 1 -> 2 -> 4, 1 -> 3 -> 4 5 | Output: 1 -> 1 -> 2 -> 3 -> 4 -> 4 6 | 7 | ========================================= 8 | Simple solution with pointers manipulation (just change the pointers of the old nodes, if there is smaller node than the old one). 9 | Time Complexity: O(N + M) 10 | Space Complexity: O(1) - working with the same old nodes (no extra space) 11 | ''' 12 | 13 | 14 | ############ 15 | # Solution # 16 | ############ 17 | 18 | # import ListNode class from ll_helpers.py 19 | from ll_helpers import ListNode 20 | 21 | def merge_two_sorted_ll(l1, l2): 22 | result = ListNode(-1) 23 | pointer = result 24 | 25 | while (l1 is not None) and (l2 is not None): 26 | if l1.val < l2.val: 27 | pointer.next = l1 28 | l1 = l1.next 29 | else: 30 | pointer.next = l2 31 | l2 = l2.next 32 | 33 | pointer = pointer.next 34 | 35 | if l1 is not None: 36 | pointer.next = l1 37 | 38 | if l2 is not None: 39 | pointer.next = l2 40 | 41 | return result.next 42 | 43 | 44 | ########### 45 | # Testing # 46 | ########### 47 | 48 | from .testing_ll import build_ll, print_ll 49 | 50 | # Test 1 51 | # Correct result => 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 52 | a = build_ll([1, 2, 3, 4, 5]) 53 | b = build_ll([6, 7, 8, 9]) 54 | print_ll(merge_two_sorted_ll(a, b)) 55 | 56 | # Test 2 57 | # Correct result => 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 58 | a = build_ll([1, 3, 5]) 59 | b = build_ll([2, 4, 6, 7]) 60 | print_ll(merge_two_sorted_ll(a, b)) 61 | 62 | # Test 3 63 | # Correct result => 1 -> 1 -> 2 -> 3 -> 4 -> 4 64 | a = build_ll([1, 2, 4]) 65 | b = build_ll([1, 3, 4]) 66 | print_ll(merge_two_sorted_ll(a, b)) -------------------------------------------------------------------------------- /Linked Lists/odd_even_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Odd Even Linked List 3 | 4 | Given a singly linked list, group all odd nodes together followed by the even nodes. 5 | Please note here we are talking about the node number and not the value in the nodes. 6 | The first node is considered odd, the second node even and so on ... 7 | 8 | Input: 1 -> 2 -> 3 -> 4 -> 5 9 | Output: 1 -> 3 -> 5 -> 2 -> 4 10 | 11 | Input: 2 -> 1 -> 3 -> 5 -> 6 -> 4 -> 7 12 | Output: 2 -> 3 -> 6 -> 7 -> 1 -> 5 -> 4 13 | 14 | ========================================= 15 | Count the index of the node and add it to the odd or even linked list (without creating new nodes). 16 | Time Complexity: O(N) 17 | Space Complexity: O(1) 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | # import ListNode class from ll_helpers.py 26 | from ll_helpers import ListNode 27 | 28 | def odd_even_ll(head): 29 | odd = ListNode(None) 30 | oddPointer = odd 31 | 32 | even = ListNode(None) 33 | evenPointer = even 34 | 35 | i = 1 36 | while head is not None: 37 | if i % 2 == 1: 38 | oddPointer.next = head 39 | oddPointer = oddPointer.next 40 | else: 41 | evenPointer.next = head 42 | evenPointer = evenPointer.next 43 | 44 | head = head.next 45 | i += 1 46 | 47 | evenPointer.next = None 48 | oddPointer.next = even.next 49 | 50 | return odd.next 51 | 52 | 53 | ########### 54 | # Testing # 55 | ########### 56 | 57 | # import build_ll and print_ll methods from ll_helpers.py 58 | from ll_helpers import build_ll, print_ll 59 | 60 | # Test 1 61 | # Correct result => 1 -> 3 -> 5 -> 2 -> 4 62 | print_ll(odd_even_ll(build_ll([1, 2, 3, 4, 5]))) 63 | 64 | # Test 2 65 | # Correct result => 2 -> 3 -> 6 -> 7 -> 1 -> 5 -> 4 66 | print_ll(odd_even_ll(build_ll([2, 1, 3, 5, 6, 4, 7]))) -------------------------------------------------------------------------------- /Linked Lists/remove_duplicates_sorted_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Remove Duplicates from Sorted Linked List 3 | 4 | Given a sorted linked list nums, remove the duplicates in-place such that each element appear only once and return the modified linked list. 5 | Do not allocate extra space for another linked list, you must do this by modifying the input linked list in-place with O(1) extra memory. 6 | 7 | Input: 1 -> 1 -> 2 8 | Output: 1 -> 2 9 | 10 | Input: 0 -> 0 -> 1 -> 1 -> 1 -> 2 -> 2 -> 3 -> 3 -> 4 11 | Output: 0 -> 1 -> 2 -> 3 -> 4 12 | 13 | ========================================= 14 | Iterate the linked list and jump the neighbouring duplicates (change the next pointer). 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | # import ListNode class from ll_helpers.py 25 | from ll_helpers import ListNode 26 | 27 | def remove_duplicates(nums): 28 | if nums is None: 29 | return nums 30 | pointer = nums 31 | 32 | while pointer.next is not None: 33 | if pointer.val == pointer.next.val: 34 | # skip the next value because it's a duplicate 35 | pointer.next = pointer.next.next 36 | else: 37 | # search next 38 | pointer = pointer.next 39 | 40 | return nums 41 | 42 | 43 | ########### 44 | # Testing # 45 | ########### 46 | 47 | # import build_ll and print_ll methods from ll_helpers.py 48 | from ll_helpers import build_ll, print_ll 49 | 50 | # Test 1 51 | # Correct result => 1 -> 2 52 | print_ll(remove_duplicates(build_ll([1, 1, 2]))) 53 | 54 | # Test 2 55 | # Correct result => 0 -> 1 -> 2 -> 3 -> 4 56 | print_ll(remove_duplicates(build_ll([0, 0, 1, 1, 1, 2, 2, 3, 3, 4]))) -------------------------------------------------------------------------------- /Linked Lists/remove_element_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Remove Element 3 | 4 | Given a linked list nums and a value val, remove all instances of that value in-place and return the new linked list. 5 | Do not allocate extra space for another linked list, you must do this by modifying the input linked list in-place with O(1) extra memory. 6 | 7 | Input: 3 -> 2 -> 2 -> 3 8 | Output: 2 -> 2 9 | 10 | Input: 0 -> 1 -> 2 -> 2 -> 3 -> 0 -> 4 -> 2 11 | Output: 0 -> 1 -> 3 -> 0 -> 4 12 | 13 | ========================================= 14 | Iterate the linked list and jump the values that needs to be deleted (change the next pointer). 15 | Time Complexity: O(N) 16 | Space Complexity: O(1) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | # import ListNode class from ll_helpers.py 25 | from ll_helpers import ListNode 26 | 27 | def remove_element(nums, val): 28 | res = ListNode(0) 29 | res.next = nums 30 | pointer = res 31 | 32 | while pointer.next is not None: 33 | if pointer.next.val == val: 34 | # skip the next value because it's value that needs to be deleted 35 | pointer.next = pointer.next.next 36 | else: 37 | # search next 38 | pointer = pointer.next 39 | 40 | return res.next 41 | 42 | 43 | ########### 44 | # Testing # 45 | ########### 46 | 47 | # import build_ll and print_ll methods from ll_helpers.py 48 | from ll_helpers import build_ll, print_ll 49 | 50 | # Test 1 51 | # Correct result => 2 -> 2 52 | print_ll(remove_element(build_ll([3, 2, 2, 3]), 3)) 53 | 54 | # Test 2 55 | # Correct result => 0 -> 1 -> 3 -> 0 -> 4 56 | print_ll(remove_element(build_ll([0, 1, 2, 3, 0, 4, 2]), 2)) -------------------------------------------------------------------------------- /Linked Lists/remove_nth_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Remove Nth Node From End of List 3 | 4 | Given a linked list, remove the n-th node from the end of list and return its head. 5 | 6 | Input: 1 -> 2 -> 3 -> 4 -> 5, 2. 7 | Output: 1 -> 2 -> 3 -> 5 8 | 9 | ========================================= 10 | Playing with the pointers. 11 | Time Complexity: O(N) 12 | Space Complexity: O(1) 13 | Recursive solution. 14 | Time Complexity: O(N) 15 | Space Complexity: O(N) , because of the recursive calls stack 16 | ''' 17 | 18 | 19 | ############## 20 | # Solution 1 # 21 | ############## 22 | 23 | # import ListNode class from ll_helpers.py 24 | from ll_helpers import ListNode 25 | 26 | def remove_nth_from_end_1(head, n): 27 | helper = ListNode(0) 28 | helper.next = head 29 | 30 | first = helper 31 | second = helper 32 | 33 | # count to N with the first pointer 34 | for i in range(n + 1): 35 | first = first.next 36 | 37 | # go (Length - N) elements with first pointer 38 | # and in that way the second pointer will be Nth from the end 39 | while first != None: 40 | first = first.next 41 | second = second.next 42 | 43 | # remove the element (change the next pointer from the previous element) 44 | second.next = second.next.next 45 | 46 | return helper.next 47 | 48 | 49 | ############## 50 | # Solution 2 # 51 | ############## 52 | 53 | def remove_nth_from_end_2(head, n): 54 | result = remove_recursively(head, n) 55 | if result[0] == n: 56 | return head.next 57 | return head 58 | 59 | def remove_recursively(pointer, n): 60 | if pointer is None: 61 | return (0, None) 62 | 63 | # go to the end and count how many are there 64 | result = remove_recursively(pointer.next, n) 65 | 66 | if result[0] == n: 67 | pointer.next = result[1] 68 | 69 | return (result[0] + 1, pointer.next) -------------------------------------------------------------------------------- /Linked Lists/reverse_ll.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse a linked list 3 | 4 | Reverse a linked list in one iteration without using additional space. 5 | 6 | Input: 1 -> 2 -> 3 -> 4 7 | Output: 4 -> 3 -> 2 -> 1 8 | 9 | ========================================= 10 | Iterate LL and change the pointer of the current nodes to point to the previous nodes. 11 | Time Complexity: O(N) 12 | Space Complexity: O(1) 13 | Solution 2: Same approach using recursion. 14 | Time Complexity: O(N) 15 | Space Complexity: O(N) , because of the recursion stack (the stack will be with N depth till the last node of the linked list is reached) 16 | ''' 17 | 18 | 19 | ############## 20 | # Solution 1 # 21 | ############## 22 | 23 | # import ListNode class from ll_helpers.py 24 | from ll_helpers import ListNode 25 | 26 | def reverse_ll(ll): 27 | prev_node = None 28 | 29 | while ll is not None: 30 | # save the current node 31 | current = ll 32 | # go to the next node 33 | ll = ll.next 34 | 35 | # change the pointer of the current node to point to the previous node 36 | current.next = prev_node 37 | # save the current node for the next iteration 38 | prev_node = current 39 | 40 | return prev_node 41 | 42 | 43 | ############## 44 | # Solution 2 # 45 | ############## 46 | 47 | def reverse_ll_2(ll): 48 | return reverse(ll, None) 49 | 50 | def reverse(node, prev_node): 51 | if node is None: 52 | # the end of the ll is reached, return the previous node 53 | # that'll be the first node in the reversed ll 54 | return prev_node 55 | 56 | # send node.next as current node and node as previous node in the next step 57 | result = reverse(node.next, node) 58 | # change the pointer of the current node to point to the previous node 59 | node.next = prev_node 60 | 61 | return result 62 | 63 | 64 | ########### 65 | # Testing # 66 | ########### 67 | 68 | # import build_ll and print_ll methods from ll_helpers.py 69 | from ll_helpers import build_ll, print_ll 70 | 71 | # Test 1 72 | # Correct result => 4 -> 3 -> 2 -> 1 73 | print_ll(reverse_ll(build_ll([1, 2, 3, 4]))) 74 | print_ll(reverse_ll_2(build_ll([1, 2, 3, 4]))) -------------------------------------------------------------------------------- /Math/calculate_area_of_polygon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Calculate Area of Polygon 3 | 4 | Given ordered coordinates of a polygon with n vertices. Find area of the polygon. 5 | Here ordered mean that the coordinates are given either in clockwise manner or anticlockwise from first vertex to last. 6 | 7 | Input: [(0, 0), (3, 0), (3, 2), (0, 2)] 8 | Output: 6.0 9 | Output explanation: The polygon is a 3x2 rectangle parallel with the X axis. The area is 6 (3*2). 10 | 11 | ========================================= 12 | Use Shoelace formula (https://en.wikipedia.org/wiki/Shoelace_formula). 13 | abs( 1/2 ((X1Y2 + X2Y3 + ... + Xn-1Yn + XnY1) - (X2Y1 + X3Y2 + ... + XnYn-1 + X1Yn)) ) 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def calculate_area_of_polygon(polygon): 24 | n = len(polygon) 25 | prev = polygon[-1] 26 | area = 0 27 | 28 | for curr in polygon: 29 | area += (prev[0] + curr[0]) * (prev[1] - curr[1]) 30 | prev = curr 31 | 32 | return abs(area / 2) # return absolute value 33 | 34 | 35 | ########### 36 | # Testing # 37 | ########### 38 | 39 | # Test 1 40 | # Correct result => 6.0 41 | print(calculate_area_of_polygon([(0, 0), (3, 0), (3, 2), (0, 2)])) -------------------------------------------------------------------------------- /Math/check_if_two_rectangles_overlap.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Check if Two Rectangles Overlap 3 | 4 | Given two rectangles, find if the given two rectangles overlap or not. 5 | Note that a rectangle can be represented by two coordinates, top left and bottom right. 6 | So mainly we are given following four coordinates (min X and Y and max X and Y). 7 | - l1: Bottom Left coordinate of first rectangle. (mins) 8 | - r1: Top Right coordinate of first rectangle. (maxs) 9 | - l2: Bottom Left coordinate of second rectangle. (mins) 10 | - r2: Top Right coordinate of second rectangle. (maxs) 11 | It may be assumed that the rectangles are PARALLEL to the coordinate axis. 12 | 13 | Input: (0, 0), (3, 2), (1, 1), (5, 4) 14 | Output: True 15 | 16 | ========================================= 17 | First check if rectangles are overlapping on X axis and 18 | after that if they are overlapping on Y axis. 19 | Time Complexity: O(1) 20 | Space Complexity: O(1) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def check_if_two_rectangles_overlap(l1, r1, l2, r2): 29 | # first check by X coordinates, if rectangles can overlap on X axis 30 | # longer form (l1[0] < l2[0] and r1[0] < l2[0]) or (l1[0] > r2[0] and r1[0] > r2[0]) 31 | # but we know that l1[0] is always smaller than r1[0] 32 | if (r1[0] < l2[0]) or (l1[0] > r2[0]): 33 | return False 34 | 35 | # now we know that the rectangles are overlapping on X axis 36 | # check if they are overlapping on Y axis 37 | # (use the same logic from previous) 38 | if (r1[1] < l2[1]) or (l1[1] > r2[1]): 39 | return False 40 | 41 | return True 42 | 43 | 44 | ########### 45 | # Testing # 46 | ########### 47 | 48 | # Test 1 49 | # Correct result => True 50 | print(check_if_two_rectangles_overlap((0, 0), (3, 2), (1, 1), (5, 4))) 51 | 52 | # Test 2 53 | # Correct result => True 54 | print(check_if_two_rectangles_overlap((0, 0), (3, 2), (3, 2), (5, 4))) 55 | 56 | # Test 3 57 | # Correct result => True 58 | print(check_if_two_rectangles_overlap((0, 0), (3, 2), (1, -1), (5, 4))) 59 | 60 | # Test 4 61 | # Correct result => False 62 | print(check_if_two_rectangles_overlap((0, 0), (3, 2), (2, 3), (5, 4))) -------------------------------------------------------------------------------- /Math/count_divisibles_in_range.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Count Divisibles in Range 3 | 4 | Let us take a breather and tackle a problem so simple that its solution needs only a couple of 5 | conditions, but not even any loops, let alone anything even more fancy. The difficulty is coming up 6 | with the conditions that cover all possible cases of this problem exactly right, including all of the 7 | potentially tricksy edge and corner cases, and not be off-by-one. Given three integers start, end 8 | and n so that start <= end, count and return how many integers between start and end, 9 | inclusive, are divisible by n. 10 | 11 | Input: 7, 28, 4 12 | Output: 6 13 | 14 | ========================================= 15 | Find the close divisible to start (the smallest divisible in the range), calculate the difference between 16 | that number and the end of the range, and in the end divide the difference by N. 17 | Time Complexity: O(1) 18 | Space Complexity: O(1) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def count_divisibles_in_range(start, end, n): 27 | # find the next start number divisable by n 28 | start += (n - (start % n)) % n 29 | 30 | if start > end: 31 | # in this case there are no numbers divisable by n 32 | return 0 33 | 34 | return 1 + ((end - start) // n) 35 | 36 | 37 | ########### 38 | # Testing # 39 | ########### 40 | 41 | # Test 1 42 | # Correct result => 6 43 | print(count_divisibles_in_range(7, 28, 4)) 44 | 45 | # Test 2 46 | # Correct result => 9 47 | print(count_divisibles_in_range(-77, 19, 10)) 48 | 49 | # Test 3 50 | # Correct result => 0 51 | print(count_divisibles_in_range(-19, -13, 10)) 52 | 53 | # Test 4 54 | # Correct result => 199999999999 55 | print(count_divisibles_in_range(1, 10**12 - 1, 5)) 56 | 57 | # Test 5 58 | # Correct result => 200000000000 59 | print(count_divisibles_in_range(0, 10**12 - 1, 5)) 60 | 61 | # Test 6 62 | # Correct result => 200000000001 63 | print(count_divisibles_in_range(0, 10**12, 5)) -------------------------------------------------------------------------------- /Math/estimate_pi.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Estimation of Pi 3 | 4 | Write a program to compute the value of PI using a random number generator/method. 5 | 6 | 7 | ========================================= 8 | To solve this problem we'll use the Monte Carlo simulation/method. 9 | Generate N random points (0 <= X, Y <= 1) in the first quadrant. 10 | Count all points that are inside the circle using the squared euclidean distance (between origin <0,0> and point ). 11 | The ratio between all points in the quarter circle and quarter square should be 12 | approximately equal to the ratio between a quarter of the circle area and a quarter of the square area. 13 | (more points = better estimation) 14 | Equation: (((r^2)*PI)/4) / (((2*r)^2)/4) = circle_points / total_points 15 | Solve the first part: (((r^2)*PI)/4) / (((2*r)^2)/4) = ((1^2)*PI) / ((2*1)^2) = (1*PI) / (2^2) = PI/4 16 | Simple equation: PI / 4 = circle_points / total_points 17 | Final form: PI = 4 * circle_points / total_points 18 | Time Complexity: O(N) 19 | Space Complexity: O(1) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | from random import random 28 | 29 | def estimate_pi(n): 30 | total_points = 0 31 | circle_points = 0 32 | 33 | for i in range(n): 34 | # generate N random points in the first quadrant 35 | x, y = random(), random() 36 | 37 | if x*x + y*y <= 1: 38 | # using squared euclidean distance find the distance from (0, 0) to (x, y) 39 | circle_points += 1 40 | total_points += 1 41 | 42 | # this formula is a short form of this: quarter_circle_area / quarter_square_area = circle_points / total_points 43 | return 4 * circle_points / total_points 44 | 45 | 46 | ########### 47 | # Testing # 48 | ########### 49 | 50 | # Test 1 51 | # Correct result => Doesn't give a good estimation at all (often the integer part is wrong) 52 | print(estimate_pi(10)) 53 | 54 | # Test 2 55 | # Correct result => Gives a good estimation to the first decimal (3.1xxx) 56 | print(estimate_pi(10000)) 57 | 58 | # Test 3 59 | # Correct result => Gives a good estimation to the second decimal (3.14xxx) 60 | print(estimate_pi(10000000)) -------------------------------------------------------------------------------- /Math/factorial_trailing_zeroes.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Factorial Trailing Zeroes 3 | 4 | Given an integer n, return the number of trailing zeroes in n!. 5 | 6 | Note: Your solution should be in logarithmic time complexity. 7 | 8 | Input: 3 9 | Output: 0 10 | Output explanation: 3! = 6, no trailing zero. 11 | 12 | Input: 5 13 | Output: 1 14 | Output explanation: 5! = 120, one trailing zero. 15 | 16 | ========================================= 17 | Find how many 5s are in range 0-N (more explanation in the solution). 18 | Time Complexity: O(logN) 19 | Space Complexity: O(1) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def trailing_zeroes(n): 28 | # 0s are produced when 2 and 5 are multiplied 29 | # because 2 * 5 = 10 30 | # so you'll need to count how many 2s and 5s are there 31 | # 2s are always more than 5s 32 | # so count just how many 5s are in that range 33 | res = 0 34 | k = 5 35 | 36 | # find all powers of 5 37 | # 25 has 2 5s, 125 has 3 5s, etc 38 | while k <= n: 39 | res += n // k 40 | k *= 5 41 | 42 | return res -------------------------------------------------------------------------------- /Math/odd_sum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Odd Sum 3 | 4 | For a given range [а,b], find the sum of all odd numbers between a and b. 5 | 6 | Input: 3, 9 7 | Output: 24 8 | Output explanation: 3+5+7+9=24 9 | 10 | ========================================= 11 | Several different O(1) approaches exist. This is the explanation of my solution/formula. 12 | 3 + 5 + 7 + 9 can be written like (3 + 0) + (3 + 2) + (3 + 4) + (3 + 6) 13 | that's (3 * 4) + (2 + 4 + 6), also this can be written like 14 | (3 * 4) + ((2 * 1) + (2 * 2) + (2 * 3)) = 3 * 4 + 2 * (1 + 2 + 3) 15 | And the formula is: Min_Odd * Num_Odds + 2 * Sum(Num_Odds) 16 | Sum formula is N*(N-1)/2. (for all numbers smaller than N) 17 | This is the simplest formula: 18 | Min_Odd * Num_Odds + 2 * Num_Odds * (Num_Odds - 1) / 2 = 19 | Num_Odds * (Min_Odd + Num_Odds - 1) 20 | Time Complexity: O(1) 21 | Space Complexity: O(1) 22 | ''' 23 | 24 | 25 | ############ 26 | # Solution # 27 | ############ 28 | 29 | def odd_sum(a, b): 30 | # find first odd number 31 | if a % 2 == 0: 32 | a += 1 33 | # to avoid rounding (math.ceil) find the biggest even number 34 | if b % 2 == 1: 35 | b += 1 36 | # count of odd numbers 37 | n = (b - a + 1) // 2 38 | # use the formula from the description 39 | return n * (a + n - 1) 40 | 41 | 42 | ########### 43 | # Testing # 44 | ########### 45 | 46 | # Test 1 47 | # Correct result => 24 48 | print(odd_sum(3, 9)) -------------------------------------------------------------------------------- /Math/prime_factors.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Prime Factors of Integers 3 | 4 | As the fundamental theorem of arithmetic again reminds us, every positive integer can be broken 5 | down into the product of its prime factors exactly one way, disregarding the order of listing these 6 | factors. Given positive integer n > 1, return the list of its prime factors in sorted ascending order, 7 | each prime factor included in the list as many times as it appears in the prime factorization of n. 8 | 9 | Input: 42 10 | Output: [2, 3, 7] 11 | 12 | ========================================= 13 | While n is divisible by 2, save all 2 factors and divide n by 2. 14 | Now n is odd, so you won't need to check if divisible by some even number, because of that starting from 3 15 | jump by 2 numbers. 16 | Time Complexity: O(N) , if prime number then N/2 checks, if all prime factors are 2 then LogN checks 17 | Space Complexity: O(LogN) , if all prime factors are 2, else less than LogN space 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | def prime_factors(n): 26 | factors = [] 27 | 28 | while n % 2 == 0: 29 | factors.append(2) 30 | n //= 2 31 | 32 | # now n is odd 33 | i = 3 34 | while i * i <= n: 35 | while n % i == 0: 36 | factors.append(i) 37 | n //= i 38 | 39 | # increase by 2, no need to check if divisbile by even numbers 40 | i += 2 41 | 42 | if n > 2: 43 | factors.append(n) 44 | 45 | return factors 46 | 47 | 48 | ########### 49 | # Testing # 50 | ########### 51 | 52 | # Test 1 53 | # Correct result => [2, 3, 7] 54 | print(prime_factors(42)) 55 | 56 | # Test 2 57 | # Correct result => [2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5] 58 | print(prime_factors(10**6)) 59 | 60 | # Test 3 61 | # Correct result => [127, 9721] 62 | print(prime_factors(1234567)) -------------------------------------------------------------------------------- /Math/smallest_multiple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Smallest multiple 3 | 4 | 2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. 5 | What is the smallest positive number that is evenly divisible by all of the numbers from A to B? 6 | 7 | ========================================= 8 | The solution is the least common multiple for more than 2 numbers (in this case all numbers from "start" to "end") 9 | Time Complexity: O(N) , N = start - end, GCD complexity is O(Log min(a, b)) 10 | Space Complexity: O(1) 11 | ''' 12 | 13 | 14 | ############ 15 | # Solution # 16 | ############ 17 | 18 | def smallest_multiple(start, end): 19 | result = 1 20 | 21 | for k in range(start, end + 1): 22 | result = lcm(max(result, k), min(result, k)) 23 | 24 | return result 25 | 26 | # least common multiple 27 | def lcm(a, b): 28 | return a * b // gcd(a, b) 29 | 30 | # Greatest common divisor (euclidian algorithm, fast algorithm) 31 | # https://en.wikipedia.org/wiki/Euclidean_algorithm 32 | # For more than 2 numbers: gcd(a, b, c) = gcd(a, gcd(b, c)) or gcd(gcd(a, b), c) or gcd(gcd(a, c), b) 33 | def gcd(a, b): 34 | while b != 0: 35 | a, b = b, a % b 36 | return a 37 | 38 | ########### 39 | # Testing # 40 | ########### 41 | 42 | # Test 1 43 | # Correct result => 2520 44 | print(smallest_multiple(1, 10)) 45 | 46 | # Test 2 47 | # Correct result => 232792560 48 | print(smallest_multiple(1, 20)) -------------------------------------------------------------------------------- /Math/sum_of_multiples.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Multiples of a OR b 3 | 4 | If we list all the natural numbers below 10 that are multiples of 3 OR 5, we get 3, 5, 6 and 9. 5 | The sum of these multiples is 23. 6 | Find the sum of all the multiples of A or B below N. 7 | 8 | ========================================= 9 | Don't need iteration to solve this problem, you need to find only how many divisors are there. 10 | Example - 3 + 6 + 9 ... + N = (1 + 2 + 3 + ... N // 3) * 3 11 | Sum(K)*N = 1*N + 2*N + ... + (K-1)*N + K*N 12 | Use sum formula - (N * (N + 1))/2 13 | Time Complexity: O(1) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | ############ 18 | # Solution # 19 | ############ 20 | 21 | def sum_of_multiples_below(a, b, total): 22 | total -= 1 23 | # sum of dividens of A + sum of dividens of B - sum of common dividens (because they're added twice) 24 | return sum_of_dividends(total, a) + sum_of_dividends(total, b) - sum_of_dividends(total, a * b) 25 | 26 | def sum_of_dividends(total, divisor): 27 | n = total // divisor 28 | return (n * (n + 1) // 2) * divisor 29 | 30 | 31 | ########### 32 | # TESTING # 33 | ########### 34 | 35 | # Test 1 36 | # Correct result => 23 37 | print(sum_of_multiples_below(3, 5, 10)) 38 | 39 | # Test 2 40 | # Correct result => 233168 41 | print(sum_of_multiples_below(3, 5, 1000)) 42 | -------------------------------------------------------------------------------- /Math/total_divisible_numbers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Total Divisible Numbers 3 | 4 | Given an array A of N numbers, 5 | your task is to find how many numbers from 1 to S are divisible by all of the elements in the array. 6 | 7 | Input: [2, 4, 5], 45 8 | Output: 2 9 | Output explanation: 20 and 40 are divisible by all numbers in the array. 10 | 11 | ========================================= 12 | Find least common multiple of all numbers in the array (lcm can be found using gcd, (a * b)/gcd(a, b)). 13 | And in the end check how many numbers are divisble by the lcm number (smaller or equal to S). 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def total_divisible_numbers(arr, S): 24 | # find lcm for all numbers in the array 25 | lcm = 1 26 | for a in arr: 27 | lcm = (a * lcm) // gcd(a, lcm) 28 | 29 | # return the count of numbers divisble by the lcm number (smaller or equal to S) 30 | return S // lcm 31 | 32 | def gcd(a, b): 33 | while a != 0: 34 | a, b = b % a, a # "Pythonic way" 35 | # or temp = a; a = b % a; b = temp; in the other languages 36 | return b 37 | 38 | 39 | ########### 40 | # Testing # 41 | ########### 42 | 43 | # Test 1 44 | # Correct result => 4 45 | print(total_divisible_numbers([3, 5, 6], 146)) 46 | 47 | # Test 2 48 | # Correct result => 52 49 | print(total_divisible_numbers([3, 3, 2], 317)) 50 | 51 | # Test 3 52 | # Correct result => 30 53 | print(total_divisible_numbers([2, 3], 30)) -------------------------------------------------------------------------------- /Math/unique_paths.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Unique Paths 3 | 4 | Find the unique paths in a matrix starting from the upper left corner and ending in the bottom right corner. 5 | 6 | ========================================= 7 | Dynamic programming (looking from the left and up neighbour), but this is a slower solution, see the next one. 8 | Time Complexity: O(N*M) 9 | Space Complexity: O(N*M) 10 | The DP table is creating an Pascal Triangle, so this problem can be easily solved by using the combinatorial formula! 11 | Much faster and doesn't use extra space. 12 | Time Complexity: O(min(M, N)) 13 | Space Complexity: O(1) 14 | ''' 15 | 16 | ################################ 17 | # Solution Dynamic Programming # 18 | ################################ 19 | 20 | def unique_paths_dp(n, m): 21 | # all values at i=0 should be 1 (the rest are not important, they'll be computed later) 22 | dp = [[1 for j in range(m)] for i in range(n)] 23 | 24 | # calculate only inner values 25 | for i in range(1, n): 26 | for j in range(1, m): 27 | dp[i][j] = dp[i][j - 1] + dp[i - 1][j] 28 | 29 | return dp[n-1][m-1] 30 | 31 | 32 | ################################# 33 | # Solution Combinations Formula # 34 | ################################# 35 | 36 | def unique_paths(n, m): 37 | m, n = min(m, n), max(m, n) 38 | lvl = m + n - 2 39 | pos = m - 1 40 | comb = 1 41 | 42 | # combinations formula C(N, R) = N! / R! * (N - R)! 43 | for i in range(1, pos + 1): 44 | comb *= lvl 45 | comb /= i 46 | lvl -= 1 47 | 48 | return int(comb + 0.001) # 0.001 just in case, because of overflow 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | # Correct result => 924 57 | n, m = 7, 7 58 | print(unique_paths(n, m)) 59 | print(unique_paths_dp(n, m)) 60 | 61 | # Test 2 62 | # Correct result => 28 63 | n, m = 7, 3 64 | print(unique_paths(n, m)) 65 | print(unique_paths_dp(n, m)) 66 | 67 | # Test 3 68 | # Correct result => 28 69 | n, m = 3, 7 70 | print(unique_paths(n, m)) 71 | print(unique_paths_dp(n, m)) -------------------------------------------------------------------------------- /Other/basic_calculator.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Basic Calculator 3 | 4 | Implement a basic calculator to evaluate a simple expression string. 5 | The expression string may contain open '(' and closing parentheses ')', 6 | the plus '+' or minus sign '-', non-negative integers and empty spaces ' '. 7 | 8 | Input: '(1+(4+5+2)-3)+(6+8)' 9 | Output: 23 10 | 11 | Input: ' 2-1 + 2 ' 12 | Output: 3 13 | 14 | ========================================= 15 | Start from the first character and respect the math rules. When brackets come, go inside the brackets 16 | and compute the inner result, after that continue with adding or subtracting. 17 | Time Complexity: O(N) 18 | Space Complexity: O(K) , much less than N (the deepest level of brackets) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def basic_calculator(s): 27 | return calculate(s, 0)[0] 28 | 29 | def calculate(s, i): 30 | sign = 1 # 1 means '+' and -1 means '-' 31 | res = 0 32 | num = 0 33 | 34 | while i < len(s) and s[i] != ')': 35 | if s[i] >= '0' and s[i] <= '9': 36 | # find the whole number 37 | num = num * 10 + int(s[i]) 38 | elif s[i] == '(': 39 | # calculate inside the brackets 40 | brackets = calculate(s, i + 1) 41 | res += brackets[0] * sign 42 | i = brackets[1] # continue from the new i 43 | elif s[i] != ' ': 44 | # add the previous number using the old sign 45 | res += num * sign 46 | num = 0 47 | 48 | if s[i] == '-': 49 | sign = -1 50 | elif s[i] == '+': 51 | sign = 1 52 | 53 | i += 1 54 | 55 | res += num * sign 56 | return (res, i) 57 | 58 | 59 | ########### 60 | # Testing # 61 | ########### 62 | 63 | # Test 1 64 | # Correct result => 23 65 | print(basic_calculator('(1+(4+5+2)-3)+(6+8)')) 66 | 67 | # Test 2 68 | # Correct result => 3 69 | print(basic_calculator(' 2-1 + 2 ')) -------------------------------------------------------------------------------- /Other/count_consecutive_sums.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Count Consecutive Sums 3 | 4 | Positive integers can be expressed as sums of consecutive positive integers in various ways. 5 | 6 | Input: 42 7 | Output: 4 8 | Output explanation: (a) 3 + 4 + 5 + 6 + 7 + 8 + 9, (b) 9 + 10 + 11 + 12, (c) 13 + 14 + 15 and (d) 42 9 | 10 | ========================================= 11 | Iterate all N elements and add each to the sum, but store the start element and if the current sum is 12 | bigger than N substract the front elements. 13 | Time Complexity: O(N) 14 | Space Complexity: O(1) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def count_consecutive_sums(n): 23 | start = 1 24 | curr_sum = count = 0 25 | 26 | for end in range(1, n + 1): 27 | curr_sum += end 28 | 29 | while curr_sum > n: 30 | # remove all numbers from the front 31 | curr_sum -= start 32 | start += 1 33 | 34 | if curr_sum == n: 35 | count += 1 36 | 37 | return count 38 | 39 | 40 | ########### 41 | # Testing # 42 | ########### 43 | 44 | # Test 1 45 | # Correct result => 4 46 | print(count_consecutive_sums(42)) 47 | 48 | # Test 2 49 | # Correct result => 6 50 | print(count_consecutive_sums(99)) 51 | 52 | # Test 3 53 | # Correct result => 2 54 | print(count_consecutive_sums(92)) -------------------------------------------------------------------------------- /Other/find_min_path.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find min path 3 | 4 | You are given an M by N matrix consisting of booleans that represents a board. 5 | Each True boolean represents a wall. Each False boolean represents a tile you can walk on. 6 | Given this matrix, a start coordinate, and an end coordinate, 7 | return the minimum number of steps required to reach the end coordinate from the start. 8 | If there is no possible path, then return null. You can move up, left, down, and right. 9 | You cannot move through walls. You cannot wrap around the edges of the board. 10 | 11 | Input: 12 | [[f, f, f, f], 13 | [t, t, f, t], 14 | [f, f, f, f], 15 | [f, f, f, f]] 16 | start = (3, 0) 17 | end = (0, 0) 18 | Output: 7 19 | Output explanation: Starting bottom left and ending top left, 20 | the minimum number of steps required to reach the end is 7, 21 | since we would need to go through (1, 2) because there is a wall everywhere else on the second row. 22 | 23 | ========================================= 24 | BFS solution using queue. 25 | Time Complexity: O(N * M) 26 | Space Complexity: O(N * M) 27 | ''' 28 | 29 | 30 | ############ 31 | # Solution # 32 | ############ 33 | 34 | from collections import deque 35 | 36 | def find_min_path(board, start, end): 37 | n = len(board) 38 | m = len(board[0]) 39 | 40 | # create a visited array 41 | visited = [[False for el in range(m)] for row in range(n)] 42 | 43 | queue = deque() 44 | queue.append((start, 0)) 45 | directions = [(1, 0), (0, 1), (-1, 0), (0, -1)] # up, right, down, left 46 | 47 | # simple bfs 48 | while queue: 49 | el = queue.popleft() 50 | position = el[0] 51 | steps = el[1] 52 | 53 | # check if this position is already visited 54 | if visited[position[0]][position[1]]: 55 | continue 56 | 57 | visited[position[0]][position[1]] = True 58 | 59 | # check if this position is walkable 60 | if board[position[0]][position[1]] == 't': 61 | continue 62 | 63 | # if the end was reached return steps 64 | if position == end: 65 | return steps 66 | 67 | newSteps = steps + 1 68 | 69 | # add all neighbours at the end of the queue 70 | for d in directions: 71 | x = position[0] + d[0] 72 | y = position[1] + d[1] 73 | 74 | if (x < n) and (x >= 0) and (y < m) and (y >= 0): 75 | queue.append(((x, y), newSteps)) 76 | 77 | # the path was not found 78 | return None 79 | 80 | 81 | ########### 82 | # Testing # 83 | ########### 84 | 85 | # Test 1 86 | # Correct result => 7 87 | f = 'f' 88 | t = 't' 89 | print(find_min_path([[f, f, f, f], [t, t, f, t], [f, f, f, f], [f, f, f, f]], (3, 0), (0, 0))) -------------------------------------------------------------------------------- /Other/generate_parentheses.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Generate Parentheses 3 | 4 | Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses. 5 | 6 | Input: 3 7 | Output: 8 | [ 9 | '((()))', 10 | '(()())', 11 | '(())()', 12 | '()(())', 13 | '()()()' 14 | ] 15 | 16 | ========================================= 17 | This problem could be solved in several ways (using stack, queue, or just a simple list - see letter_combinations.py), all of them have the same complexity. 18 | I'll solve it using simple recursive algorithm. 19 | Time Complexity: O(4^N) , O(2^(2*N)) = O(4^N) 20 | Space Complexity: O(4^N) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def generate_parentheses(n): 29 | result = [] 30 | if n == 0: 31 | return result 32 | 33 | combinations(result, n, n, '') 34 | 35 | return result 36 | 37 | 38 | def combinations(result, open_left, close_left, combination): 39 | if close_left == 0: 40 | # a new combination is created (no more open or close parentheses) 41 | result.append(combination) 42 | elif open_left == 0: 43 | # no more open parentheses, so all left parentheses must be closed (just add the missing close parentheses) 44 | result.append(combination + (')' * close_left)) 45 | else: 46 | combinations(result, open_left - 1, close_left, combination + '(') 47 | 48 | # check if there is a pair for this close parenthesis 49 | if open_left < close_left: 50 | combinations(result, open_left, close_left - 1, combination + ')') 51 | 52 | 53 | ########### 54 | # Testing # 55 | ########### 56 | 57 | # Test 1 58 | # Correct result => ['((()))', '(()())', '(())()', '()(())', '()()()'] 59 | print(generate_parentheses(3)) -------------------------------------------------------------------------------- /Other/jumping_numbers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Jumping numbers 3 | 4 | A number is called as a Jumping Number if all adjacent digits in it differ by 1. 5 | The difference between ‘9’ and ‘0’ is not considered as 1. 6 | All single digit numbers are considered as Jumping Numbers. 7 | For example 7, 8987 and 4343456 are Jumping numbers but 796 and 89098 are not. 8 | 9 | Input: 20 10 | Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12] 11 | 12 | ========================================= 13 | Make a tree (DFS way/backtracking), for each next digit take the last digit, go up and down 14 | (example: 123, last digit is 3, so next digit should be 2 or 4). 15 | Time Complexity: O(9 * 2^(NumOfDigits(N) - 1)) 16 | Space Complexity: O(1) , recursion stack will have depth 9 (but this can be considered as constant) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | def jumping_numbers(x): 25 | result = [] 26 | 27 | # take all 9 possible starting combinations 28 | for i in range(1, 10): 29 | jumping_num(i, x, result) 30 | 31 | return result 32 | 33 | def jumping_num(num, x, result): 34 | if num > x: 35 | return 36 | 37 | result.append(num) 38 | 39 | last_digit = num % 10 40 | next_num = num * 10 41 | 42 | # decrease the last digit by one 43 | if last_digit != 0: 44 | jumping_num(next_num + last_digit - 1, x, result) 45 | 46 | # increase the last digit by one 47 | if last_digit != 9: 48 | jumping_num(next_num + last_digit + 1, x, result) 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | # Correct result => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12] 57 | print(jumping_numbers(20)) 58 | 59 | # Test 2 60 | # Correct result => [1, 10, 12, 2, 21, 23, 3, 32, 34, 4, 43, 45, 5, 54, 56, 6, 65, 67, 7, 76, 78, 8, 87, 89, 9, 98] 61 | print(jumping_numbers(100)) -------------------------------------------------------------------------------- /Other/letter_combinations.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Letter Combinations of a Phone Number 3 | 4 | Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. 5 | A mapping of digit to letters is just like on the telephone buttons. Note that 1 does not map to any letters. 6 | 7 | Input: '23' 8 | Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'] 9 | 10 | ========================================= 11 | This problem could be solved in several ways (using recursion, stack, queue...) and the complexity is same in all, but this one has the simplest code. 12 | Iterate all digits and in each step look for the previous combinations, create a new 3 or 4 combinations from each combination using the mapping letters. 13 | Time Complexity: O(3^N * 4^M) , N = number of digits that maps to 3 letters, M = number of digits that maps to 4 letters 14 | Space Complexity: O(3^N * 4^M) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def letter_combinations(digits): 23 | if len(digits) == 0: 24 | return [] 25 | 26 | mappings = { 27 | '2': ['a','b','c'], 28 | '3': ['d','e','f'], 29 | '4': ['g','h','i'], 30 | '5': ['j','k','l'], 31 | '6': ['m','n','o'], 32 | '7': ['p','q','r','s'], 33 | '8': ['t','u','v'], 34 | '9': ['w','x','y','z'] 35 | } 36 | prev_combinations = [''] 37 | 38 | for digit in digits: 39 | new_combinations = [] 40 | for combination in prev_combinations: 41 | # use the mappings and create new combinations 42 | for mapping in mappings[digit]: 43 | new_combinations.append(combination + mapping) 44 | # save the newest combinations 45 | prev_combinations = new_combinations 46 | 47 | return prev_combinations 48 | 49 | 50 | ########### 51 | # Testing # 52 | ########### 53 | 54 | # Test 1 55 | # Correct result => ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'] 56 | print(letter_combinations('23')) -------------------------------------------------------------------------------- /Other/number_of_islands.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Number of Islands 3 | 4 | Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. 5 | An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. 6 | You may assume all four edges of the grid are all surrounded by water. 7 | 8 | Input: [ 9 | ['1','1','1','1','0'], 10 | ['1','1','0','1','0'], 11 | ['1','1','0','0','0'], 12 | ['0','0','0','0','0'] 13 | ] 14 | Output: 1 15 | 16 | Input: [ 17 | ['1','1','0','0','0'], 18 | ['1','1','0','0','0'], 19 | ['0','0','1','0','0'], 20 | ['0','0','0','1','1'] 21 | ] 22 | Output: 3 23 | 24 | ========================================= 25 | This problem can be solved in several ways (using DFS recursion or using the stack data structure) i'll solve it with BFS using Queue data structure. 26 | Time Complexity: O(M * N) 27 | Space Complexity: O(M * N) 28 | ''' 29 | 30 | 31 | ############ 32 | # Solution # 33 | ############ 34 | 35 | from collections import deque 36 | 37 | def num_of_islands(grid): 38 | n = len(grid) 39 | if n == 0: 40 | return 0 41 | m = len(grid[0]) 42 | 43 | islands = 0 44 | queue = deque() 45 | directions = [(-1, 0), (0, 1), (1, 0), (0, -1)] 46 | 47 | for i in range(n): 48 | for j in range(m): 49 | # search for an island 50 | if grid[i][j] == '1': 51 | islands += 1 52 | queue.append((i, j)) 53 | 54 | # BFS 55 | while queue: 56 | coord = queue.popleft() 57 | x, y = coord 58 | 59 | if grid[x][y] != '1': 60 | continue 61 | # delete the island 62 | grid[x][y] = '0' 63 | 64 | for direction in directions: 65 | # calculate the next position 66 | next_x, next_y = (x + direction[0], y + direction[1]) 67 | # check if the next position is valid 68 | if (next_x < 0) or (next_x >= n): 69 | continue 70 | if (next_y < 0) or (next_y >= m): 71 | continue 72 | # save this position 73 | queue.append((next_x, next_y)) 74 | 75 | return islands 76 | 77 | 78 | ########### 79 | # Testing # 80 | ########### 81 | 82 | # Test 1 83 | # Correct result => 3 84 | print(num_of_islands([['1','1','0','0','0'], ['1','1','0','0','0'], ['0','0','1','0','0'], ['0','0','0','1', '1']])) -------------------------------------------------------------------------------- /Other/palindrome_integer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Palindrome Integer 3 | 4 | Determine whether an integer is a palindrome. 5 | An integer is a palindrome when it reads the same backward as forward. 6 | 7 | Input: 121 8 | Output: True 9 | 10 | Input: -121 11 | Output: False 12 | Output explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome. 13 | 14 | Input: 10 15 | Output: false 16 | Output oxplanation: Reads 01 from right to left. Therefore it is not a palindrome. 17 | 18 | ========================================= 19 | Juste reverse the number and compare it with the original. 20 | Time Complexity: O(N) , N = number of digits 21 | Space Complexity: O(1) 22 | If you care about integer overflow (in Python you shouldn't care about this), then reverse only a half of the number 23 | and compare it with the other half. Also this solution is faster than the previous one because iterates only a half of the number. 24 | Time Complexity: O(N) 25 | Space Complexity: O(1) 26 | ''' 27 | 28 | 29 | ############## 30 | # Solution 1 # 31 | ############## 32 | 33 | def palindrome_integer_1(x): 34 | if x < 0: 35 | return False 36 | 37 | rev = 0 38 | temp = x 39 | while temp > 0: 40 | rev = (rev * 10) + (temp % 10) 41 | temp //= 10 42 | 43 | return rev == x 44 | 45 | 46 | ############## 47 | # Solution 2 # 48 | ############## 49 | 50 | def palindrome_integer_2(x): 51 | # check if negative or ends with zero 52 | if (x < 0) or (x > 0 and x % 10 == 0): 53 | return False 54 | 55 | rev = 0 56 | # if the reversed number is bigger from the original 57 | # that means the reversed number has same number of digits or more (1 or 2 more) 58 | while x > rev: 59 | rev = (rev * 10) + (x % 10) 60 | x //= 10 61 | 62 | # first comparison is for even number of digits and the second for odd number of digits 63 | return (rev == x) or (rev // 10 == x) 64 | 65 | 66 | ########### 67 | # Testing # 68 | ########### 69 | 70 | # Test 1 71 | # Correct result => True 72 | x = 121 73 | print(palindrome_integer_1(x)) 74 | print(palindrome_integer_2(x)) 75 | 76 | # Test 2 77 | # Correct result => False 78 | x = -121 79 | print(palindrome_integer_1(x)) 80 | print(palindrome_integer_2(x)) 81 | 82 | # Test 2 83 | # Correct result => False 84 | x = 10 85 | print(palindrome_integer_1(x)) 86 | print(palindrome_integer_2(x)) -------------------------------------------------------------------------------- /Other/permutations.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Permutations 3 | 4 | Given a collection of distinct integers, return all possible permutations. 5 | 6 | Input: [1,2,3] 7 | Output: 8 | [ 9 | [1,2,3], 10 | [1,3,2], 11 | [2,1,3], 12 | [2,3,1], 13 | [3,1,2], 14 | [3,2,1] 15 | ] 16 | 17 | ========================================= 18 | A classical recursive algorithm for permutations. 19 | Time Complexity: O(N!) 20 | Space Complexity: O(N!) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def permutations(nums): 29 | result = [] 30 | if len(nums) == 0: 31 | return result 32 | 33 | permute(result, set(nums), []) 34 | 35 | return result 36 | 37 | def permute(result, nums, permutation): 38 | if len(nums) == 0: 39 | result.append([num for num in permutation]) 40 | else: 41 | for num in list(nums): # create a new object with the same values because nums will be changed later 42 | nums.remove(num) 43 | permutation.append(num) 44 | 45 | permute(result, nums, permutation) 46 | 47 | # reset the structures 48 | del permutation[-1] 49 | nums.add(num) 50 | 51 | 52 | ########### 53 | # Testing # 54 | ########### 55 | 56 | # Test 1 57 | # Correct result => [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] 58 | print(permutations([1, 2, 3])) -------------------------------------------------------------------------------- /Other/postfix_evaluate.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Postfix Evaluate 3 | 4 | When arithmetic expressions are given in the familiar infix notation 2 + 3 * 4, we need to use 5 | parentheses to force a different evaluation order than the usual PEMDAS order determined by 6 | precedence and associativity. Writing arithmetic expressions in postfix notation (also known as 7 | Reverse Polish Notation) may look strange to us humans accustomed to the conventional infix 8 | notation, but is computationally much easier to handle, since postfix notation allows any evaluation 9 | order to be expressed without using any parentheses at all! A postfix expression is given as a list of 10 | items that can be either individual integers or one of the strings '+', '-', '*' and '/' for the four 11 | possible arithmetic operators. Calculate the result of the postfix expression. 12 | 13 | Input: [2, 3, '+', 4, '*'] 14 | Output: 20 15 | Output explanation: (2+3) * 4 16 | 17 | Input: [1, 2, 3, 4, 5, 6, '*', '*', '*', '*', '*'] 18 | Output: 720 19 | Output explanation: 1 * 2 * 3 * 4 * 5 * 6 20 | 21 | ========================================= 22 | Use stack, save all numbers into the stack. 23 | When a sign comes, pop the last 2 numbers from the stack, calculate their result and return the result into the stack. 24 | Time Complexity: O(N) 25 | Space Complexity: O(N) 26 | ''' 27 | 28 | 29 | ############ 30 | # Solution # 31 | ############ 32 | 33 | from collections import deque 34 | 35 | def postfix_evaluate(items): 36 | stack = deque() 37 | # lambda functions for all 4 operations 38 | operations = { 39 | '+': (lambda a, b: a + b), 40 | '-': (lambda a, b: a - b), 41 | '*': (lambda a, b: a * b), 42 | '/': (lambda a, b: 0 if (b == 0) else (a // b)) 43 | } 44 | 45 | for item in items: 46 | # check if the item is a sign or a number 47 | if item in operations: 48 | b = stack.pop() 49 | a = stack.pop() 50 | 51 | result = operations[item](a, b) 52 | 53 | stack.append(result) 54 | else: 55 | stack.append(item) 56 | 57 | return stack.pop() 58 | 59 | 60 | ########### 61 | # Testing # 62 | ########### 63 | 64 | # Test 1 65 | # Correct result => 20 66 | print(postfix_evaluate([2, 3, '+', 4, '*'])) 67 | 68 | # Test 2 69 | # Correct result => 14 70 | print(postfix_evaluate([2, 3, 4, '*', '+'])) 71 | 72 | # Test 3 73 | # Correct result => 0 74 | print(postfix_evaluate([3, 3, 3, '-', '/'])) 75 | 76 | # Test 4 77 | # Correct result => 2 78 | print(postfix_evaluate([7, 3, '/'])) 79 | 80 | # Test 5 81 | # Correct result => 720 82 | print(postfix_evaluate([1, 2, 3, 4, 5, 6, '*', '*', '*', '*', '*'])) -------------------------------------------------------------------------------- /Other/power.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Power 3 | 4 | Implement pow (a^b , a**b) method 5 | 6 | ========================================= 7 | Using divide and conquer approach. 8 | Time Complexity: O(LogB) 9 | Space Complexity: O(LogB) , because of recursion calls stack 10 | ''' 11 | 12 | ############ 13 | # Solution # 14 | ############ 15 | 16 | def power(a, b): 17 | if b < 0: 18 | # negative power 19 | return 1 / power_recursive(a, -b) 20 | 21 | return power_recursive(a, b) 22 | 23 | def power_recursive(a, b): 24 | if b == 0: 25 | return 1 26 | 27 | res = power_recursive(a, b // 2) 28 | res *= res 29 | 30 | if b % 2 == 1: 31 | res *= a 32 | 33 | return res 34 | 35 | 36 | ########### 37 | # Testing # 38 | ########### 39 | 40 | # Test 1 41 | # Correct result => 1 42 | print(power(2, 0)) 43 | 44 | # Test 2 45 | # Correct result => 2 46 | print(power(2, 1)) 47 | 48 | # Test 3 49 | # Correct result => 4 50 | print(power(2, 2)) 51 | 52 | # Test 4 53 | # Correct result => 8 54 | print(power(2, 3)) 55 | 56 | # Test 5 57 | # Correct result => 16 58 | print(power(2, 4)) 59 | 60 | # Test 6 61 | # Correct result => 32 62 | print(power(2, 5)) 63 | 64 | # Test 7 65 | # Correct result => 1024 66 | print(power(2, 10)) 67 | 68 | # Test 8 69 | # Correct result => 0.5 70 | print(power(2, -1)) 71 | 72 | # Test 9 73 | # Correct result => 0.25 74 | print(power(2, -2)) 75 | 76 | # Test 10 77 | # Correct result => 0.125 78 | print(power(2, -3)) 79 | 80 | # Test 11 81 | # Correct result => 0.0625 82 | print(power(2, -4)) 83 | 84 | # Test 12 85 | # Correct result => -8 86 | print(power(-2, 3)) 87 | 88 | # Test 13 89 | # Correct result => 16 90 | print(power(-2, 4)) -------------------------------------------------------------------------------- /Other/power_set.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The Power Set 3 | 4 | The power set of a set is the set of all its subsets. 5 | Write a function that, given a set, generates its power set. 6 | 7 | Input: [1, 2, 3] 8 | Output: [[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]] 9 | * You may also use a list or array to represent a set. 10 | 11 | ========================================= 12 | Simple recursive combinations algorithm. 13 | Time Complexity: O(Sum(C(I, N))) , sum of all combinations between 0 and N = C(0, N) + C(1, N) + ... + C(N, N) 14 | Space Complexity: O(Sum(C(I, N))) , this is for the result array, if we print the number then the space complexity will be O(N) (because of the recursive stack) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | def power_set(arr): 23 | result = [] 24 | combinations(result, arr, [], 0) 25 | return result 26 | 27 | # result, arr and taken are the same references always 28 | def combinations(result, arr, taken, pos): 29 | result.append([arr[i] for i in taken]) # create the current combination 30 | 31 | n = len(arr) 32 | if n == pos: 33 | return 34 | 35 | # start from the last position (don't need duplicates) 36 | for i in range(pos, n): 37 | taken.append(i) 38 | combinations(result, arr, taken, i + 1) 39 | del taken[-1] # return to the old state 40 | 41 | 42 | ########### 43 | # Testing # 44 | ########### 45 | 46 | # Test 1 47 | # Correct result => [[], [1], [1, 2], [2]] 48 | print(power_set([1, 2])) 49 | 50 | # Test 2 51 | # Correct result => [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]] 52 | print(power_set([1, 2, 3])) -------------------------------------------------------------------------------- /Other/queens_problem.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Queens Problem 3 | 4 | You have an N by N board. Write a function that, given N, returns the number of possible arrangements 5 | of the board where N queens can be placed on the board without threatening each other, 6 | i.e. no two queens share the same row, column, or diagonal. 7 | 8 | ========================================= 9 | Backtracking solution. 10 | Time Complexity: O(N!) (but I think it's much faster!) 11 | Space Complexity: O(N) 12 | * There are much faster solutions, like O(N^2) 13 | ''' 14 | 15 | 16 | ############ 17 | # Solution # 18 | ############ 19 | 20 | def place_n_queens(n): 21 | columns = [False for i in range(n)] 22 | order = [] 23 | 24 | return backtracking(columns, order) 25 | 26 | def backtracking(columns, order): 27 | # columns and order are references, no extra memory for those arrays (they are just pointers) 28 | n = len(columns) 29 | 30 | if len(order) == n: 31 | return 1 32 | 33 | total = 0 34 | 35 | for i in range(n): 36 | if (not columns[i]) and check_diagonals(order, i): 37 | order.append(i) 38 | columns[i] = True 39 | total += backtracking(columns, order) 40 | # return to the old state 41 | columns[i] = False 42 | del order[-1] 43 | 44 | return total 45 | 46 | def check_diagonals(order, pos): 47 | current_row = len(order) 48 | 49 | for i in range(current_row): 50 | if (i - order[i]) == (current_row - pos): 51 | return False 52 | if (i + order[i]) == (current_row + pos): 53 | return False 54 | 55 | return True 56 | 57 | 58 | ########### 59 | # Testing # 60 | ########### 61 | 62 | # Test 1 63 | # Correct result => 1 64 | print(place_n_queens(1)) 65 | 66 | # Test 2 67 | # Correct result => 0 68 | print(place_n_queens(2)) 69 | 70 | # Test 3 71 | # Correct result => 0 72 | print(place_n_queens(3)) 73 | 74 | # Test 4 75 | # Correct result => 2 76 | print(place_n_queens(4)) 77 | 78 | # Test 5 79 | # Correct result => 10 80 | print(place_n_queens(5)) 81 | 82 | # Test 6 83 | # Correct result => 4 84 | print(place_n_queens(6)) 85 | 86 | # Test 7 87 | # Correct result => 40 88 | print(place_n_queens(7)) 89 | 90 | # Test 8 91 | # Correct result => 92 92 | print(place_n_queens(8)) 93 | 94 | # Test 9 95 | # Correct result => 352 96 | print(place_n_queens(9)) 97 | 98 | # Test 10 99 | # Correct result => 724 100 | print(place_n_queens(10)) -------------------------------------------------------------------------------- /Other/reverse_all_lists.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse All Lists 3 | 4 | Return a list that contains the items in reverse, but so that whenever each item is 5 | itself a list, its elements are also reversed. This reversal of sublists must keep going on all the way 6 | down, no matter how deep the nesting of these lists, 7 | 8 | Input: [1, [2, 3, 4, 'yeah'], 5] 9 | Output: [5, ['yeah', 4, 3, 2], 1] 10 | 11 | ========================================= 12 | This problem can be solved using queue, stack (or recursion). Use in place reversing and save all 13 | inner lists for reversing later. 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | from collections import deque 24 | 25 | def reverse_all_lists(arr): 26 | queue = deque() 27 | queue.append(arr) 28 | 29 | while queue: 30 | inner_arr = queue.popleft() 31 | 32 | # in place reverse 33 | reverse_arr(inner_arr) 34 | 35 | # take all inner lists and save them for later 36 | for item in inner_arr: 37 | if isinstance(item, list): 38 | queue.append(item) 39 | 40 | # the arr is already reversed 41 | return arr 42 | 43 | def reverse_arr(arr): 44 | start = 0 45 | end = len(arr) - 1 46 | 47 | while start < end: 48 | # reverse the array from the start index to the end index by 49 | # swaping each element with the pair from the other part of the array 50 | arr[start], arr[end] = arr[end], arr[start] 51 | start += 1 52 | end -= 1 53 | 54 | return arr 55 | 56 | 57 | ########### 58 | # Testing # 59 | ########### 60 | 61 | # Test 1 62 | # Correct result => [5, ['yeah', 4, 3, 2], 1] 63 | print(reverse_all_lists([1, [2, 3, 4, 'yeah'], 5])) 64 | 65 | # Test 2 66 | # Correct result => [[[[['boo!'], 33], 17], 99], 42] 67 | print(reverse_all_lists([42, [99, [17, [33, ['boo!']]]]])) -------------------------------------------------------------------------------- /Other/reverse_integer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse Integer 3 | 4 | Given signed integer, reverse digits of an integer. 5 | 6 | Input: 123 7 | Output: 321 8 | 9 | Input: -123 10 | Output: -321 11 | 12 | Input: 120 13 | Output: 21 14 | 15 | ========================================= 16 | Simple solution, mod 10 to find all digits. 17 | Time Complexity: O(N) , N = number of digits 18 | Space Complexity: O(1) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | def reverse_integer(x): 27 | if x == 0: 28 | return 0 29 | 30 | sign = x // abs(x) # find the sign, -1 or 1 31 | x *= sign # make positive x, or x = abs(x) 32 | 33 | res = 0 34 | while x > 0: 35 | res = (res * 10) + (x % 10) 36 | x //= 10 37 | 38 | return res * sign 39 | 40 | 41 | ########### 42 | # Testing # 43 | ########### 44 | 45 | # Test 1 46 | # Correct result => 321 47 | print(reverse_integer(123)) 48 | 49 | # Test 2 50 | # Correct result => -321 51 | print(reverse_integer(-123)) 52 | 53 | # Test 3 54 | # Correct result => 21 55 | print(reverse_integer(120)) -------------------------------------------------------------------------------- /Other/river_sizes.py: -------------------------------------------------------------------------------- 1 | ''' 2 | River Sizes 3 | 4 | You are given a two-dimensional array (matrix) of potentially unequal height and width containing only 0s and 1s. 5 | Each 0 represents land, and each 1 represents part of a river. A river consists of any number of 1s that are 6 | either horizontally or vertically adjacent (but not diagonally adjacent). 7 | The number of adjacent 1s forming a river determine its size. 8 | Write a function that returns an array of the sizes of all rivers represented in the input matrix. 9 | Note that these sizes do not need to be in any particular order. 10 | 11 | Input: 12 | [ 13 | [1, 0, 0, 1], 14 | [1, 0, 1, 0], 15 | [0, 0, 1, 0], 16 | [1, 0, 1, 0] 17 | ] 18 | Output: [2, 1, 3, 1] 19 | 20 | ========================================= 21 | This problem can be solved using DFS or BFS. 22 | If 1 is found, find all horizontal or vertical neighbours (1s), and mark them as 0. 23 | Time Complexity: O(N*M) 24 | Space Complexity: O(N*M) , because of recursion calls stack 25 | ''' 26 | 27 | 28 | ############ 29 | # Solution # 30 | ############ 31 | 32 | def river_sizes(matrix): 33 | n = len(matrix) 34 | m = len(matrix[0]) 35 | 36 | results = [] 37 | 38 | for i in range(n): 39 | for j in range(m): 40 | if matrix[i][j] != 0: 41 | # find the river size 42 | size = dfs((i, j), matrix) 43 | 44 | # save the river size 45 | results.append(size) 46 | 47 | return results 48 | 49 | def dfs(coord, matrix): 50 | (i, j) = coord 51 | 52 | if i < 0 or j < 0: 53 | # invalid position 54 | return 0 55 | 56 | n = len(matrix) 57 | m = len(matrix[0]) 58 | 59 | if i == n or j == m: 60 | # invalid position 61 | return 0 62 | 63 | if matrix[i][j] == 0: 64 | # not a river 65 | return 0 66 | 67 | # update the matrix, the matrix is passed by reference 68 | matrix[i][j] = 0 69 | # this position is part of river 70 | size = 1 71 | 72 | # directions: down, left, up, right 73 | dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)] 74 | 75 | # check all 4 directions 76 | for d in dirs: 77 | size += dfs((i + d[0], j + d[1]), matrix) 78 | 79 | return size 80 | 81 | 82 | ########### 83 | # Testing # 84 | ########### 85 | 86 | # Test 1 87 | # Correct result => [2, 1, 3, 1] 88 | matrix = [ 89 | [1, 0, 0, 1], 90 | [1, 0, 1, 0], 91 | [0, 0, 1, 0], 92 | [1, 0, 1, 0] 93 | ] 94 | print(river_sizes(matrix)) -------------------------------------------------------------------------------- /Other/safe_squares_rooks.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Safe Squares from Rooks 3 | 4 | On a generalized n-by-n chessboard, there are some number of rooks, each rook represented as a 5 | two-tuple (row, column) of the row and the column that it is in. (The rows and columns are 6 | numbered from 0 to n-1.) A chess rook covers all squares that are in the same row or in the same 7 | column as that rook. Given the board size n and the list of rooks on that board, count the number of 8 | empty squares that are safe, that is, are not covered by any rook. 9 | 10 | Input: [(1, 1), (3, 5), (7, 0), (7, 6)], 8 11 | Output: 20 12 | 13 | ========================================= 14 | The result is a multiplication between free rows and free columns. 15 | Use hashsets to store the free rows and columns. 16 | Time Complexity: O(N) 17 | Space Complexity: O(N) 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | def safe_squares_rooks(rooks, n): 26 | rows = set() 27 | cols = set() 28 | 29 | for i in range(n): 30 | rows.add(i) 31 | cols.add(i) 32 | 33 | for rook in rooks: 34 | if rook[0] in rows: 35 | rows.remove(rook[0]) 36 | if rook[1] in cols: 37 | cols.remove(rook[1]) 38 | 39 | return len(rows) * len(cols) 40 | 41 | 42 | ########### 43 | # Testsing # 44 | ########### 45 | 46 | # safe_squares_rooks 1 47 | # Correct result => 1 48 | print(safe_squares_rooks([(1, 1)], 2)) 49 | 50 | # safe_squares_rooks 2 51 | # Correct result => 4 52 | print(safe_squares_rooks([(2, 3), (0, 1)], 4)) 53 | 54 | # safe_squares_rooks 3 55 | # Correct result => 20 56 | print(safe_squares_rooks([(1, 1), (3, 5), (7, 0), (7, 6)], 8)) 57 | 58 | # safe_squares_rooks 4 59 | # Correct result => 0 60 | print(safe_squares_rooks([(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)], 6)) -------------------------------------------------------------------------------- /Other/search_2d_matrix.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Search a 2D Matrix 3 | 4 | Write an efficient algorithm that searches for a value in an m x n matrix. 5 | This matrix has the following properties: 6 | - Integers in each row are sorted in ascending from left to right. 7 | - Integers in each column are sorted in ascending from top to bottom. 8 | 9 | Input: target = 21 10 | [ 11 | [1, 4, 7, 11, 15], 12 | [2, 5, 8, 12, 19], 13 | [3, 6, 9, 16, 22], 14 | [10, 13, 14, 17, 24], 15 | [18, 21, 23, 26, 30] 16 | ] 17 | Output: True 18 | 19 | ========================================= 20 | Start from bottom left corner and search in top right direction. 21 | Time Complexity: O(N + M) 22 | Space Complexity: O(1) 23 | ''' 24 | 25 | 26 | ############ 27 | # Solution # 28 | ############ 29 | 30 | def search_2d_matrix(matrix, target): 31 | n = len(matrix) 32 | m = len(matrix[0]) 33 | 34 | j = 0 35 | i = n - 1 36 | 37 | while (i >= 0) and (j < m): 38 | if matrix[i][j] > target: 39 | i -= 1 40 | elif matrix[i][j] < target: 41 | j += 1 42 | else: 43 | return True 44 | 45 | return False 46 | 47 | 48 | ########### 49 | # Testing # 50 | ########### 51 | 52 | # Test 1 53 | # Correct result => True 54 | print(search_2d_matrix([[1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30]], 21)) 55 | 56 | # Test 2 57 | # Correct result => False 58 | print(search_2d_matrix([[1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30]], 20)) -------------------------------------------------------------------------------- /Other/set_matrix_zeroes.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Set Matrix Zeroes 3 | 4 | Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in-place. 5 | 6 | Input: 7 | [ 8 | [1,1,1], 9 | [1,0,1], 10 | [1,1,1] 11 | ] 12 | Output: 13 | [ 14 | [1,0,1], 15 | [0,0,0], 16 | [1,0,1] 17 | ] 18 | 19 | ========================================= 20 | Use first column and first row for marking when 0 is found. 21 | Time Complexity: O(N*M) 22 | Space Complexity: O(1) 23 | ''' 24 | 25 | 26 | ############ 27 | # Solution # 28 | ############ 29 | 30 | def set_matrix_zeroes(matrix): 31 | n = len(matrix) 32 | if n == 0: 33 | return 34 | m = len(matrix[0]) 35 | 36 | # check if 0 exist in first row 37 | is_row = False 38 | for j in range(m): 39 | if matrix[0][j] == 0: 40 | is_row = True 41 | # check if 0 exist in first column 42 | is_col = False 43 | for i in range(n): 44 | if matrix[i][0] == 0: 45 | is_col = True 46 | 47 | # find which columns and rows should be 0 48 | for i in range(1, n): 49 | for j in range(1, m): 50 | if matrix[i][j] == 0: 51 | matrix[i][0] = matrix[0][j] = 0 52 | 53 | # set 0 if the row or column where this element is located is 0 54 | for i in range(1, n): 55 | for j in range(1, m): 56 | if (matrix[i][0] == 0) or (matrix[0][j] == 0): 57 | matrix[i][j] = 0 58 | 59 | # fill the first row with 0 if needed 60 | if is_row: 61 | for j in range(m): 62 | matrix[0][j] = 0 63 | # fill the first column with 0 if needed 64 | if is_col: 65 | for i in range(n): 66 | matrix[i][0] = 0 67 | 68 | 69 | ########### 70 | # Testing # 71 | ########### 72 | 73 | # Test 1 74 | # Correct result => [[0, 0, 0, 0], [0, 4, 5, 0], [0, 3, 1, 0]] 75 | mat = [[0, 1, 2, 0], [3, 4, 5, 2], [1, 3, 1, 5]] 76 | set_matrix_zeroes(mat) 77 | print(mat) 78 | 79 | # Test 2 80 | # Correct result => [[1, 0, 1], [0, 0, 0], [1, 0, 1]] 81 | mat = [[1, 1, 1], [1, 0, 1], [1, 1, 1]] 82 | set_matrix_zeroes(mat) 83 | print(mat) -------------------------------------------------------------------------------- /Other/sliding_window_maximum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Maximum of All Subarrays of Size K 3 | 4 | Given an array and an integer k, find the maximum for each and every contiguous subarray of size k. 5 | O(n) time and O(k) space! 6 | 7 | For example, given array = [10, 5, 2, 7, 8, 7] and k = 3, we should get: [10, 7, 8, 8], since: 8 | 10 = max(10, 5, 2) 9 | 7 = max(5, 2, 7) 10 | 8 = max(2, 7, 8) 11 | 8 = max(7, 8, 7) 12 | 13 | ========================================= 14 | Sliding window solution using deque or linked lists 15 | (only need to be able to remove from both sides and to add on both sides in constant time). 16 | Time Complexity: O(N) 17 | Space Complexity: O(K) 18 | ''' 19 | 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | from collections import deque 26 | 27 | def max_el_subarrays(arr, k): 28 | n = len(arr) 29 | if n == 0: 30 | return -1 31 | 32 | deq = deque() 33 | result = [] 34 | 35 | # starting sliding window 36 | for i in range(min(k, n)): 37 | # start from the end and remove all previous indicies 38 | # which have smaller values than the current one 39 | # using this logic, we're sure that there is no smaller element (in the previous added indicies, the whole deque) than the last added 40 | while (deq) and (arr[i] >= arr[deq[-1]]): 41 | deq.pop() 42 | # add the current index on back/right 43 | deq.append(i) 44 | 45 | # add the biggest element in the result array 46 | # the biggest element is always located in front/left 47 | result.append(arr[deq[0]]) 48 | 49 | # move element by element (slide the window) 50 | for i in range(k, n): 51 | # remove the front index if it's outside the window 52 | if (deq) and (deq[0] == i - k): 53 | deq.popleft() 54 | 55 | # use the same logic as before, remove from back/right and add to front/left 56 | while (deq) and (arr[i] >= arr[deq[-1]]): 57 | deq.pop() 58 | 59 | deq.append(i) 60 | result.append(arr[deq[0]]) 61 | 62 | return result -------------------------------------------------------------------------------- /Other/spiral_matrix.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Spiral Matrix 3 | 4 | Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. 5 | 6 | Input: 7 | [ 8 | [ 1, 2, 3 ], 9 | [ 4, 5, 6 ], 10 | [ 7, 8, 9 ] 11 | ] 12 | Output: [1, 2, 3, 6, 9, 8, 7, 4, 5] 13 | 14 | Input: 15 | [ 16 | [1, 2, 3, 4], 17 | [5, 6, 7, 8], 18 | [9, 10, 11, 12] 19 | ] 20 | Output: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] 21 | 22 | ========================================= 23 | Simulate spiral moving, start from (0,0) and when a border is reached change the X or Y direction. 24 | Time Complexity: O(N*M) 25 | Space Complexity: O(N*M) 26 | ''' 27 | 28 | 29 | ############ 30 | # Solution # 31 | ############ 32 | 33 | def spiral_matrix(matrix): 34 | n = len(matrix) 35 | if n == 0: 36 | return [] 37 | 38 | m = len(matrix[0]) 39 | if m == 0: 40 | return [] 41 | 42 | total = n * m 43 | res = [] 44 | 45 | n -= 1 46 | xDir, yDir = 1, 1 47 | x, y = 0, -1 48 | 49 | while len(res) < total: 50 | for i in range(m): 51 | y += yDir 52 | res.append(matrix[x][y]) 53 | m -= 1 # decrease horizontal moving steps 54 | yDir *= -1 # change the Y direction 55 | 56 | for i in range(n): 57 | x += xDir 58 | res.append(matrix[x][y]) 59 | n -= 1 # decrease vertical moving steps 60 | xDir *= -1 # change the Y direction 61 | 62 | return res 63 | 64 | 65 | ########### 66 | # Testing # 67 | ########### 68 | 69 | # Test 1 70 | # Correct result => [1, 2, 3, 6, 9, 8, 7, 4, 5] 71 | print(spiral_matrix([[ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ]])) 72 | 73 | # Test 2 74 | # Correct result => [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] 75 | print(spiral_matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])) -------------------------------------------------------------------------------- /Other/valid_parentheses.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Valid Parentheses 3 | 4 | Given a string of round, curly, and square open and closing brackets, return whether the brackets are balanced (well-formed). 5 | For example, given the string '([])[]({})', you should return true. 6 | Given the string '([)]' or '((()', you should return false. 7 | 8 | Input: '()[{([]{})}]' 9 | Output: True 10 | 11 | ========================================= 12 | Use stack. Add open brackets in the stack, remove the last bracket from the stack if there is a closing brackets. 13 | Time Complexity: O(N) 14 | Space Complexity: O(N) 15 | ''' 16 | 17 | 18 | ############ 19 | # Solution # 20 | ############ 21 | 22 | from collections import deque 23 | 24 | def is_valid(string): 25 | closing = { 26 | '}': '{', 27 | ']': '[', 28 | ')': '(' 29 | } 30 | stack = deque() 31 | 32 | for char in string: 33 | if char in closing: 34 | if len(stack) == 0: 35 | return False 36 | 37 | last = stack.pop() 38 | if last != closing[char]: 39 | return False 40 | else: 41 | stack.append(char) 42 | 43 | return True 44 | 45 | 46 | ########### 47 | # Testing # 48 | ########### 49 | 50 | # Test 1 51 | # Correct result => True 52 | print(is_valid('()[{([]{})}]')) 53 | 54 | # Test 2 55 | # Correct result => False 56 | print(is_valid('()[{([]{]})}]')) 57 | 58 | # Test 3 59 | # Correct result => False 60 | print(is_valid('(]]])')) -------------------------------------------------------------------------------- /Strings/encoding_string.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Encoding string 3 | 4 | Run-length encoding is a fast and simple method of encoding strings. 5 | The basic idea is to represent repeated successive characters as a single count and character. 6 | Implement run-length encoding and decoding. You can assume the string to be encoded have no digits and consists solely of alphabetic characters. 7 | You can assume the string to be decoded is valid. 8 | 9 | Input: 'AAAABBBCCDAA' 10 | Output: '4A3B2C1D2A' 11 | 12 | ========================================= 13 | Simple solution, just iterate the string and count. 14 | Time Complexity: O(N) 15 | Space Complexity: O(1) 16 | ''' 17 | 18 | 19 | ############ 20 | # Solution # 21 | ############ 22 | 23 | def encoding(word): 24 | n = len(word) 25 | if n == 0: 26 | return '' 27 | 28 | letter = word[0] 29 | length = 1 30 | res = '' 31 | 32 | for i in range(1, n): 33 | if word[i] == letter: 34 | length += 1 35 | else: 36 | res += str(length) + letter 37 | letter = word[i] 38 | length = 1 39 | 40 | res += str(length) + letter 41 | 42 | return res 43 | 44 | 45 | ########### 46 | # Testing # 47 | ########### 48 | 49 | # Correct result => '4A3B2C1D2A' 50 | print(encoding('AAAABBBCCDAA')) -------------------------------------------------------------------------------- /Strings/longest_common_prefix.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Common Prefix 3 | 4 | Write a function to find the longest common prefix string amongst an array of strings. 5 | If there is no common prefix, return an empty string ''. 6 | 7 | Input: ['flower', 'flow', 'flight'] 8 | Output: 'fl' 9 | 10 | Input: ['dog', 'racecar', 'car'] 11 | Output: '' 12 | 13 | Input: ['aa', 'a'] 14 | Output: 'a' 15 | 16 | ========================================= 17 | Many solutions for this problem exist (Divide and Conquer, Trie, etc) but this is the simplest and the fastest one. 18 | Use the first string as LCP and iterate the rest in each step compare it with another one. 19 | Time Complexity: O(N*A) , N = number of strings, A = average chars, or simplest notation O(S) = total number of chars 20 | Space Complexity: O(1) 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | def longest_common_prefix(strs): 29 | n = len(strs) 30 | if n == 0: 31 | return '' 32 | 33 | lcp = strs[0] 34 | # instead of string manipulations, manipulate with the last common index 35 | lcp_idx = len(lcp) 36 | 37 | for i in range(1, n): 38 | lcp_idx = min(lcp_idx, len(strs[i])) 39 | 40 | for j in range(lcp_idx): 41 | if lcp[j] != strs[i][j]: 42 | lcp_idx = j 43 | break 44 | 45 | return lcp[:lcp_idx] 46 | ''' 47 | # if you like string manipulations, you can use this code 48 | # i don't like string manipulations in Python because they're immutable 49 | lcp = strs[0] 50 | for i in range(1, n): 51 | lcp = lcp[:len(strs[i])] 52 | for j in range(len(lcp)): 53 | if lcp[j] != strs[i][j]: 54 | lcp = lcp[:j] 55 | break 56 | return lcp 57 | ''' 58 | 59 | 60 | ########### 61 | # Testing # 62 | ########### 63 | 64 | # Test 1 65 | # Correct result => 'fl' 66 | print(longest_common_prefix(['flower', 'flow', 'flight'])) 67 | 68 | # Test 2 69 | # Correct result => '' 70 | print(longest_common_prefix(['dog', 'racecar', 'car'])) 71 | 72 | # Test 3 73 | # Correct result => 'a' 74 | print(longest_common_prefix(['aa', 'a'])) -------------------------------------------------------------------------------- /Strings/longest_palindromic_substring.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Longest Palindromic Substring 3 | 4 | Find the length of the longest palindromic substring. 5 | 6 | Input: 'google' 7 | Output: 4 8 | 9 | ========================================= 10 | Simple algorithm, for each position compare left and right side and count the length of matching. 11 | Time Complexity: O(N^2) 12 | Space Complexity: O(1) 13 | * For this problem exists a faster algorithm, called Manchester's Algorithm. Time Complexity O(N) and Space Complexity O(N). 14 | ''' 15 | 16 | 17 | ############ 18 | # Solution # 19 | ############ 20 | 21 | def longest_palindromic_substring(s): 22 | n = len(s) 23 | longest = 1 24 | 25 | for i in range(n): 26 | # search for palindrom with odd number of chars 27 | count_odd = compare_both_sides(s, 1, i - 1, i + 1) 28 | 29 | # search for palindrom with even number of chars 30 | count_even = compare_both_sides(s, 0, i - 1, i) 31 | 32 | # save the longest 33 | longest = max(longest, count_odd, count_even) 34 | 35 | return longest 36 | 37 | def compare_both_sides(s, count, left, right): 38 | # helper method to avoid duplicate code 39 | n = len(s) 40 | 41 | while (left >= 0) and (right < n) and (s[left] == s[right]): 42 | count += 2 43 | left -= 1 44 | right += 1 45 | 46 | return count 47 | 48 | 49 | ########### 50 | # Testing # 51 | ########### 52 | 53 | # Test 1 54 | # Correct result => 4 55 | print(longest_palindromic_substring('google')) 56 | 57 | # Test 2 58 | # Correct result => 11 59 | print(longest_palindromic_substring('sgoaberebaogle')) 60 | 61 | # Test 3 62 | # Correct result => 2 63 | print(longest_palindromic_substring('abcdeef')) 64 | 65 | # Test 4 66 | # Correct result => 7 67 | print(longest_palindromic_substring('racecar')) 68 | 69 | # Test 5 70 | # Correct result => 5 71 | print(longest_palindromic_substring('abbabbc')) 72 | 73 | # Test 6 74 | # Correct result => 10 75 | print(longest_palindromic_substring('forgeeksskeegfor')) -------------------------------------------------------------------------------- /Strings/reverse_string.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse string 3 | 4 | Reverse string, in linear time complexity. 5 | 6 | Input: 'i like this program very much' 7 | Output: 'hcum yrev margorp siht ekil i' 8 | 9 | Input: 'how are you' 10 | Output: 'uoy era woh' 11 | 12 | ========================================= 13 | Reverse the whole sentence by swapping pair letters in-place (first with last, second with second from the end, etc). 14 | In Python, the string manipulation operations are too slow (string is immutable), because of that we need to convert the string into array. 15 | In C/C++, the Space complexity will be O(1) (because the strings are just arrays with chars). 16 | Exist 2 more "Pythonic" ways of reversing strings/arrays: 17 | - reversed_str = reversed(str) 18 | - reversed_str = str[::-1] 19 | But I wanted to show how to implement a reverse algorithm step by step so someone will know how to implement it in other languages. 20 | Time Complexity: O(N) 21 | Space Complexity: O(N) 22 | ''' 23 | 24 | 25 | ############ 26 | # Solution # 27 | ############ 28 | 29 | def reverse_sentence(sentence): 30 | arr = [c for c in sentence] # or just arr = list(sentence) 31 | start = 0 32 | end = len(arr) - 1 33 | 34 | while start < end: 35 | # reverse the array from the start index to the end index by 36 | # swaping each char with the pair from the other part of the array 37 | swap(arr, start, end) 38 | start += 1 39 | end -= 1 40 | 41 | return ''.join(arr) 42 | 43 | def swap(arr, i, j): 44 | # swapping two elements from a same array 45 | arr[i], arr[j] = arr[j], arr[i] 46 | '''same as 47 | temp = arr[i] 48 | arr[i] = arr[j] 49 | arr[j] = temp 50 | ''' 51 | 52 | 53 | ########### 54 | # Testing # 55 | ########### 56 | 57 | # Test 1 58 | # Correct result => 'hcum yrev margorp siht ekil i' 59 | print(reverse_sentence('i like this program very much')) 60 | 61 | # Test 2 62 | # Correct result => 'uoy era woh' 63 | print(reverse_sentence('how are you')) -------------------------------------------------------------------------------- /Strings/reverse_vowels.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse Vowels 3 | 4 | Given a text string, create and return a new string constructed by finding all its vowels (for 5 | simplicity, in this problem vowels are the letters in the string 'aeiouAEIOU') and reversing their 6 | order, while keeping all non-vowel characters exactly as they were in their original positions. 7 | 8 | Input: 'Hello world' 9 | Output: 'Hollo werld' 10 | 11 | ========================================= 12 | Simple solution, find a vowel from left and swap it with a vowel from right. 13 | In Python, the string manipulation operations are too slow (string is immutable), because of that we need to convert the string into array. 14 | In C/C++, the Space complexity will be O(1) (because the strings are just arrays with chars). 15 | Time Complexity: O(N) 16 | Space Complexity: O(N) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | def reverse_vowels(sentence): 25 | arr = [c for c in sentence] # or just arr = list(sentence) 26 | 27 | vowels = { 28 | 'a': True, 'A': True, 29 | 'e': True, 'E': True, 30 | 'i': True, 'I': True, 31 | 'o': True, 'O': True, 32 | 'u': True, 'U': True 33 | } 34 | 35 | left = 0 36 | right = len(arr) - 1 37 | 38 | while True: 39 | # find a vowel from left 40 | while left < right: 41 | if arr[left] in vowels: 42 | break 43 | left += 1 44 | 45 | # find a vowel from right 46 | while left < right: 47 | if arr[right] in vowels: 48 | break 49 | right -= 1 50 | 51 | if left >= right: 52 | # in this case, there are only 1 or 0 vowels 53 | # so this is the end of the algorithm, no need from more reversing 54 | break 55 | 56 | # swap the vowels 57 | arr[left], arr[right] = arr[right], arr[left] 58 | 59 | left += 1 60 | right -= 1 61 | 62 | return ''.join(arr) 63 | 64 | 65 | ########### 66 | # Testing # 67 | ########### 68 | 69 | # Test 1 70 | # Correct result => 'ubcdofghijklmnepqrstavwxyz' 71 | print(reverse_vowels('abcdefghijklmnopqrstuvwxyz')) 72 | 73 | # Test 2 74 | # Correct result => 'Hollo werld' 75 | print(reverse_vowels('Hello world')) -------------------------------------------------------------------------------- /Strings/reverse_words_in_sentence.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reverse words in sentence 3 | 4 | Reverse words in a given string, in linear time complexity. 5 | 6 | Input: 'i like this program very much' 7 | Output: 'much very program this like i' 8 | 9 | Input: 'how are you' 10 | Output: 'you are how' 11 | 12 | ========================================= 13 | First, find each word and reverse it (in place, by swapping the letters), 14 | after all words are reversed, reverse the whole sentence (in place, by swapping the letters) 15 | and the first word will be last and will be in the original form. 16 | In Python, the string manipulation operations are too slow (string is immutable), because of that we need to convert the string into array. 17 | In C/C++, the Space complexity will be O(1) (because the strings are just arrays with chars). 18 | Time Complexity: O(N) 19 | Space Complexity: O(N) 20 | ''' 21 | 22 | 23 | ############ 24 | # Solution # 25 | ############ 26 | 27 | def reverse_words_in_sentence(sentence): 28 | arr = [c for c in sentence] # or just arr = list(sentence) 29 | n = len(arr) 30 | last_idx = n - 1 31 | start = 0 32 | 33 | # reverse all words 34 | for i in range(n): 35 | if arr[i] == ' ': 36 | # in this moment we're sure that the word is complete 37 | reverse_array(arr, start, i - 1) 38 | start = i + 1 39 | # reverse the last word 40 | reverse_array(arr, start, last_idx) 41 | # reverse the whole sentence 42 | reverse_array(arr, 0, last_idx) 43 | 44 | return ''.join(arr) 45 | 46 | def reverse_array(arr, start, end): 47 | # reverse the array from the start index to the end index 48 | while start < end: 49 | arr[start], arr[end] = arr[end], arr[start] # swap 50 | start += 1 51 | end -= 1 52 | 53 | 54 | ########### 55 | # Testing # 56 | ########### 57 | 58 | # Test 1 59 | # Correct result => 'much very program this like i' 60 | print(reverse_words_in_sentence('i like this program very much')) 61 | 62 | # Test 2 63 | # Correct result => 'you are how' 64 | print(reverse_words_in_sentence('how are you')) -------------------------------------------------------------------------------- /Strings/swap_first_and_last_word.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Swap the frst and the last word 3 | 4 | Given an string, you need to swap the first and last word in linear time. 5 | Everything between should stay in same order. 6 | 7 | Sample input: 'i like this program very much' 8 | Sample output: 'much like this program very i' 9 | 10 | ========================================= 11 | Reverse the whole string, after that reverse only first and only last word, 12 | in the end reverse everything between first and last word. (using IN-PLACE reversing) 13 | In Python, the string manipulation operations are too slow (string is immutable), because of that we need to convert the string into array. 14 | In C/C++, the Space complexity will be O(1) (because the strings are just arrays with chars). 15 | Time complexity: O(N) , O(N + N) = O(2 * N) = O(N) 16 | Space Complexity: O(N) 17 | ''' 18 | 19 | 20 | ############ 21 | # Solution # 22 | ############ 23 | 24 | def swap_first_and_last_word(sentence): 25 | arr = [c for c in sentence] # or just arr = list(sentence) 26 | first_idx = 0 27 | last_idx = len(arr) - 1 28 | 29 | # reverse the whole array, in this way I'll change the first and the last word 30 | reverse_array(arr, first_idx, last_idx) 31 | 32 | # find positions of the first and the last space char 33 | first_space = first_idx 34 | while arr[first_space] != ' ': 35 | first_space += 1 36 | 37 | last_space = last_idx 38 | while arr[last_space] != ' ': 39 | last_space -= 1 40 | 41 | # reverse only the first word 42 | reverse_array(arr, first_idx, first_space - 1) 43 | # reverse only the last word 44 | reverse_array(arr, last_space + 1, last_idx) 45 | # reverse everything between (with this reversing, all words between will have the same order as the starting one) 46 | reverse_array(arr, first_space + 1, last_space - 1) 47 | 48 | return ''.join(arr) 49 | 50 | def reverse_array(arr, start, end): 51 | # reverse the array from the start index to the end index 52 | while start < end: 53 | arr[start], arr[end] = arr[end], arr[start] # swap 54 | start += 1 55 | end -= 1 56 | 57 | 58 | ########### 59 | # Testing # 60 | ########### 61 | 62 | # Test 1 63 | # Correct result => 'practice makes perfect 64 | print(swap_first_and_last_word('perfect makes practice')) 65 | 66 | # Test 2 67 | # Correct result => 'much like this program very i' 68 | print(swap_first_and_last_word('i like this program very much')) -------------------------------------------------------------------------------- /Strings/zigzag_conversion.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ZigZag Conversion 3 | 4 | The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility) 5 | 6 | P A H N 7 | A P L S I I G 8 | Y I R 9 | And then read line by line: 'PAHNAPLSIIGYIR' 10 | 11 | Input: s = 'PAYPALISHIRING', num_rows = 3 12 | Output: 'PAHNAPLSIIGYIR' 13 | 14 | ========================================= 15 | Go row by row and using the steps logic build the new string by jumping chars. 16 | Middle rows have 2 times more elements than the first and last row. 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) 19 | Collect all parts in separate bucket, in the end merge these buckets. 20 | Time Complexity: O(N) 21 | Space Complexity: O(N) 22 | ''' 23 | 24 | 25 | ############## 26 | # Solution 1 # 27 | ############## 28 | 29 | def convert(s, num_rows): 30 | if num_rows == 1: 31 | return s 32 | 33 | n = len(s) 34 | res = '' 35 | cycle = 2 * (num_rows - 1) 36 | 37 | for i in range(0, num_rows): 38 | steps = cycle - 2 * i 39 | if (i == 0) or (i == num_rows - 1): 40 | # if first or last row, make a whole cycle 41 | steps = cycle 42 | 43 | j = i 44 | while j < n: 45 | res += s[j] 46 | j += steps 47 | if (i > 0) and (i < num_rows - 1): 48 | # change the steps if not first or last row 49 | steps = cycle - steps 50 | 51 | return res 52 | 53 | 54 | ############## 55 | # Solution 2 # 56 | ############## 57 | 58 | def convert_2(word, numRows): 59 | numLetters = len(word) 60 | bucket = [""] * numRows 61 | fullCycle = 2 * (numRows - 1) 62 | if numRows == 1: 63 | fullCycle = 1 64 | 65 | for pos in range(0, numLetters): 66 | posCycle = pos % fullCycle 67 | 68 | if posCycle >= numRows: 69 | posCycle = fullCycle - posCycle 70 | 71 | bucket[posCycle] += word[pos] 72 | 73 | result = "" 74 | for part in bucket: 75 | result += part 76 | 77 | return result 78 | 79 | 80 | ########### 81 | # Testing # 82 | ########### 83 | 84 | # Test 1 85 | # Correct result => 'PAHNAPLSIIGYIR' 86 | print(convert('PAYPALISHIRING', 3)) 87 | print(convert_2('PAYPALISHIRING', 3)) 88 | 89 | # Test 2 90 | # Correct result => 'PINALSIGYAHRPI' 91 | print(convert('PAYPALISHIRING', 4)) 92 | print(convert_2('PAYPALISHIRING', 4)) 93 | -------------------------------------------------------------------------------- /Trees/diameter_of_binary_tree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Diameter of Binary Tree 3 | 4 | Given a binary tree, you need to compute the length of the diameter of the tree. 5 | The diameter of a binary tree is the length of the longest path between any two nodes in a tree. 6 | This path may or may not pass through the root. 7 | Note: The length of path between two nodes is represented by the number of nodes. 8 | 9 | Input: 3 10 | / \ 11 | 1 4 12 | \ \ 13 | 2 5 14 | / 15 | 7 16 | Output: 6 17 | Output Explanation: [7, 2, 1, 3, 4, 5] is the diameter of the binary tree. 18 | 19 | Input: 5 20 | / \ 21 | 3 6 22 | / \ 23 | 2 4 24 | / \ 25 | 1 8 26 | Output: 5 27 | Output Explanation: [1, 2, 3, 4, 8] is the diameter of the binary tree. 28 | 29 | ========================================= 30 | Traverse the tree and keep/return information about the longest/max branch and longest/max diameter. 31 | Time Complexity: O(N) 32 | Space Complexity: O(N) , because of the recursion stack (but this is if the tree is one branch), O(LogN) if the tree is balanced. 33 | ''' 34 | 35 | 36 | ############ 37 | # Solution # 38 | ############ 39 | 40 | # import TreeNode class from tree_helpers.py 41 | from tree_helpers import TreeNode 42 | 43 | def diameter(root): 44 | return find_diameter(root)[1] 45 | 46 | def find_diameter(root): 47 | ''' returns (max branch length, max diameter) ''' 48 | if not root: 49 | return 0, 0 50 | 51 | # traverse left and right subtrees 52 | left, right = find_diameter(root.left), find_diameter(root.right) 53 | 54 | # return the max branch from the left and right subtrees plus the current node 55 | # and find the max diameter till now (using the current node and the max left and right subtree branches) 56 | return max(left[0], right[0]) + 1, max(left[1], right[1], left[0] + right[0] + 1) 57 | 58 | 59 | ########### 60 | # Testing # 61 | ########### 62 | 63 | # Test 1 64 | # Correct result => 6 65 | tree = TreeNode(3, TreeNode(1, None, TreeNode(2, TreeNode(7))), TreeNode(4, None, TreeNode(5))) 66 | print(diameter(tree)) 67 | 68 | # Test 2 69 | # Correct result => 5 70 | tree = TreeNode(5, TreeNode(3, TreeNode(2, TreeNode(1)), TreeNode(4, None, TreeNode(8))), TreeNode(6)) 71 | print(diameter(tree)) -------------------------------------------------------------------------------- /Trees/find_kth_smallest_node_bst.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Kth Smallest Element in a BST 3 | 4 | Given a binary search tree, write a function kthSmallest to find the kth smallest element in it 5 | 6 | Input: 3 , k = 1 7 | / \ 8 | 1 4 9 | \ 10 | 2 11 | Output: 1 12 | 13 | Input: 5 , k = 3 14 | / \ 15 | 3 6 16 | / \ 17 | 2 4 18 | / 19 | 1 20 | Output: 3 21 | 22 | ========================================= 23 | Traverse Inorder the tree (Type of depth first traversal: left, root, right) and count the nodes. 24 | When the Kth node is found, return that node. 25 | Time Complexity: O(N) 26 | Space Complexity: O(N) , because of the recursion stack (but this is if the tree is one branch), O(LogN) if the tree is balanced. 27 | ''' 28 | 29 | 30 | ############ 31 | # Solution # 32 | ############ 33 | 34 | # import TreeNode class from tree_helpers.py 35 | from tree_helpers import TreeNode 36 | 37 | def find_kth_smallest_node_bst(root, k): 38 | return search(root, k)[1] 39 | 40 | def search(node, k): 41 | if node is None: 42 | return (k, None) 43 | 44 | # check left 45 | left = search(node.left, k) 46 | if left[0] == 0: 47 | return left 48 | 49 | # check current node 50 | k = left[0] - 1 51 | if k == 0: 52 | return (k, node) 53 | 54 | # check right 55 | return search(node.right, k) 56 | 57 | 58 | ########### 59 | # Testing # 60 | ########### 61 | 62 | # Test 1 63 | # Correct result => 9 64 | tree = TreeNode(5, TreeNode(3, TreeNode(1), TreeNode(4)), TreeNode(8, TreeNode(7), TreeNode(12, TreeNode(10, TreeNode(9))))) 65 | print(find_kth_smallest_node_bst(tree, 7).val) 66 | 67 | # Test 2 68 | # Correct result => 5 69 | tree = TreeNode(5, TreeNode(3, TreeNode(1), TreeNode(4)), TreeNode(8, TreeNode(7), TreeNode(12))) 70 | print(find_kth_smallest_node_bst(tree, 4).val) 71 | 72 | # Test 3 73 | # Correct result => 3 74 | tree = TreeNode(5, TreeNode(3, TreeNode(1), TreeNode(4))) 75 | print(find_kth_smallest_node_bst(tree, 2).val) 76 | 77 | # Test 4 78 | # Correct result => 6 79 | tree = TreeNode(5, TreeNode(3, TreeNode(1), TreeNode(4)), TreeNode(8, TreeNode(6, None, TreeNode(7)))) 80 | print(find_kth_smallest_node_bst(tree, 5).val) 81 | 82 | # Test 5 83 | # Correct result => 9 84 | tree = TreeNode(5, TreeNode(3, TreeNode(1), TreeNode(4)), TreeNode(8, TreeNode(7), TreeNode(12, TreeNode(9, None, TreeNode(10, None, TreeNode(11)))))) 85 | print(find_kth_smallest_node_bst(tree, 7).val) -------------------------------------------------------------------------------- /Trees/find_max_branch_sum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find max branch sum 3 | 4 | Wrie a function that takes a binary tree and returns its max branch (branch is "root to leaf path") sum. 5 | 6 | Input: 7 | 1 8 | / \ 9 | 2 3 10 | / \ / \ 11 | 4 5 6 7 12 | Output: 11 13 | Output explanation: 1 -> 3 -> 7 14 | 15 | ========================================= 16 | Traverse the tree and in each node compare the left and right subbranch sum, and take the bigger one. 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) , because of the recursion stack (but this is the tree is one branch), O(LogN) if the tree is balanced. 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | # import TreeNode class from tree_helpers.py 27 | from tree_helpers import TreeNode 28 | 29 | def max_branch_sum(node): 30 | if node is None: 31 | return 0 32 | 33 | # take the max left subbranch sum and add the current node value 34 | left_max_sum = max_branch_sum(node.left) + node.val 35 | # take the max right subbranch sum and add the current node value 36 | right_max_sum = max_branch_sum(node.right) + node.val 37 | 38 | # return the bigger sum 39 | return max(left_max_sum, right_max_sum) 40 | 41 | 42 | ########### 43 | # Testing # 44 | ########### 45 | 46 | # Test 1 47 | # Correct result => 11 48 | tree = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3, TreeNode(6), TreeNode(7))) 49 | print(max_branch_sum(tree)) -------------------------------------------------------------------------------- /Trees/find_max_path_sum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find max path sum 3 | 4 | Wrie a function that takes a Binary Tree and returns its max path sum. 5 | 6 | Input: 7 | 1 8 | / \ 9 | 2 3 10 | / \ / \ 11 | 4 5 6 7 12 | Output: 18 13 | Output explanation: 5 -> 2 -> 1 -> 3 -> 7 14 | 15 | Input: 16 | -1 17 | / \ 18 | -2 3 19 | / \ / \ 20 | -4 -5 2 5 21 | Output: 10 22 | Output explanation: 2 -> 3 -> 5 23 | 24 | ========================================= 25 | Traverse the tree and in each node compare create a new path where the left and right max subpaths are merging in the current node. 26 | Time Complexity: O(N) 27 | Space Complexity: O(N) , because of the recursion stack (but this is the tree is one branch), O(LogN) if the tree is balanced. 28 | ''' 29 | 30 | 31 | ############ 32 | # Solution # 33 | ############ 34 | 35 | # import TreeNode class from tree_helpers.py 36 | from tree_helpers import TreeNode 37 | 38 | def max_path_sum(tree): 39 | return find_max_path_sum(tree)[0] 40 | 41 | def find_max_path_sum(node): 42 | if node is None: 43 | return (0, 0) 44 | 45 | # get the result from the left subtree 46 | left_result = find_max_path_sum(node.left) 47 | # get the result from the right subtree 48 | right_result = find_max_path_sum(node.right) 49 | 50 | # create a new path by merging the max left and right subpaths 51 | current_path = left_result[1] + node.val + right_result[1] 52 | # find the max path till now, comparing the new path, max path from the left and right subtree 53 | max_path = max(left_result[0], current_path, right_result[0]) 54 | # find the max subpath, min value for a subpath sum is 0 55 | max_subpath = max(left_result[1] + node.val, right_result[1] + node.val, node.val, 0) 56 | 57 | return (max_path, max_subpath) 58 | 59 | 60 | ########### 61 | # Testing # 62 | ########### 63 | 64 | # Test 1 65 | # Correct result => 18 66 | tree = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3, TreeNode(6), TreeNode(7))) 67 | print(max_path_sum(tree)) 68 | 69 | # Test 2 70 | # Correct result => 10 71 | tree = TreeNode(-1, TreeNode(-2, TreeNode(-4), TreeNode(-5)), TreeNode(3, TreeNode(2), TreeNode(5))) 72 | print(max_path_sum(tree)) 73 | 74 | # Test 3 75 | ''' 76 | 1 77 | / \ 78 | 7 3 79 | / \ / \ 80 | -4 -5 6 2 81 | ''' 82 | # Correct result => 17 (7 -> 1 -> 3 -> 6) 83 | tree = TreeNode(1, TreeNode(7, TreeNode(-4), TreeNode(-5)), TreeNode(3, TreeNode(6), TreeNode(2))) 84 | print(max_path_sum(tree)) 85 | 86 | # Test 4 87 | ''' 88 | 1 89 | / \ 90 | 2 3 91 | / \ / \ 92 | -4 -5 -2 -3 93 | ''' 94 | # Correct result => 6 (2 -> 1 -> 3) 95 | tree = TreeNode(1, TreeNode(2, TreeNode(-4), TreeNode(-5)), TreeNode(3, TreeNode(-2), TreeNode(-3))) 96 | print(max_path_sum(tree)) -------------------------------------------------------------------------------- /Trees/find_second_largest_node.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Find second largest node (not search tree) 3 | 4 | Given the root to a tree (not bst), find the second largest node in the tree. 5 | 6 | ========================================= 7 | Traverse tree and compare the current value with the saved 2 values. 8 | Time Complexity: O(N) 9 | Space Complexity: O(N) , because of the recursion stack (but this is the tree is one branch), O(LogN) if the tree is balanced. 10 | ''' 11 | 12 | 13 | ############ 14 | # Solution # 15 | ############ 16 | 17 | # import TreeNode class from tree_helpers.py 18 | from tree_helpers import TreeNode 19 | 20 | import math 21 | 22 | def find_second_largest(root): 23 | arr = [TreeNode(-math.inf), TreeNode(-math.inf)] 24 | traverse_tree(root, arr) 25 | if arr[1] == -math.inf: 26 | # the tree has 0 or 1 elements 27 | return None 28 | return arr[1] 29 | 30 | def traverse_tree(node, arr): 31 | if node == None: 32 | return 33 | 34 | if arr[0].val < node.val: 35 | arr[1] = arr[0] 36 | arr[0] = node 37 | elif arr[1].val < node.val: 38 | arr[1] = node 39 | 40 | # search left 41 | traverse_tree(node.left, arr) 42 | # search right 43 | traverse_tree(node.right, arr) 44 | 45 | 46 | ########### 47 | # Testing # 48 | ########### 49 | 50 | # Test 1 51 | # Correct result => 8 52 | tree = TreeNode(1, TreeNode(5, TreeNode(2), TreeNode(8)), TreeNode(4, TreeNode(12), TreeNode(7))) 53 | print(find_second_largest(tree).val) -------------------------------------------------------------------------------- /Trees/populating_next_pointers_tree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Populating Next Right Pointers in Each Node 3 | 4 | You are given a perfect binary tree where all leaves are on the same level, and every parent has two children. 5 | The binary tree has the following definition: 6 | struct Node { 7 | int val; 8 | Node *left; 9 | Node *right; 10 | Node *next; 11 | } 12 | Populate each next pointer to point to its next right node. 13 | If there is no next right node, the next pointer should be set to NULL. 14 | Initially, all next pointers are set to NULL. 15 | 16 | ========================================= 17 | Breadth first (level order) traversal, using queue. 18 | Save the previous node and level and if the current level is same 19 | then make the previous node to point to the current node. 20 | Time Complexity: O(N) 21 | Space Complexity: O(N) 22 | ''' 23 | 24 | 25 | ############ 26 | # Solution # 27 | ############ 28 | 29 | from collections import deque 30 | 31 | # Definition for a Node. 32 | class Node: 33 | def __init__(self, val, left, right, next): 34 | self.val = val 35 | self.left = left 36 | self.right = right 37 | self.next = next 38 | 39 | def populating_next_pointers_tree(root): 40 | previous = None 41 | queue = deque() 42 | queue.append((root, 0)) 43 | 44 | while queue: 45 | el = queue.popleft() 46 | node = el[0] 47 | lvl = el[1] 48 | 49 | if node is None: 50 | continue 51 | 52 | if (previous is not None) and (lvl == previous[1]): 53 | previous[0].next = node 54 | 55 | previous = (node, lvl) 56 | 57 | lvl += 1 58 | queue.append((node.left, lvl)) 59 | queue.append((node.right, lvl)) 60 | 61 | return root -------------------------------------------------------------------------------- /Trees/same_tree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Same Tree 3 | 4 | Given two binary trees, write a function to check if they are the same or not. 5 | Two binary trees are considered the same if they are structurally identical and the nodes have the same value. 6 | 7 | Input: 1 1 8 | / \ / \ 9 | 2 3 2 3 10 | Output: True 11 | 12 | Input: 1 1 13 | / \ 14 | 2 2 15 | Output: False 16 | 17 | ========================================= 18 | Traverse both trees in same time and if something isn't equal, return False. 19 | Time Complexity: O(N) 20 | Space Complexity: O(N) , because of the recursion stack (but this is if the tree is one branch), O(LogN) if the tree is balanced. 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | # import TreeNode class from tree_helpers.py 29 | from tree_helpers import TreeNode 30 | 31 | def is_same_tree(p, q): 32 | if (p is None) and (p == q): 33 | return True 34 | 35 | if (p is None) or (q is None): 36 | return False 37 | 38 | if p.val != q.val: 39 | return False 40 | 41 | # check left 42 | if not is_same_tree(p.left, q.left): 43 | return False 44 | 45 | # check right 46 | if not is_same_tree(p.right, q.right): 47 | return False 48 | 49 | return True 50 | 51 | 52 | ########### 53 | # Testing # 54 | ########### 55 | 56 | # Test 1 57 | # Correct result => True 58 | p = TreeNode(1, TreeNode(2), TreeNode(3)) 59 | q = TreeNode(1, TreeNode(2), TreeNode(3)) 60 | print(is_same_tree(p, q)) 61 | 62 | # Test 2 63 | # Correct result => False 64 | p = TreeNode(1, TreeNode(2)) 65 | q = TreeNode(1, None, TreeNode(2)) 66 | print(is_same_tree(p, q)) -------------------------------------------------------------------------------- /Trees/tree_helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers used in tree solutions. 3 | This file is created to avoid duplicate code in tree solutions. 4 | ''' 5 | 6 | class TreeNode: 7 | def __init__(self, val, left=None, right=None): 8 | '''Definition for binary tree.''' 9 | self.val = val 10 | self.left = left 11 | self.right = right -------------------------------------------------------------------------------- /Trees/unival_trees.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Unival Trees 3 | 4 | A unival tree (which stands for "universal value") is a tree where all nodes under it have the same value. 5 | Given the root to a binary tree, count the number of unival subtrees. 6 | 7 | Input: 8 | 0 9 | / \ 10 | 1 0 11 | / \ 12 | 1 0 13 | / \ 14 | 1 1 15 | Output: 5 16 | 17 | ========================================= 18 | Simple tree traversal solution. 19 | Time Complexity: O(N) 20 | Space Complexity: O(N) , because of the recursion stack (but this is if the tree is one branch), O(LogN) if the tree is balanced. 21 | ''' 22 | 23 | 24 | ############ 25 | # Solution # 26 | ############ 27 | 28 | # import TreeNode class from tree_helpers.py 29 | from tree_helpers import TreeNode 30 | 31 | def count_unival_trees(tree): 32 | if tree is None: 33 | return 0 34 | return total_unival_trees(tree)[0] 35 | 36 | def total_unival_trees(node): 37 | left_value = None 38 | is_left_unival_tree = True 39 | right_value = None 40 | is_right_unival_tree = True 41 | unival_trees = 0 42 | 43 | # count left unival trees and save the value 44 | if node.left is not None: 45 | left_result = total_unival_trees(node.left) 46 | unival_trees += left_result[0] 47 | is_left_unival_tree = left_result[1] 48 | left_value = node.left.val 49 | 50 | # count right unival trees and save the value 51 | if node.right is not None: 52 | right_result = total_unival_trees(node.right) 53 | unival_trees += right_result[0] 54 | is_right_unival_tree = right_result[1] 55 | right_value = node.right.val 56 | 57 | # check if this root is an unival tree 58 | is_this_unival_tree = is_left_unival_tree and is_right_unival_tree and (left_value == right_value) 59 | unival_trees += is_this_unival_tree 60 | 61 | return (unival_trees, is_this_unival_tree) 62 | 63 | 64 | ########### 65 | # Testing # 66 | ########### 67 | 68 | # Test 1 69 | # Correct result => 5 70 | print(count_unival_trees(TreeNode(0, TreeNode(1), TreeNode(0, TreeNode(1, TreeNode(1), TreeNode(1)), TreeNode(0))))) -------------------------------------------------------------------------------- /Trees/valid_bst.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Valid binary search tree 3 | 4 | Check if a given tree is a valid binary search tree. 5 | 6 | Input: 5 7 | / \ 8 | 1 7 9 | / \ 10 | 6 8 11 | Output: True 12 | 13 | ========================================= 14 | Visit all nodes and check if the values are inside the boundaries. 15 | When visiting the left child use the value of the parent node like an upper boundary. 16 | When visiting the right child use the value of the parent node like a lower boundary. 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) , because of the recursion stack (but this is the tree is one branch), O(LogN) if the tree is balanced. 19 | ''' 20 | 21 | ############ 22 | # Solution # 23 | ############ 24 | 25 | # import TreeNode class from tree_helpers.py 26 | from tree_helpers import TreeNode 27 | 28 | import math 29 | 30 | def is_valid_bst(root): 31 | return is_valid_sub_bst(root, -math.inf, math.inf) 32 | 33 | def is_valid_sub_bst(node, lower, upper): 34 | if node is None: 35 | return True 36 | 37 | if (node.val <= lower) or (node.val >= upper): 38 | return False 39 | 40 | # check left 41 | if not is_valid_sub_bst(node.left, lower, node.val): 42 | return False 43 | 44 | # check right 45 | if not is_valid_sub_bst(node.right, node.val, upper): 46 | return False 47 | 48 | return True 49 | 50 | 51 | ########### 52 | # Testing # 53 | ########### 54 | 55 | # Test 1 56 | ''' 57 | 5 58 | / \ 59 | 1 4 60 | / \ 61 | 3 6 62 | ''' 63 | # Correct result => False 64 | root = TreeNode(5, TreeNode(1), TreeNode(4, TreeNode(3), TreeNode(6))) 65 | print(is_valid_bst(root)) 66 | 67 | # Test 2 68 | ''' 69 | 5 70 | / \ 71 | 1 6 72 | / \ 73 | 4 7 74 | ''' 75 | # Correct result => False 76 | root = TreeNode(5, TreeNode(1), TreeNode(6, TreeNode(4), TreeNode(7))) 77 | print(is_valid_bst(root)) 78 | 79 | # Test 3 80 | ''' 81 | 5 82 | / \ 83 | 1 6 84 | / \ 85 | 7 8 86 | ''' 87 | # Correct result => False 88 | root = TreeNode(5, TreeNode(1), TreeNode(6, TreeNode(7), TreeNode(8))) 89 | print(is_valid_bst(root)) 90 | 91 | # Test 4 92 | ''' 93 | 5 94 | / \ 95 | 1 7 96 | / \ 97 | 6 8 98 | ''' 99 | # Correct result => True 100 | root = TreeNode(5, TreeNode(1), TreeNode(7, TreeNode(6), TreeNode(8))) 101 | print(is_valid_bst(root)) -------------------------------------------------------------------------------- /Trees/zigzag_level_order_traversal.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Binary Tree Zigzag Level Order Traversal 3 | 4 | Given a binary tree, return the zigzag level order traversal of its nodes' values. 5 | (ie, from left to right, then right to left for the next level and alternate between). 6 | 7 | Input: 3 8 | / \ 9 | 9 20 10 | / \ 11 | 15 7 12 | Output: [[3], [20, 9], [15, 7]] 13 | 14 | ========================================= 15 | Breadth first (level order) traversal, using queue. 16 | In the end reverse each odd level. 17 | Time Complexity: O(N) 18 | Space Complexity: O(N) 19 | ''' 20 | 21 | 22 | ############ 23 | # Solution # 24 | ############ 25 | 26 | # import TreeNode class from tree_helpers.py 27 | from tree_helpers import TreeNode 28 | 29 | from collections import deque 30 | 31 | class TreeNode: 32 | def __init__(self, val, left=None, right=None): 33 | self.val = val 34 | self.left= left 35 | self.right = right 36 | 37 | def zigzag_level_order_traversal(root): 38 | results = [] 39 | queue = deque() 40 | # save nodes and levels in queue 41 | queue.append((root, 0)) 42 | 43 | while queue: 44 | node, lvl = queue.popleft() 45 | 46 | if node is None: 47 | continue 48 | 49 | if len(results) < lvl + 1: 50 | results.append([]) 51 | results[lvl].append(node.val) 52 | 53 | lvl += 1 54 | queue.append((node.left, lvl)) 55 | queue.append((node.right, lvl)) 56 | 57 | # reverse odd level 58 | for i in range(1, len(results), 2): 59 | results[i] = results[i][::-1] 60 | 61 | return results 62 | 63 | 64 | ########### 65 | # Testing # 66 | ########### 67 | 68 | # Test 1 69 | # Correct result => [[3], [20, 9], [15, 7]] 70 | tree = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7))) 71 | print(zigzag_level_order_traversal(tree)) --------------------------------------------------------------------------------