├── .github └── pull_request_template.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── algorithms ├── bit_manipulation │ ├── range_sum_set_bits.py │ └── sum_of_two_integers.py ├── dynamic_programming │ ├── 01_knapsack.py │ ├── Z_Algorithm.py │ ├── coin_change.py │ ├── hamilton_cycle.py │ ├── longest_common_subsequence.py │ ├── longest_common_substring.py │ ├── longest_consecutive_subsequence.py │ ├── longest_increasing_consecutive_subsequence.py │ ├── longest_increasing_subsequence.py │ ├── longest_palindromic_substring.py │ ├── longest_subarray_sum_divisible_by_k.py │ ├── longest_subarray_with_no_pairsum_divisible_by_k.py │ ├── partition_sum.py │ ├── prefix_function.py │ └── prefix_sums.py ├── greedy │ ├── activity_selection.py │ ├── cost_of_tiles.py │ ├── dijkstra_greedy.py │ ├── egyptian_fraction.py │ └── min_platforms.py ├── math │ ├── divisors.py │ ├── factorial_iterative.py │ ├── factorial_recursive.py │ ├── fibonacci_number.py │ ├── greatest_common_divisor.py │ ├── index.md │ ├── least_common_multiple.py │ ├── modular_exponentiation.py │ ├── number_convertion.py │ ├── perfect_square.py │ ├── power_of_two.py │ ├── prime.py │ ├── recursive_fibonacci.py │ ├── sieve_of_eratosthenes.py │ └── sumofdigits.py ├── miscellaneous │ ├── front_and_back_search.py │ ├── index.md │ ├── luhn_algorithm.py │ └── markov.py └── sorting │ ├── bubble_sort.py │ ├── counting_sort.py │ ├── gnome_sort.py │ ├── heap_sort.py │ ├── index.md │ ├── insertion_sort.py │ ├── merge_sort.py │ ├── permutation_sort.py │ ├── pigeonhole_sort.py │ ├── qsort.py │ ├── select_sort.py │ └── shell_sort.py ├── bookmarks ├── articles.md ├── books.md ├── database.md ├── misc.md ├── topics.md └── tutorials.md ├── data_structures ├── array │ ├── all_numbers_divisible.py │ ├── binary_search_infinite_array.py │ ├── duplicate.py │ ├── duplicates.py │ ├── dutch_flag_problem.py │ ├── equilibrium_index.py │ ├── even_more_than_odd.py │ ├── find_common_in_three_arrays.py │ ├── find_given_sum_in_array.py │ ├── find_missing_numbers.py │ ├── find_odd_number.py │ ├── find_sum.py │ ├── first_repeating_char.py │ ├── flatten_array.py │ ├── index.md │ ├── intersection_sorted_array.py │ ├── kadane_algorithm.py │ ├── largest_element.py │ ├── majority_element.py │ ├── max_consecutive_ones.py │ ├── max_product.py │ ├── max_product_three_elements.py │ ├── max_triplet_sum.py │ ├── min_product.py │ ├── min_swaps.py │ ├── moves_zeros_to_end.py │ ├── number_of_1_in_sorted_array.py │ ├── number_of_elements_that_can_searched_using_binary_search.py │ ├── partition_three_parts_equal_sum.py │ ├── peak_element.py │ ├── permutations_of_word.py │ ├── pivot_index.py │ ├── product_of_array_except_self.py │ ├── quick_sort.py │ ├── random_matrix.py │ ├── rearrange_positive_negative.py │ ├── right_place.py │ ├── rotation.py │ ├── rotation_simplified.py │ ├── sort_by_parity.py │ ├── sorted_array_three_common_elements.py │ ├── square_of_sorted_array.py │ ├── three_largest_elements.py │ ├── transpose_matrix.py │ ├── triplet_sum.py │ └── union_sorted_array.py ├── binary_trees │ ├── array_to_binary_tree.py │ ├── boundary_traversal.py │ ├── check_all_leaves_at_same_level.py │ ├── check_cousin.py │ ├── check_divide_in_two_halves.py │ ├── check_full_binary_tree.py │ ├── check_if_path_exists.py │ ├── check_perfect_binary_tree.py │ ├── children_sum_property.py │ ├── continuous_tree.py │ ├── diagonal_tree.py │ ├── diameter.py │ ├── evaluate_expresssion_tree.py │ ├── flip_tree.py │ ├── foldable_or_symmetric_tree.py │ ├── height_of_tree.py │ ├── identical_trees.py │ ├── largest_subtree_sum.py │ ├── left_right_to_down_right.py │ ├── left_view.py │ ├── logical_and_tree.py │ ├── longest_path_with_same_value.py │ ├── maximum_left_node.py │ ├── perfect_tree.py │ ├── print_all_root_to_leaf_paths.py │ ├── print_full_nodes.py │ ├── print_nodes_at_k_distance_from_root.py │ ├── print_nodes_with_no_sibling.py │ ├── print_odd_level_nodes.py │ ├── print_path_to_a_node.py │ ├── print_spiral_tree_two_stacks.py │ ├── right_view.py │ ├── spiral_tree.py │ ├── sum_of_all_left_leaves.py │ ├── sum_of_all_nodes.py │ ├── sum_of_perfect_binary_tree.py │ ├── sum_predecessor_successor.py │ ├── top_view.py │ ├── traversals.py │ └── vertical_traversal.py ├── bst │ ├── average_of_levels.py │ ├── bfs.py │ ├── binary_search_tree.py │ ├── bt_to_bst.py │ ├── ceil.py │ ├── check_bt_is_subtree_of_another_bt.py │ ├── check_if_bt_if_bst.py │ ├── closest_element.py │ ├── convert_bst_to_right_node_tree.py │ ├── deletion.py │ ├── dfs_iterative.py │ ├── dfs_recursion.py │ ├── diameter.py │ ├── duplicate_keys.py │ ├── insertion_iterative.py │ ├── insertion_recursive.py │ ├── kth_largest_in_bst.py │ ├── kth_smallest_in_bst.py │ ├── linked_list_to_bst.py │ ├── lowest_common_ancestor.py │ ├── merge_sum.py │ ├── min_max_value_in_bst.py │ ├── print_ancestor.py │ ├── print_left_node.py │ ├── range_sum.py │ ├── reverse_inorder_traversal.py │ ├── search.py │ ├── second_largest_in_bst.py │ ├── sorted_array_to_bst.py │ └── trim_bst.py ├── circular_linked_list │ ├── check_circular_linked_list.py │ ├── delete.py │ └── traversal.py ├── deque │ └── deque.py ├── doubly_linked_list │ └── doubly_linked_list.py ├── fenwick_tree │ └── fenwick_tree.py ├── graphs │ ├── adjacency_list.py │ ├── adjacency_matrix.py │ ├── all_paths_between_two_vertices.py │ ├── bellman_ford.py │ ├── bfs.py │ ├── bipartite_graph.py │ ├── check_if_graph_is_tree.py │ ├── connected_components_undirected_graphs.py │ ├── count_edges.py │ ├── count_paths_between_nodes.py │ ├── count_sink_nodes.py │ ├── count_trees.py │ ├── cycle_in_directed_graph.py │ ├── cycle_in_directed_graph_iterative.py │ ├── cycle_in_directed_graph_using_colors.py │ ├── cycle_in_directed_graph_using_colors_recursive.py │ ├── cycle_in_undirected_graph.py │ ├── cycle_in_undirected_graph_iterative.py │ ├── cycle_in_undirected_graph_union_find.py │ ├── dag_longest_path.py │ ├── dag_shortest_path.py │ ├── dfs.py │ ├── dijsktra_algorithm.py │ ├── iterative_dfs.py │ ├── kosaraju_algorithm.py │ ├── level_of_nodes.py │ ├── longest_path.py │ ├── max_edges_that_can_be_added_to_dag.py │ ├── max_edges_to_make_bipartite.py │ ├── min_edges_between_two_vertices.py │ ├── min_nodes_to_reach_all_nodes.py │ ├── min_number_of_operations.py │ ├── mother_vertex.py │ ├── not_reachable_nodes.py │ ├── number_of_triangles.py │ ├── print_all_paths_between_nodes.py │ ├── root_which_gives_min_height.py │ ├── same_path.py │ ├── same_path_recursive.py │ ├── shortest_path_unweighted_graph.py │ ├── topological_sort.py │ ├── transpose.py │ ├── tree_stays_bipartite.py │ └── two_cliques.py ├── hash │ └── hash_table.py ├── heap │ ├── heap_using_heapq.py │ ├── kth_largest_element_in_stream.py │ ├── max_heap.py │ ├── median_of_infinite_stream.py │ ├── min_heap.py │ └── sum_elements_range.py ├── linked_list │ ├── cycle_detection.py │ ├── delete_last_occurrence.py │ ├── index.md │ ├── linked_list.py │ ├── merge_linked_list.py │ ├── merge_list_of_linked_lists.py │ ├── odd_even_arrangement.py │ ├── pair_swap.py │ ├── remove.py │ ├── remove_duplicates.py │ ├── remove_nth_node_from_end.py │ ├── reverse.py │ └── sum_of_two_numbers.py ├── matrix │ ├── find_in_row_and_column_wise_sorted_matrix.py │ ├── find_pair.py │ └── flood_fill_algorithm.py ├── palindromic_tree │ └── palindromic_tree.py ├── queue │ └── queue.py ├── segment_tree │ ├── index.md │ ├── min_in_range_queries.py │ ├── seg_tree_max.py │ └── seg_tree_sum.py ├── stack │ ├── balanced_expression.py │ ├── index.md │ ├── largest_rectangle_area_in_histogram.py │ ├── remove_duplicates_adjacent.py │ ├── stack_using_linked_list.py │ ├── stack_using_list.py │ ├── valid_parenthesis.py │ └── validate_stack_sequence.py ├── strings │ ├── KMP_Pattern_Search.py │ ├── adjacent_vowel_pairs.py │ ├── are_anagrams.py │ ├── check_interleaving_of_strings.py │ ├── check_permutations.py │ ├── finding_all_substrings.py │ ├── index.md │ ├── one_away.py │ ├── reverse_with_special_chars.py │ ├── roman_to_integer.py │ ├── rotate_string.py │ ├── toggle_string.py │ └── unique_char_check.py ├── trie │ └── trie.py └── union_find │ └── uf.py └── notes.md /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed or what question/feature you have added. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature/question 13 | - [ ] This change requires a documentation update 14 | - [ ] Bookmark link 15 | 16 | # Checklist: 17 | 18 | - [ ] My code follows the style guidelines of this project i.e. [Pep8](https://www.python.org/dev/peps/pep-0008/) 19 | - [ ] I have performed a self-review of my own code 20 | - [ ] I have commented my code, particularly in hard-to-understand areas 21 | - [ ] I have made corresponding changes to the documentation 22 | - [ ] Any dependent changes have been merged and published in downstream modules 23 | - [ ] I have squashed unnecessary commits 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | questions_to_do.txt 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Prabhu Pant 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 | -------------------------------------------------------------------------------- /algorithms/bit_manipulation/range_sum_set_bits.py: -------------------------------------------------------------------------------- 1 | # Question: Find the sum of number of set bits in all the numbers in the range [1, n]. 2 | 3 | def countBits(n): 4 | 5 | """ Consider a number x and half of the number (x//2). 6 | The binary representation of x has all the digits as 7 | the binary representation of x//2 followed by an additional 8 | digit at the last position. Therefore, we can find the number 9 | of set bits in x by finding the number of set bits in x//2 10 | and determining whether the last digit in x is 0 or 1. """ 11 | 12 | res = [0] * (n+1) 13 | for i in range(1, n+1): 14 | res[i] = res[i//2] + (i & 1) 15 | return sum(res) 16 | 17 | 18 | # Extension: Find the sum of number of set bits in all the numbers in the range [m, n]. 19 | # Answer: In countBits(m, n), return sum(res) - sum(res[:m]) -------------------------------------------------------------------------------- /algorithms/bit_manipulation/sum_of_two_integers.py: -------------------------------------------------------------------------------- 1 | # Question: Calculate the sum of two integers a and b but without the 2 | # use of the operators + and -. 3 | 4 | #Solution 5 | def getSum(a, b): 6 | """ 7 | :type a: int 8 | :type b: int 9 | :rtype: int 10 | """ 11 | 12 | mask = 0xffffffff 13 | diff = 0 14 | carry = 0 15 | while b & mask: 16 | diff = a ^ b 17 | carry = trunc(a & b) 18 | carry = carry << 1 19 | a = diff 20 | b = carry 21 | if b > 0: return (a & mask) 22 | else: return a -------------------------------------------------------------------------------- /algorithms/dynamic_programming/01_knapsack.py: -------------------------------------------------------------------------------- 1 | def knapsack(values, weights, total): 2 | total_items = len(weights) 3 | 4 | rows = total_items + 1 5 | cols = total + 1 6 | 7 | # rows are the number of items 8 | # columns are the values of weights required 9 | 10 | t = [[0 for i in range(cols)] for i in range(rows)] 11 | 12 | for i in range(1, rows): 13 | for j in range(1, cols): 14 | if j < weights[i-1]: 15 | t[i][j] = t[i-1][j] 16 | else: 17 | t[i][j] = max(t[i-1][j], values[i-1] + t[i-1][j-weights[i-1]]) 18 | 19 | return t[rows-1][cols-1] 20 | 21 | 22 | weights = [1,3,4,5] 23 | values = [1,4,5,7] 24 | 25 | ans = knapsack(values, weights, 7) 26 | print(ans) 27 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/Z_Algorithm.py: -------------------------------------------------------------------------------- 1 | class Z_Algorithm(): 2 | """ 3 | return all occurences of string s in text, returns its indexes starting from zero 4 | delimeter should be charachter which will not occur neither is S nor in text 5 | by default its '$' 6 | """ 7 | @staticmethod 8 | def find_occurrences(s:str , text:str , delimeter = '$'): 9 | return Z_Algorithm.z_function(s + delimeter + text , len(s)) 10 | 11 | @staticmethod 12 | def z_function(text:str , size:int): 13 | l = 0 14 | r = 0 15 | z = [0] * len(text) 16 | for i in range(1 , len(text)): 17 | if i <= r: 18 | z[i] = min(r - i + 1 , z[i - l]) 19 | while i + z[i] < len(text) and text[z[i]] == text[i + z[i]]: 20 | z[i] += 1 21 | 22 | if i + z[i] - 1 > r: 23 | l = i 24 | r = i + z[i] - 1 25 | 26 | res = [] 27 | for i in range(size , len(text)): 28 | if z[i] == size: 29 | res.append(i - size - 1) 30 | return res -------------------------------------------------------------------------------- /algorithms/dynamic_programming/coin_change.py: -------------------------------------------------------------------------------- 1 | 2 | def min_coins(coins, total): 3 | cols = total + 1 4 | 5 | min_coins = [float('inf')] * (total + 1) 6 | coins_used = [-1] * (total + 1) 7 | 8 | min_coins[0] = 0 # to form 0, we need 0 coins 9 | 10 | for i in range(0, len(coins)): 11 | for j in range(1, len(min_coins)): 12 | if coins[i] > j: # if the coin value is more than j (curr total), ignore it 13 | continue 14 | 15 | if (1 + min_coins[j - coins[i]]) < min_coins[j]: 16 | min_coins[j] = 1 + min_coins[j - coins[i]] 17 | coins_used[j] = i 18 | 19 | # finding which coins were used 20 | picked_coins = [] 21 | while total > 0: 22 | index_of_coin_used = coins_used[total] 23 | coin = coins[index_of_coin_used] 24 | picked_coins.append(coin) 25 | total -= coin 26 | 27 | print('Min coins needed - ', min_coins[-1]) 28 | print('Coins used - ', picked_coins) 29 | 30 | total = 11 31 | coins = [9, 6, 5, 1] 32 | 33 | min_coins(coins, total) 34 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/hamilton_cycle.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | def hamilton_cycle(graph, n): 4 | height = 1 << n 5 | 6 | dp = [[False for _ in range(n)] for _ in range(height)] 7 | for i in range(n): 8 | dp[1 << i][i] = True 9 | 10 | for i in range(height): 11 | ones, zeros = [], [] 12 | for pos in range(n): 13 | if (1 << pos) & i: 14 | ones.append(pos) 15 | else: 16 | zeros.append(pos) 17 | 18 | for o in ones: 19 | if not dp[i][o]: 20 | continue 21 | 22 | for z in zeros: 23 | if graph[o][z]: 24 | new_val = i + (1 << z) 25 | dp[new_val][z] = True 26 | 27 | return functools.reduce(lambda a, b: a or b, dp[height - 1]) -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_common_subsequence.py: -------------------------------------------------------------------------------- 1 | def lcs(s1, s2): 2 | cols = len(s1) + 1 3 | rows = len(s2) + 1 4 | 5 | t = [[0 for i in range(cols)] for i in range(rows)] 6 | 7 | max_length = 0 8 | 9 | for i in range(1, rows): 10 | for j in range(1, cols): 11 | if s2[i-1] == s1[j-1]: 12 | t[i][j] = 1 + t[i-1][j-1] 13 | else: 14 | t[i][j] = max(t[i-1][j], t[i][j-1]) 15 | 16 | max_length = max(max_length, t[i][j]) 17 | 18 | return max_length 19 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_common_substring.py: -------------------------------------------------------------------------------- 1 | def lcs(s1, s2): 2 | cols = len(s1) + 1 3 | rows = len(s2) + 1 4 | 5 | t = [[0 for i in range(cols)] for i in range(rows)] 6 | 7 | max_length = 0 8 | 9 | for i in range(1, rows): 10 | for j in range(1, cols): 11 | if s2[i-1] == s1[j-1]: 12 | t[i][j] = t[i-1][j-1] + 1 13 | max_length = max(max_length, t[i][j]) 14 | 15 | return max_length 16 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_consecutive_subsequence.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given an array of integers, find the length of the longest sub-sequence 3 | such that elements in the subsequence are consecutive integers, the 4 | consecutive numbers can be in any order. 5 | 6 | The idea is to store all the elements in a set first. Then as we are iterating 7 | over the array, we check two things - 8 | 1. a number x can be a starting number in a sequence if x-1 is not present in the 9 | set. If this is the case, create a loop and check how many elements from x to x+j are 10 | in the set 11 | 2. if x -1 is there in the set, do nothing as this number is not a starting element 12 | and must have been considered in a different sequence 13 | """ 14 | 15 | def find_seq(arr, n): 16 | s = set() 17 | 18 | for num in arr: 19 | s.add(num) 20 | 21 | ans = 0 22 | elements = [] 23 | 24 | for i in range(n): 25 | temp = [] 26 | 27 | if arr[i] - 1 not in s: 28 | j = arr[i] 29 | 30 | while j in s: 31 | temp.append(j) 32 | j += 1 33 | 34 | if j - arr[i] > ans: 35 | ans = j - arr[i] 36 | elements = temp.copy() 37 | 38 | return ans, elements 39 | 40 | 41 | arr = [36, 41, 56, 35, 44, 33, 34, 92, 43, 32, 42] 42 | 43 | ans, elements = find_seq(arr, len(arr)) 44 | print('Length - ', ans) 45 | print('Elements - ', elements) 46 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_increasing_consecutive_subsequence.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the longest increasing consecutive subsequence in an array 3 | 4 | Idea - 5 | 6 | create a dictionary 'seq' and start iterating over the array 7 | 8 | 1. if arr[i] - 1 exists in the array, length = length + seq[arr[i] - 1] 9 | 2. else, seq[i] = 1 10 | """ 11 | 12 | def find_seq(arr): 13 | seq = {} 14 | count = 0 15 | 16 | for num in arr: 17 | if num - 1 in seq: 18 | seq[num] = seq[num - 1] + 1 19 | count = max(count, seq[num]) 20 | else: 21 | seq[num] = 1 22 | 23 | return count 24 | 25 | arr = [6, 7, 8, 3, 4, 5, 9, 10] 26 | print(find_seq(arr)) 27 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_increasing_subsequence.py: -------------------------------------------------------------------------------- 1 | def LIS(arr): 2 | n = len(arr) 3 | if n == 0: return 0 4 | res = 1 5 | dp = [0] * n 6 | dp[0] = 1 7 | for i in range(1, n): 8 | dp[i] = 1; 9 | for j in range(0 , i): 10 | if arr[i] > arr[j]: 11 | dp[i] = max(dp[i] , dp[j] + 1) 12 | res = max(res , dp[i]) 13 | return res -------------------------------------------------------------------------------- /algorithms/dynamic_programming/longest_subarray_with_no_pairsum_divisible_by_k.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the longest subarray in the input array such that the pairwise sum of 3 | the elements of this subarray is not divisible by K 4 | 5 | The idea is - 6 | How can we tell that two numbers x and y will make a pairsum that will be 7 | divisible by K just by looking at their remainders? There can be two conditions 8 | 9 | 1. It will be only possible if the sum of the remainders when x and y are 10 | divided by K is equal to K. As the sum of the remainders cannot exceed K 11 | so if it reaches K then it means that the sum of those numbers will also be 12 | divisible by K 13 | 0 < (X%K) + (Y%K) <= K 14 | 15 | 2. If arr[i] % k == 0 and there is also an element j such that arr[j] % k == 0 16 | and 0 exists in the hash (i.e hash[j] = True) 17 | """ 18 | 19 | def find_subarray(arr, k): 20 | """ 21 | True means divisible by k 22 | """ 23 | start, end = 0, 0 24 | max_start, max_end = 0, 0 25 | 26 | n = len(arr) 27 | mod_arr = [0] * n 28 | 29 | mod_arr[arr[0] % k] = mod_arr[arr[0] % k] + 1 30 | 31 | for i in range(1, n): 32 | mod = arr[i] % k 33 | 34 | while (mod_arr[k - mod] != 0) or (mod == 0 and mod_arr[mod] != 0): 35 | mod_arr[arr[start] % k] = mod_arr[arr[start] % k] - 1 36 | start += 1 37 | 38 | mod_arr[mod] = mod_arr[mod] + 1 39 | end += 1 40 | 41 | if (end - start) > (max_end - max_start): 42 | max_end = end 43 | max_start = start 44 | 45 | print(f'Max size is {max_end - max_start}') 46 | 47 | for i in (max_start, max_end + 1): 48 | print(arr[i], end=" ") 49 | 50 | 51 | arr = [3, 7, 1, 9, 2] 52 | k = 3 53 | find_subarray(arr, k) 54 | 55 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/partition_sum.py: -------------------------------------------------------------------------------- 1 | # A Dynamic Programming based 2 | # Python3 program to partition problem 3 | 4 | # Returns true if arr[] can be partitioned 5 | # in two subsets of equal sum, otherwise false 6 | def find_partiion(arr, n) : 7 | sum = 0 8 | 9 | # Calculate sum of all elements 10 | for i in range(n) : 11 | sum += arr[i] 12 | if (sum % 2 != 0) : 13 | return 0 14 | part = [0] * ((sum // 2) + 1) 15 | 16 | # Initialize the part array as 0 17 | for i in range((sum // 2) + 1) : 18 | part[i] = 0 19 | 20 | # Fill the partition table in bottom up manner 21 | for i in range(n) : 22 | 23 | # the element to be included 24 | # in the sum cannot be 25 | # greater than the sum 26 | for j in range(sum // 2, arr[i] - 1, -1) : 27 | 28 | # check if sum - arr[i] 29 | # could be formed 30 | # from a subset 31 | # using elements 32 | # before index i 33 | if (part[j - arr[i]] == 1 or j == arr[i]) : 34 | part[j] = 1 35 | 36 | return part[sum // 2] 37 | 38 | # Drive code 39 | arr = [ 1, 3, 3, 2, 3, 2 ] 40 | n = len(arr) 41 | 42 | # Function call 43 | if (find_partiion(arr, n) == 1) : 44 | print("Can be divided into two subsets of equal sum") 45 | else : 46 | print("Can not be divided into two subsets of equal sum") 47 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/prefix_function.py: -------------------------------------------------------------------------------- 1 | 2 | def prefix_function(s: str) -> [int]: 3 | """ 4 | The prefix function for string s is defined as an array pi of length n, 5 | where pi[i] is the length of the longest proper prefix of the substring 6 | s[0...i] which is also a suffix of this substring. A proper prefix of a 7 | string is a prefix that is not equal to the string itself. 8 | By definition, pi[0] = 0. 9 | """ 10 | n = len(s) 11 | pi = [0] * n 12 | for i in range(1, n): 13 | j = pi[i - 1] 14 | while (j > 0) and (s[i] != s[j]): 15 | j = pi[j - 1] 16 | if s[i] == s[j]: 17 | j += 1 18 | pi[i] = j 19 | return pi 20 | -------------------------------------------------------------------------------- /algorithms/dynamic_programming/prefix_sums.py: -------------------------------------------------------------------------------- 1 | def prefix_sums(ls: [int]) -> [int]: 2 | """ 3 | Returns list of prefix sums for given list of integers. 4 | """ 5 | n = len(ls) 6 | total = 0 7 | sums = [0] * n 8 | for i in range(n): 9 | total += ls[i] 10 | sums[i] = total 11 | return sums 12 | -------------------------------------------------------------------------------- /algorithms/greedy/activity_selection.py: -------------------------------------------------------------------------------- 1 | #Prints a maximum set of activities that can be done by a 2 | #single person, one at a time 3 | #n --> Total number of activities 4 | #s[]--> An array that contains start time of all activities 5 | #f[] --> An array that contains finish time of all activities 6 | 7 | 8 | def find_activities(arr): 9 | n = len(arr) 10 | selected = [] 11 | 12 | arr.sort(key = lambda x: x[1]) 13 | 14 | i = 0 15 | 16 | # since it is a greedy algorithm, the first acitivity is always 17 | # selected because it is the most optimal choice at that point 18 | selected.append(arr[i]) 19 | 20 | for j in range(1, n): 21 | start_time_next_activity = arr[j][0] 22 | end_time_prev_activity = arr[i][1] 23 | 24 | if start_time_next_activity >= end_time_prev_activity: 25 | selected.append(arr[j]) 26 | i = j 27 | 28 | return selected 29 | 30 | 31 | arr = [[5, 9], [1, 2], [3, 4], [0, 6],[5, 7], [8, 9]] 32 | print(find_activities(arr)) 33 | 34 | -------------------------------------------------------------------------------- /algorithms/greedy/cost_of_tiles.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the min cost of tiles to cover a floor. 3 | Floor is represented by 2D array where - 4 | * = tile already placed 5 | . = no tile 6 | 7 | tiles available are 1*1 and 1*2 and their costs 8 | are A and B 9 | 10 | Source - https://www.geeksforgeeks.org/minimize-cost-to-cover-floor-using-tiles-of-dimensions-11-and-12/ 11 | """ 12 | 13 | def cost(arr, A, B): 14 | n = len(arr) 15 | m = len(arr[0]) 16 | 17 | ans = 0 18 | 19 | for i in range(n): 20 | j = 0 21 | 22 | while j < m: 23 | if arr[i][j] == '*': # tile is already there 24 | j += 1 25 | continue 26 | 27 | if j == m - 1: # if j is pointing to last tile, you can use only 1*1 tile 28 | ans += A 29 | else: 30 | if arr[i][j+1] == '.': 31 | ans += min(2 * A, B) 32 | j += 1 33 | else: 34 | ans += A 35 | 36 | j += 1 37 | 38 | print('Cost of tiling is - ', ans) 39 | 40 | arr = [ [ '.', '.', '*' ], 41 | [ '.', '*', '*' ] ] 42 | 43 | A, B = 2, 10 44 | 45 | cost(arr, A, B) 46 | -------------------------------------------------------------------------------- /algorithms/greedy/dijkstra_greedy.py: -------------------------------------------------------------------------------- 1 | def dijkstra(graph, start, end): 2 | shortest_distance = {} 3 | non_visited_nodes = {} 4 | for i in graph: 5 | non_visited_nodes[i] = graph[i] 6 | 7 | infinit = float('inf') 8 | 9 | for no in non_visited_nodes: 10 | shortest_distance[no] = infinit 11 | shortest_distance[start] = 0 12 | 13 | while non_visited_nodes != {}: 14 | shortest_extracted_node = None 15 | for i in non_visited_nodes: 16 | if shortest_extracted_node is None: 17 | shortest_extracted_node = i 18 | elif shortest_distance[i] < shortest_distance[shortest_extracted_node]: 19 | shortest_extracted_node = i 20 | 21 | for no_v, Weight in graph[shortest_extracted_node]: 22 | if Weight + shortest_distance[shortest_extracted_node] < shortest_distance[no_v]: 23 | shortest_distance[no_v] = Weight + shortest_distance[shortest_extracted_node] 24 | non_visited_nodes.pop(shortest_extracted_node) 25 | return shortest_distance 26 | 27 | #in this case, I made a graph within the code, but I didn't put here, you can create your graph the way you like. 28 | #this algorithm needs the start, end, and weight, but you can remove the weight as well 29 | #I will leave my example here, how I use this algorithm to solve the shortest path problem 30 | # V is vertex, u is edges and W IS WEIGHT. 31 | 32 | cities, origin, destiny = map(int, input().split()) 33 | graph = {i:[] for i in range(1, cities+1)} 34 | for i in range(cities-1): 35 | u, v, w = map(int, input().split()) 36 | graph[v].append((u, w)) 37 | graph[u].append((v, w)) -------------------------------------------------------------------------------- /algorithms/greedy/egyptian_fraction.py: -------------------------------------------------------------------------------- 1 | # Reference - https://www.geeksforgeeks.org/greedy-algorithm-egyptian-fraction/ 2 | 3 | import math 4 | 5 | def egyptian_fraction(nr, dr): 6 | ef = [] 7 | 8 | while nr != 0: 9 | x = math.ceil(dr / nr) 10 | ef.append(x) 11 | 12 | nr = x * nr - dr 13 | dr = dr * x 14 | -------------------------------------------------------------------------------- /algorithms/greedy/min_platforms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given the arrival and departure times of buses at a station 3 | find the min number of platforms that must be there 4 | """ 5 | 6 | 7 | def find_platforms(arrival, departure): 8 | n = len(arrival) 9 | 10 | arrival.sort() 11 | departure.sort() 12 | 13 | i = 1 14 | j = 0 15 | 16 | ans = 1 # atleast one platform is required 17 | plat = 1 18 | 19 | while i < n and j < n: 20 | if arrival[i] <= departure[j]: 21 | plat += 1 22 | i += 1 23 | 24 | elif arrival[i] > departure[j]: 25 | plat -= 1 26 | j += 1 27 | 28 | ans = max(ans, plat) 29 | 30 | 31 | return ans 32 | 33 | 34 | arr = [900, 940, 950, 1100, 1500, 1800] 35 | dep = [910, 1200, 1120, 1130, 1900, 2000] 36 | 37 | print(find_platforms(arr, dep)) 38 | -------------------------------------------------------------------------------- /algorithms/math/divisors.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | class divisors: 4 | 5 | def findAllDivisors(self, n): 6 | result = list() 7 | 8 | for i in range(1, n+1): 9 | if (n%i == 0): 10 | result.append(i) 11 | 12 | return result 13 | 14 | def divisorsCount(self, n): 15 | divs = self.findAllDivisors(n) 16 | return len(divs) 17 | 18 | def oddFactors(self, n): 19 | result = list() 20 | 21 | for i in range(1, n+1): 22 | if (n%i == 0 and i%2==1): 23 | result.append(i) 24 | 25 | return result 26 | 27 | def oddFactorsSum(self, n): 28 | divs = self.evenFactors(n) 29 | return sum(divs) 30 | 31 | def evenFactors(self, n): 32 | result = list() 33 | 34 | for i in range(1, n+1): 35 | if (n%i == 0 and i%2==0): 36 | result.append(i) 37 | 38 | return result 39 | 40 | def evenFactorsSum(self, n): 41 | divs = self.evenFactors(n) 42 | return sum(divs) 43 | 44 | def primeFactors(self, n): 45 | result = list() 46 | 47 | while n % 2 == 0: 48 | result.append(2) 49 | n = n / 2 50 | 51 | for i in range(3,int(math.sqrt(n))+1,2): 52 | while n % i== 0: 53 | result.append(i) 54 | n = n / i 55 | 56 | if n > 2: 57 | result.append(n) 58 | 59 | return result 60 | 61 | def primeFactorsSum(self, n): 62 | divs = self.primeFactors(n) 63 | return sum(divs) -------------------------------------------------------------------------------- /algorithms/math/factorial_iterative.py: -------------------------------------------------------------------------------- 1 | #Calculate factorial of a given number using iterative method. 2 | 3 | def factorial(number): 4 | 5 | answer = 1 6 | 7 | if number == 0: 8 | return 1 9 | else: 10 | for num in range(1, number+1): 11 | answer = answer * num 12 | return answer 13 | 14 | if __name__ == '__main__': 15 | 16 | enter_number = int(input("Enter a number whose factorial is required : ")) 17 | result = factorial(enter_number) 18 | print(result) -------------------------------------------------------------------------------- /algorithms/math/factorial_recursive.py: -------------------------------------------------------------------------------- 1 | #Calculate factorial of a given number using recursive method. 2 | 3 | def factorial(number): 4 | 5 | if number == 0 or number == 1: 6 | return 1 7 | 8 | answer = number * factorial(number - 1) 9 | 10 | return answer 11 | 12 | if __name__ == '__main__': 13 | 14 | enter_number = int(input("Enter a number whose factorial is required : ")) 15 | result = factorial(enter_number) 16 | print(result) -------------------------------------------------------------------------------- /algorithms/math/fibonacci_number.py: -------------------------------------------------------------------------------- 1 | # Program that prints out the fibonacci sequence until a given number 2 | 3 | 4 | def main(): 5 | n = int(input("insert number")) 6 | fib = [1, 1] 7 | for i in range(2,n,1): 8 | print(i) 9 | fib.append(fib[i-1]+fib[i-2]) 10 | 11 | print(fib) 12 | 13 | 14 | if __name__ == '__main__': 15 | main() 16 | -------------------------------------------------------------------------------- /algorithms/math/greatest_common_divisor.py: -------------------------------------------------------------------------------- 1 | """ 2 | High Level Description: 3 | Given two input integers, find their Greatest Common Divisor. 4 | 5 | Time Complexity: 6 | O(log(n)) 7 | """ 8 | # Iterative Solution 9 | def gcd(x, y): 10 | if x == 0: 11 | return y 12 | if y == 0: 13 | return x 14 | 15 | while x % y != 0: 16 | rem = x % y; 17 | x = y 18 | y = rem 19 | 20 | return y 21 | 22 | # Recursive Solution 23 | def gcd(x, y): 24 | if x == 0: 25 | return y 26 | if y == 0: 27 | return x 28 | 29 | return gcd(y, x % y) 30 | 31 | # Iterative optimized 32 | def gcd(x, y): 33 | while y != 0: 34 | x, y = y, x % y 35 | return x 36 | -------------------------------------------------------------------------------- /algorithms/math/index.md: -------------------------------------------------------------------------------- 1 | # Index of math 2 | 3 | * [Greatest Common Divisor](greatest_common_divisor.py) 4 | * [Fibonacci Number](fibonacci_number.py) 5 | * [Nth Prime Number](prime.py) 6 | * [Sieve of Erastothenes](sieve_of_eratosthenes.py) 7 | * [Perfect Square](perfect_square.py) 8 | * [Number Convertion](number_convertion.py) 9 | * [Iterative Factorial](factorial_iterative.py) 10 | * [Recursive Factorial](factorial_recursive.py) 11 | -------------------------------------------------------------------------------- /algorithms/math/least_common_multiple.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given two input integers, find their Least Common Multiple. 3 | """ 4 | 5 | from greatest_common_divisor import gcd 6 | 7 | def lcm(x, y): 8 | return abs(x * y) // gcd(x, y) 9 | -------------------------------------------------------------------------------- /algorithms/math/modular_exponentiation.py: -------------------------------------------------------------------------------- 1 | # to compute modular power 2 | 3 | # Iterative Function to calculate 4 | # (x^y)%p in O(log y) 5 | 6 | def power(x, y, p) : 7 | res = 1 # Initialize result 8 | 9 | # Update x if it is more 10 | # than or equal to p 11 | x = x % p 12 | 13 | while (y > 0) : 14 | 15 | # If y is odd, multiply 16 | # x with result 17 | if ((y & 1) == 1) : 18 | res = (res * x) % p 19 | 20 | # y must be even now 21 | y = y >> 1 # y = y/2 22 | x = (x * x) % p 23 | 24 | return res -------------------------------------------------------------------------------- /algorithms/math/perfect_square.py: -------------------------------------------------------------------------------- 1 | def is_perfect_square(num): 2 | """ 3 | Given a positive integer num, write a function which returns True if num is a perfect square else False. 4 | 5 | Note: Do not use any built-in library function such as `sqrt`. 6 | 7 | :type num: int 8 | :rtype: bool 9 | """ 10 | i = 0 11 | while i * i < num: 12 | i += 1 13 | if i * i == num: 14 | return True 15 | else: 16 | return False 17 | 18 | 19 | # Test 20 | print(is_perfect_square(16)) 21 | print(is_perfect_square(14)) 22 | -------------------------------------------------------------------------------- /algorithms/math/power_of_two.py: -------------------------------------------------------------------------------- 1 | """ 2 | This simple code is to check if a given number is a poer of two or not. 3 | Method: 4 | if a number is a power of two, then its binary representation is (2^k). 5 | ex: 4 --> 2^2 --> 100 --> k = 2 6 | 8 --> 2^3 --> 1000 --> k = 3 7 | 16--> 2^4 --> 10000 --> k = 4 8 | 9 | assume that n is a power of two, then (n-1)&(n) will be zero; 10 | ex: 11 | n = 8 --> (1000) , n-1 = 7 ---> (0111) 12 | performing bit (and) operation between both, then n&(n-1) = 0000 13 | 14 | n = 12 --> (1100) , n-1 = 11 ---> (1011) 15 | performing bit (and) operation between both, then n&(n-1) = 1000 16 | 17 | Conclusion: 18 | The result of the above bit "and" operation will be zero, ONLY if the given number is a pwoer of two. 19 | 20 | NOTE: 21 | - Since Python considers 0 as "false", then we are gonna return the inversion of the result; i.e. return not(n&(n-1). 22 | - BUT If n = 0, the result will be zero indicating that 0 is a power of 2, wich is not true. 23 | So to fix that, we are going to perform an extra logical "and" operation with the oreginal number. 24 | """ 25 | 26 | 27 | def pow_of_two(n): 28 | return(n and (not(n&(n-1)))) 29 | 30 | for i in range(20): 31 | if pow_of_two(i): 32 | print(f"{i} is a power of 2.") 33 | else: 34 | print(f"{i} is NOT a power of 2.") 35 | -------------------------------------------------------------------------------- /algorithms/math/prime.py: -------------------------------------------------------------------------------- 1 | def prime(limit): 2 | 3 | count = 1 4 | while (count < limit): 5 | 6 | flag = 0 7 | for i in range(3, count, 2): 8 | if (count % i == 0): 9 | flag = 1 10 | 11 | if (flag == 0): 12 | print(count) 13 | 14 | count += 2 15 | 16 | prime(100) 17 | -------------------------------------------------------------------------------- /algorithms/math/recursive_fibonacci.py: -------------------------------------------------------------------------------- 1 | """ 2 | Recursivly compute the Fibonacci sequence using two different methods 3 | rec_fib(n) requires O(Fibo(n)) operations, whereas binary_rec_fib(n) requires less than O(n) 4 | """ 5 | 6 | def rec_fib(n): 7 | if n == 1: 8 | return 1 9 | elif n == 0: 10 | return 0 11 | else: 12 | return rec_fib(n-1)+rec_fib(n-2) 13 | 14 | def binary_rec_fib(n): 15 | if n == 2 or n == 1: 16 | return 1 17 | elif n == 0: 18 | return 0 19 | else: 20 | # This recursive step takes advantage of the following two properties of the fibonacci numbers: 21 | # Fibo(2n) = Fibo(n+1)^2 + Fibo(n)^2 22 | # Fibo(2n+1) = Fibo(n+1)^2 - Fibo(n-1)^2 23 | sgn = n % 2 24 | return binary_rec_fib((n-sgn)/2 + 1)**2 - ((-1)**sgn) * binary_rec_fib((n+sgn)/2 - 1)**2 25 | 26 | def main(): 27 | times = [] 28 | n : int = int(input("n := ")) 29 | for i in range(0, n): 30 | print(binary_rec_fib(i)) 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /algorithms/math/sieve_of_eratosthenes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Description: 3 | Sieve of Eratosthenes is a very fast method (loglog(n)) of finding primes upto a given number. 4 | """ 5 | 6 | 7 | def sieve(n): 8 | prime_list = [] 9 | for i in range(2, n+1): 10 | if i not in prime_list: 11 | print(i) 12 | for j in range(i*i, n+1, i): 13 | prime_list.append(j) 14 | 15 | 16 | if __name__ == '__main__': 17 | input_number = int(input("Provide a number upto which primes are to be found :")) 18 | sieve(input_number) 19 | -------------------------------------------------------------------------------- /algorithms/math/sumofdigits.py: -------------------------------------------------------------------------------- 1 | # This is to find the sum of digits of a number until it is a single digit 2 | 3 | def sum_of_digits(n): 4 | n = int(input()) # here n is the number 5 | if n % 9 != 0: 6 | print(n % 9) 7 | else: 8 | print("9") 9 | 10 | # This method reduces time complexity by a factor of n and also without using any loop 11 | 12 | -------------------------------------------------------------------------------- /algorithms/miscellaneous/front_and_back_search.py: -------------------------------------------------------------------------------- 1 | def front_and_back_search(lst, item): 2 | ''' 3 | args: 4 | lst: an unsorted array of integers 5 | item: data to be found 6 | 7 | return: 8 | item which is found else False 9 | ''' 10 | rear=0 11 | front=len(lst)-1 12 | u=None 13 | if rear>front: 14 | return False 15 | else: 16 | while rear<=front: 17 | if item==lst[rear] or item==lst[front]: 18 | u='' 19 | return True ##item found 20 | elif item!=lst[rear] and item!=lst[front]: 21 | if item > lst[rear]: 22 | rear=rear+1 23 | elif item < lst[front]: 24 | front=front-1 25 | if u==None: 26 | return False 27 | -------------------------------------------------------------------------------- /algorithms/miscellaneous/index.md: -------------------------------------------------------------------------------- 1 | # Index of miscellaneous 2 | 3 | * [Luhn Algorithm](luhn_algorithm.py) -------------------------------------------------------------------------------- /algorithms/miscellaneous/luhn_algorithm.py: -------------------------------------------------------------------------------- 1 | def check_luhn(card_number): 2 | """ 3 | Luhn algorithm or Luhn formula is a simple checksum formula 4 | used to validate a variety of identification numbers, such as 5 | credit card numbers, IMEI numbers, National Provider Identifier numbers 6 | in some of the countries. 7 | 8 | It takes a number as an input 9 | (Assuming cardnumber as a string) 10 | and returns true or false 11 | based upon whether number is valid or not 12 | 13 | :param card_number: 14 | :return: bool: valid or not 15 | 16 | Examples: 17 | 18 | >>> check_luhn("950123440000") 19 | False 20 | >>> check_luhn("490154203237518") 21 | True 22 | """ 23 | card_len = len(card_number) 24 | 25 | check_sum = 0 26 | 27 | is_parity = False 28 | 29 | for digit in range(card_len - 1, -1, -1): 30 | if is_parity: 31 | cal = int(card_number[digit]) * 2 32 | else: 33 | cal = int(card_number[digit]) 34 | 35 | if cal > 9: 36 | check_sum += cal - 9 37 | else: 38 | check_sum += cal 39 | 40 | is_parity = not is_parity 41 | 42 | return check_sum % 10 == 0 43 | 44 | 45 | if __name__ == "__main__": 46 | import doctest 47 | 48 | doctest.testmod() 49 | -------------------------------------------------------------------------------- /algorithms/miscellaneous/markov.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import random 3 | 4 | MAX_LETTERS = 2000 5 | 6 | def readFile(f, mp, k): 7 | seed = '' 8 | mostFreqSeed = seed 9 | mostFreq = 1 10 | for line in f: 11 | for ch in line: 12 | seed, mostFreq, mostFreqSeed = processSeed(seed, mostFreqSeed, mostFreq, ch, k, mp) 13 | return mostFreqSeed 14 | 15 | def processSeed(seed, mostFreqSeed, mostFreq, ch, k, mp): 16 | seed += ch 17 | if len(seed) == k+1: 18 | oldSeed = seed[:-1] 19 | mp.setdefault(oldSeed, []).append(ch) 20 | if mostFreq < len(mp[oldSeed]): 21 | mostFreq, mostFreqSeed = len(mp[oldSeed]), oldSeed 22 | seed = seed[1:] 23 | return seed, mostFreq, mostFreqSeed 24 | 25 | def generateText(mp, mostFreqSeed): 26 | text, curSeed = mostFreqSeed, mostFreqSeed 27 | while (len(text) < MAX_LETTERS): 28 | ch = random.choice(mp[curSeed]) 29 | text, curSeed = text+ch, curSeed+ch 30 | curSeed = curSeed[1:] 31 | return text 32 | 33 | def main(): 34 | fileName = input("Enter the file name: ") + ".txt" 35 | f = codecs.open(fileName, encoding='utf-8') 36 | k = int(input("Enter the Markov order [1-10]: ")) 37 | assert (k >= 1 and k <= 10) 38 | mp = {} 39 | mostFreqSeed = readFile(f, mp, k) 40 | f.close() 41 | text = generateText(mp, mostFreqSeed) 42 | print(text) 43 | result = codecs.open("result.txt", "w", encoding='utf-8') 44 | result.write(text) 45 | result.close() 46 | 47 | if __name__ == "__main__": 48 | main() -------------------------------------------------------------------------------- /algorithms/sorting/bubble_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bubble Sort worst time complexity occurs when array is reverse sorted - O(n^2) 3 | Best time scenario is when array is already sorted - O(n) 4 | """ 5 | 6 | def bubble_sort(array): 7 | n = len(array) 8 | for i in range(n): 9 | for j in range(0, n-i-1): 10 | if array[j] > array[j+1]: 11 | array[j], array[j+1] = array[j+1], array[j] 12 | return array 13 | 14 | 15 | def bubble_sort_optimized(array): 16 | """ 17 | Optimizes on bubble sort by taking care of already swapped cases 18 | Reference - https://github.com/prabhupant/python-ds/pull/346 19 | """ 20 | has_swapped = True 21 | 22 | num_of_iterations = 0 23 | 24 | while has_swapped: 25 | has_swapped = False 26 | for i in range(len(array) - num_of_iterations - 1): 27 | if array[i] > array[i + 1]: 28 | array[i], array[i + 1] = array[i + 1], array[i] 29 | has_swapped = True 30 | num_of_iterations += 1 31 | -------------------------------------------------------------------------------- /algorithms/sorting/counting_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | High level description: 3 | Counting sort is a sorting technique based on keys between a specific range, 4 | with efective performance on the predetermined range size of the values. 5 | It works by counting the number of objects having distinct key values (kind of hashing). 6 | Then doing some arithmetic to calculate the position of each object in the output sequence. 7 | 8 | Time complexity: 9 | O(n+k) where n is the number of elements in input array and k is the range of input. 10 | 11 | Auxiliary Space: O(n+k) 12 | """ 13 | 14 | def counting_sort(arr): 15 | # Find min and max values 16 | min_value = min(arr) 17 | max_value = max(arr) 18 | 19 | # Count number appearances in the array 20 | counting_arr = [0]*(max_value-min_value+1) 21 | for num in arr: 22 | counting_arr[num-min_value] += 1 23 | 24 | # Rearrange sequence in the array 25 | index = 0 26 | for i, count in enumerate(counting_arr): 27 | for _ in range(count): 28 | arr[index] = min_value + i 29 | index += 1 30 | 31 | test_array = [3, 3, 2, 6, 4, 7, 9, 7, 8] 32 | 33 | counting_sort(test_array) 34 | 35 | print(test_array) 36 | -------------------------------------------------------------------------------- /algorithms/sorting/gnome_sort.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Gnome sort is not best sorting algorithms but sure it takes its pride. 3 | It has time O(n^2) 4 | ''' 5 | 6 | 7 | def gnome_sort(arr): 8 | """ 9 | Examples: 10 | >>> gnome_sort([0, 5, 2, 3, 2]) 11 | [0, 2, 2, 3, 5] 12 | 13 | >>> gnome_sort([]) 14 | [] 15 | >>> gnome_sort([-2, -45, -5]) 16 | [-45, -5, -2] 17 | """ 18 | 19 | # first case 20 | size = len(arr) 21 | 22 | if size <= 1: 23 | return arr 24 | ind = 0 25 | # while loop 26 | while ind < size: 27 | if ind == 0: 28 | ind += 1 29 | elif arr[ind] >= arr[ind - 1]: 30 | ind += 1 31 | else: 32 | # swap 33 | temp = arr[ind - 1] 34 | arr[ind - 1] = arr[ind] 35 | arr[ind] = temp 36 | ind -= 1 37 | 38 | return arr 39 | -------------------------------------------------------------------------------- /algorithms/sorting/index.md: -------------------------------------------------------------------------------- 1 | # Index of sorting 2 | 3 | * [Insertion Sort](insertion_sort.py) 4 | * [Merge Sort](merge_sort.py) 5 | * [Quick Sort](qsort.py) 6 | * [Select Sort](select_sort.py) 7 | * [Bubble Sort](bubble_sort.py) -------------------------------------------------------------------------------- /algorithms/sorting/insertion_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | High Level Description: 3 | For every element in the given list, find its correct index by iterating 4 | backwards and finding a slot. This forms a sorted array. 5 | Time Complexity: 6 | Every element is visited, which contributes O(n). Swapping backwards takes 7 | O(n/2) time on average, meaning that the total complexity is O(n^2) 8 | """ 9 | 10 | def insertion_sort(lst): 11 | for i in range(1,len(lst)): 12 | while(i > 0 and lst[i] < lst[i - 1]): 13 | lst[i], lst[i - 1] = lst[i - 1], lst[i] 14 | i -= 1 15 | return lst 16 | 17 | test_data = [5,9,4,27,3,6] 18 | print(insertion_sort(test_data)) 19 | 20 | test_data = ['f','b','z','a','x'] 21 | print(insertion_sort(test_data)) 22 | 23 | # Resulting output: 24 | # [3, 4, 5, 6, 9, 27] 25 | # ['a', 'b', 'f', 'x', 'z'] 26 | -------------------------------------------------------------------------------- /algorithms/sorting/merge_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | High level explanation: 3 | 4 | merge_sort is a Divide and conquer algorithm that splits in halves the array and 5 | then builds it back up by merging and sorting at the same time its elements. 6 | 7 | Time complexity: 8 | 9 | merge_sort has a time complexity of O(n log n). 10 | """ 11 | 12 | def merge_sort(arr): 13 | if len(arr) >1: 14 | mid = len(arr)//2 #Finding the mid of the array 15 | L = arr[:mid] # Dividing the array elements 16 | R = arr[mid:] # into 2 halves 17 | 18 | merge_sort(L) # Sorting the first half 19 | merge_sort(R) # Sorting the second half 20 | 21 | i = j = k = 0 22 | 23 | # Copy data to temp arrays L[] and R[] 24 | while i < len(L) and j < len(R): 25 | if L[i] < R[j]: 26 | arr[k] = L[i] 27 | i+=1 28 | else: 29 | arr[k] = R[j] 30 | j+=1 31 | k+=1 32 | 33 | # Checking if any element was left 34 | while i < len(L): 35 | arr[k] = L[i] 36 | i+=1 37 | k+=1 38 | 39 | while j < len(R): 40 | arr[k] = R[j] 41 | j+=1 42 | k+=1 43 | 44 | test_array = [10,30,20,100,40,80,90,210,34] 45 | 46 | merge_sort(test_array) 47 | 48 | print(test_array) 49 | 50 | # This code is contributed by Mayank Khanna 51 | # and extented by thanasis mpalatsoukas 52 | -------------------------------------------------------------------------------- /algorithms/sorting/permutation_sort.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # Sorts array a[0..n-1] using Bogo sort 4 | 5 | 6 | def bogo_sort(a): 7 | n = len(a) 8 | while (is_sorted(a) == False): 9 | shuffle(a) 10 | 11 | # To check if array is sorted or not 12 | 13 | 14 | def is_sorted(a): 15 | n = len(a) 16 | for i in range(0, n - 1): 17 | if (a[i] > a[i + 1]): 18 | return False 19 | return True 20 | 21 | # To generate permuatation of the array 22 | 23 | 24 | def shuffle(a): 25 | n = len(a) 26 | for i in range(0, n): 27 | r = random.randint(0, n - 1) 28 | a[i], a[r] = a[r], a[i] 29 | 30 | 31 | # Driver code to test above 32 | a = [3, 2, 4, 1, 0, 5] 33 | bogo_sort(a) 34 | print("Sorted array :") 35 | for i in range(len(a)): 36 | print("%d" % a[i]), 37 | -------------------------------------------------------------------------------- /algorithms/sorting/pigeonhole_sort.py: -------------------------------------------------------------------------------- 1 | #Pigeonhole sorting is a sorting algorithm that is suitable for sorting lists of elements where the number of elements and the number of possible key values are approximately the same. 2 | #It requires O(n + Range) time where n is number of elements in input array and ‘Range’ is number of possible values in array. 3 | 4 | def pigeonhole_sort(a): 5 | my_min = min(a) 6 | my_max = max(a) 7 | size = my_max - my_min + 1 8 | 9 | # our list of pigeonholes 10 | 11 | 12 | holes = [0] * size 13 | 14 | # Populate the pigeonholes. 15 | 16 | 17 | for x in a: 18 | assert type(x) is int, "integers only please" 19 | holes[x - my_min] += 1 20 | 21 | # Put the elements back into the array in order. 22 | 23 | 24 | i = 0 25 | for count in range(size): 26 | while holes[count] > 0: 27 | holes[count] -= 1 28 | a[i] = count + my_min 29 | i += 1 30 | 31 | 32 | a = [8, 3, 2, 7, 4, 6, 8] 33 | print("Sorted order is : ", end = ' ') 34 | pigeonhole_sort(a) 35 | for i in range(0, len(a)): 36 | print(a[i], end = ' ') 37 | -------------------------------------------------------------------------------- /algorithms/sorting/qsort.py: -------------------------------------------------------------------------------- 1 | """ 2 | High level explanation: 3 | Quicksort algorithm is that if we can efficiently partition a list, 4 | then we can efficiently sort it. Partitioning a list means that 5 | we pick a pivot item in the list, and then modify the list 6 | to move all items larger than the pivot to the right and all 7 | smaller items to the left. 8 | 9 | Once the pivot is done, we can do the same operation to the 10 | left and right sections of the list recursively until the list is sorted. 11 | 12 | Time complexity: 13 | Quicksort has a time complexity of O(n log n). 14 | """ 15 | 16 | 17 | def qsort(arr): 18 | if len(arr) <= 1: 19 | return arr 20 | pivot = arr.pop() 21 | greater, lesser = [], [] 22 | for item in arr: 23 | if item > pivot: 24 | greater.append(item) 25 | else: 26 | lesser.append(item) 27 | return qsort(lesser) + [pivot] + qsort(greater) 28 | -------------------------------------------------------------------------------- /algorithms/sorting/select_sort.py: -------------------------------------------------------------------------------- 1 | 2 | def find_smallest(arr): 3 | smallest = arr[0] 4 | smallest_index = 0 5 | 6 | for i in range(1, len(arr)): 7 | 8 | if arr[i] < smallest: 9 | smallest = arr[i] 10 | smallest_index = i 11 | return smallest_index 12 | 13 | 14 | 15 | def selection_sort(arr): 16 | new_arr = [] 17 | 18 | for i in range(len(arr)): 19 | smallest = find_smallest(arr) 20 | new_arr.append(arr.pop(smallest)) 21 | 22 | return new_arr 23 | 24 | array = [100, 5, 72, 41, 80, 1, 99, 36, 27, 78] 25 | 26 | print(selection_sort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] 27 | -------------------------------------------------------------------------------- /algorithms/sorting/shell_sort.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Time complexity of shell_sort is O(n2). 3 | In the above implementation gap is reduce by half in every iteration. 4 | here are many other ways to reduce gap which lead to better time complexity. 5 | See this for more details. 6 | ''' 7 | 8 | def shell_sort(arr): 9 | 10 | # Start with a big gap, then reduce the gap 11 | n = len(arr) 12 | gap = int(n / 2) 13 | 14 | # Do a gapped insertion sort for this gap size. 15 | # The first gap elements a[0..gap-1] are already in gapped 16 | # order keep adding one more element until the entire array 17 | # is gap sorted 18 | while gap > 0: 19 | 20 | for i in range(gap, n): 21 | 22 | # add a[i] to the elements that have been gap sorted 23 | # save a[i] in temp and make a hole at position i 24 | temp = arr[i] 25 | 26 | # shift earlier gap-sorted elements up until the correct 27 | # location for a[i] is found 28 | j = i 29 | while j >= gap and arr[j - gap] > temp: 30 | arr[j] = arr[j - gap] 31 | j -= gap 32 | 33 | # put temp (the original a[i]) in its correct location 34 | arr[j] = temp 35 | gap = int(gap / 2) 36 | 37 | arr = [ 12, 34, 54, 2, 3] 38 | 39 | shell_sort(arr) 40 | print(arr) -------------------------------------------------------------------------------- /bookmarks/books.md: -------------------------------------------------------------------------------- 1 | # Books 2 | 3 | This is a list of links to books that may be useful for algorithms and data structures learning. 4 | 5 | ## Links: 6 | 7 | - https://drive.google.com/open?id=1d23_sJdK8XPBJEi-4oUrMnrYMhU2fDcI 8 | 9 | - https://drive.google.com/open?id=1sBSGJ8gUakcn0NuQam4BRhRCQfsTrrwh 10 | 11 | -------------------------------------------------------------------------------- /bookmarks/database.md: -------------------------------------------------------------------------------- 1 | https://stackoverflow.com/questions/23435361/does-mysql-update-the-index-on-all-inserts-can-i-make-it-update-after-every-x-i 2 | -------------------------------------------------------------------------------- /bookmarks/misc.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | This is a list of misc links that may be useful when learning or researching data structures and algorithms. 4 | 5 | ## Links: 6 | 7 | - https://google.github.io/eng-practices/review/reviewer/ 8 | 9 | - https://www.preining.info 10 | 11 | - https://cheatsheetseries.owasp.org 12 | 13 | - https://wsvincent.com/ 14 | 15 | - https://machinelearningmastery.com 16 | 17 | - https://stackoverflow.com/questions/10631326/difference-between-select-into-and-insert-into-from-old-table 18 | 19 | - Why SSL are not issued for IP address - https://stackoverflow.com/a/33419662/6111200 20 | 21 | - PATCH vs PUT https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios/39338329#39338329 22 | -------------------------------------------------------------------------------- /bookmarks/topics.md: -------------------------------------------------------------------------------- 1 | # Topics 2 | 3 | This is a list of links to topics that may be helpful in learning or researching data structures and algorithms. 4 | 5 | ## Links: 6 | 7 | - https://en.wikipedia.org/wiki/Data_deduplication 8 | 9 | - https://searchstorage.techtarget.com/definition/data-deduplication 10 | 11 | - https://docs.gitlab.com/ce/development/architecture.html 12 | 13 | - https://stackoverflow.com/questions/35390533/actual-difference-between-data-compression-and-data-deduplication 14 | 15 | - https://stackoverflow.com/questions/40200413/sessions-vs-token-based-authentication 16 | 17 | - https://stackoverflow.com/questions/15678406/when-to-use-myisam-and-innodb 18 | 19 | - https://www.bigocheatsheet.com/ 20 | -------------------------------------------------------------------------------- /bookmarks/tutorials.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | This is a list of links to tutorials that may be helpful in learning or researching data structures and algorithms. 4 | 5 | ## Links: 6 | 7 | - https://cstack.github.io/db_tutorial/ 8 | 9 | - https://github.com/eon01/kubernetes-workshop 10 | 11 | - http://cp-algorithms.com/ 12 | 13 | - https://realpython.com/python-data-classes/ 14 | 15 | - https://chrisalbon.com 16 | -------------------------------------------------------------------------------- /data_structures/array/all_numbers_divisible.py: -------------------------------------------------------------------------------- 1 | # Q - Find a number in the array such that all the elements 2 | # in the array are divisible by it 3 | 4 | # A - just find the min in the array and check if all the other elements 5 | # are divisble by it 6 | 7 | import sys 8 | 9 | def check(arr): 10 | min = sys.maxsize 11 | for i in arr: 12 | if i < min: 13 | min = i 14 | for i in arr: 15 | if not i % min == 0: 16 | return False 17 | return True 18 | 19 | 20 | arr = [20,10,15,5,100,200, 201] 21 | 22 | print(check(arr)) 23 | -------------------------------------------------------------------------------- /data_structures/array/binary_search_infinite_array.py: -------------------------------------------------------------------------------- 1 | def binary_search(arr, start, end, val): 2 | while start <= end: 3 | mid = (start + end)//2 4 | if val == arr[mid]: 5 | return mid 6 | elif val > arr[mid]: 7 | start = mid + 1 8 | else: 9 | end = mid - 1 10 | 11 | return -1 12 | 13 | 14 | def search(arr, val): 15 | if len(arr) == 1: 16 | if arr[0] == val: 17 | return "Found" 18 | else: 19 | return "Not found" 20 | 21 | temp = arr[0] 22 | low = 0 23 | high = 1 24 | 25 | while temp < val: 26 | low = high 27 | high = 2 * high 28 | temp = arr[high] 29 | 30 | ans = binary_search(arr, low, high, val) 31 | 32 | if ans == -1: 33 | return "Not Found" 34 | else: 35 | return "Found at index {}".format(ans) 36 | 37 | 38 | arr = [1,3,5,6,7,8,9,11,13,15,19] 39 | 40 | print(search(arr, 7)) 41 | -------------------------------------------------------------------------------- /data_structures/array/duplicate.py: -------------------------------------------------------------------------------- 1 | # Find duplicate in an array of integers given that the integers are in random order and 2 | # not necessarily each integer i is 0 <= i <= N where N = length of array 3 | 4 | # Solution - use tortoise and hare algorithm. The tortoise pointer moves slower while the hare pointer 5 | # moves faster 6 | 7 | # Note: this array will always contain a duplicate number due to the pigeonhole principle. You are trying to fit 8 | # N different numbers in an array of size N - 1 so one number will be repeated 9 | 10 | def duplicate(arr): 11 | tortoise = arr[0] 12 | hare = arr[0] 13 | 14 | while True: 15 | tortoise = arr[tortoise] 16 | hare = arr[arr[hare]] 17 | if tortoise == hare: 18 | break 19 | 20 | tortoise = arr[0] 21 | 22 | while tortoise != hare: 23 | tortoise = arr[tortoise] 24 | hare = arr[hare] 25 | 26 | return hare 27 | 28 | 29 | arr = [3,5,1,2,4,5] 30 | print(duplicate(arr)) -------------------------------------------------------------------------------- /data_structures/array/duplicates.py: -------------------------------------------------------------------------------- 1 | def duplicate(arr): 2 | res = [] 3 | for x in arr: 4 | if arr[abs(x) - 1] < 0: 5 | res.append(abs(x)) 6 | else: 7 | arr[abs(x) - 1] *= -1 8 | return res 9 | -------------------------------------------------------------------------------- /data_structures/array/dutch_flag_problem.py: -------------------------------------------------------------------------------- 1 | # Given an array containing only 0s, 1s and 2s in a random order, arrange the array such that all 0s come 2 | # before 1s which in turn come before all 2s 3 | # Input - [1,2,0,1,2,1,0,1,2,1,0] 4 | # Output - [0,0,0,1,1,1,1,1,2,2,2] 5 | 6 | def dutch(arr): 7 | low = 0 8 | mid = 0 9 | high = len(arr) - 1 10 | while mid <= high: 11 | if arr[mid] == 0: 12 | arr[low], arr[mid] = arr[mid], arr[low] 13 | low += 1 14 | mid += 1 15 | elif arr[mid] == 1: 16 | mid += 1 17 | else: 18 | arr[mid], arr[high] = arr[high], arr[mid] 19 | high -= 1 20 | 21 | 22 | arr = [1,0,2,1,0,2,1,2,1,2,1,1,0,2,1,0,1,2,1,2,1,1,2,1,0,2,1,1] 23 | print(arr) 24 | dutch(arr) 25 | print(arr) 26 | 27 | -------------------------------------------------------------------------------- /data_structures/array/equilibrium_index.py: -------------------------------------------------------------------------------- 1 | # Find the equilibrium index of an array. An equilibrium index is such that 2 | # the sum of elements to the left of it is equal to sum of elements to the right 3 | # of it 4 | 5 | def find_equi(arr): 6 | total_sum = sum(arr) 7 | 8 | left_sum = 0 9 | 10 | for i, num in enumerate(arr): 11 | 12 | total_sum -= num 13 | 14 | if left_sum == total_sum: 15 | return i 16 | 17 | left_sum += num 18 | 19 | return -1 20 | 21 | 22 | arr = [-7, 1, 5, 2, -4, 3, 0] 23 | print(find_equi(arr)) 24 | -------------------------------------------------------------------------------- /data_structures/array/even_more_than_odd.py: -------------------------------------------------------------------------------- 1 | # Rearrange an array such that numbers at even indexes are greater than numbers 2 | # at odd indexes 3 | 4 | def rearrange(arr): 5 | for i in range(1, len(arr)): 6 | if i % 2 == 0: 7 | if arr[i] > arr[i-1]: 8 | arr[i-1], arr[i] = arr[i], arr[i-1] 9 | else: 10 | if arr[i] < arr[i-1]: 11 | arr[i-1], arr[i] = arr[i], arr[i-1] 12 | print(arr) 13 | 14 | 15 | arr = [ 1, 3, 2, 2, 5 ] 16 | rearrange(arr) -------------------------------------------------------------------------------- /data_structures/array/find_common_in_three_arrays.py: -------------------------------------------------------------------------------- 1 | # Find the common elements in three arrays 2 | 3 | def find_common(arr1, arr2, arr3): 4 | i, j, k = 0, 0, 0 5 | 6 | while i < len(arr1) and j < len(arr2) and k < len(arr3): 7 | if arr1[i] == arr2[j] == arr3[k]: 8 | print(arr1[i]) 9 | i += 1 10 | j += 1 11 | k += 1 12 | 13 | elif arr1[i] < arr2[j]: 14 | i += 1 15 | 16 | elif arr2[j] < arr3[k]: 17 | j += 1 18 | 19 | else: 20 | k += 1 21 | 22 | arr1 = [1,2,3,4,5,6] 23 | arr2 = [5,6,7,8,9] 24 | arr3 = [4,5,6,9,10] 25 | 26 | find_common(arr1, arr2, arr3) 27 | -------------------------------------------------------------------------------- /data_structures/array/find_given_sum_in_array.py: -------------------------------------------------------------------------------- 1 | # Find if a given sum exists in an array 2 | # Reference - https://www.geeksforgeeks.org/find-subarray-with-given-sum/ 3 | 4 | def find_sum(arr, s): 5 | curr_sum = arr[0] 6 | start = 0 7 | n = len(arr) - 1 8 | 9 | i = 1 10 | 11 | while i <= n: 12 | 13 | while curr_sum > s and start < i: 14 | curr_sum = curr_sum - arr[start] 15 | start += 1 16 | 17 | if curr_sum == s: 18 | return "Found between {} and {}".format(start, i - 1) 19 | 20 | curr_sum = curr_sum + arr[i] 21 | i += 1 22 | 23 | return "Sum not found" 24 | 25 | arr = [15, 2, 4, 8, 9, 5, 10, 23] 26 | 27 | print(find_sum(arr, 6)) -------------------------------------------------------------------------------- /data_structures/array/find_missing_numbers.py: -------------------------------------------------------------------------------- 1 | def find(arr): 2 | n = len(arr) 3 | index = 0 4 | for i in range(n): 5 | index = abs(arr[i]) - 1 6 | arr[index] = -(abs(arr[index])) 7 | 8 | return [i+1 for i in range(len(arr)) if arr[i] > 0] 9 | -------------------------------------------------------------------------------- /data_structures/array/find_odd_number.py: -------------------------------------------------------------------------------- 1 | def find_odd_number(nums): 2 | """ Find one number in array which is not duplicated, 3 | or exsits odd times. 4 | """ 5 | s = 0 6 | for n in nums: 7 | s ^= n 8 | return s 9 | 10 | 11 | a = [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10] 12 | print(find_odd_number(a)) 13 | -------------------------------------------------------------------------------- /data_structures/array/find_sum.py: -------------------------------------------------------------------------------- 1 | def find(arr, target): 2 | i = 0 3 | j = len(arr) - 1 4 | while i < j: 5 | if arr[i] + arr[j] == target: 6 | return [i+1, j+1] 7 | elif arr[i] + arr[j] > target: 8 | j -= 1 9 | else: 10 | i += 1 11 | return [] 12 | -------------------------------------------------------------------------------- /data_structures/array/first_repeating_char.py: -------------------------------------------------------------------------------- 1 | # Find the first repeated character in a string without using extra space 2 | # With extra space its simple. Just check for the element in a hash map 3 | # If present, then it is the recurrent char 4 | 5 | # Without extra space idea - 6 | # The idea is to use an integer variable and uses bits in its binary representation to store whether a 7 | # character is present or not. 8 | # Typically an integer has at-least 32 bits and we need to store presence/absence of only 26 characters. 9 | 10 | def first_recurrence(s): 11 | checker = 0 12 | pos = 0 13 | for i in s: 14 | val = ord(i) - ord('a') 15 | if (checker & (1 << val) > 0): 16 | return i 17 | checker = checker | (1 << val) 18 | pos += 1 19 | return -1 20 | -------------------------------------------------------------------------------- /data_structures/array/flatten_array.py: -------------------------------------------------------------------------------- 1 | # Input - [1,2,3,4,[5,6,7,8],[9,10]] 2 | # Output - [1,2,3,4,5,6,7,8,9,10] 3 | 4 | lst = [1,2,3,4,[5,6,7,8],[9,10]] 5 | 6 | flat = [] 7 | 8 | for sub in lst: 9 | if isinstance(sub, list): 10 | flat.extend(sub) 11 | else: 12 | flat.append(sub) 13 | 14 | -------------------------------------------------------------------------------- /data_structures/array/intersection_sorted_array.py: -------------------------------------------------------------------------------- 1 | def intersection(arr1, arr2): 2 | res = [] 3 | i, j = 0, 0 4 | while i < len(arr1) and j < len(arr2): 5 | if arr1[i] < arr2[j]: 6 | i += 1 7 | elif arr2[j] < arr1[i]: 8 | j += 1 9 | else: 10 | res.append(arr1[i]) 11 | i += 1 12 | j += 1 13 | return res 14 | 15 | arr1 = [1,2,3,4,5,6,7,8] 16 | arr2 = [2,4,6,8] 17 | 18 | print(intersection(arr1, arr2)) 19 | 20 | -------------------------------------------------------------------------------- /data_structures/array/kadane_algorithm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kadane's algorithm is used to find the maximum contiguous sum in an array. 3 | The logic is simple. Take the first element in the sum and then find current max num. 4 | Curr max = max(arr[i], curr_max + arr[i]) - we add this number if it increases the sum, 5 | otherwise we take the number if it is more than the sum 6 | 7 | Then keep track of max of this value 8 | """ 9 | 10 | def max_sum(arr): 11 | max_so_far = arr[0] 12 | curr_max = arr[0] 13 | for i in range(1, len(arr)): 14 | curr_max = max(arr[i], curr_max + arr[i]) 15 | max_so_far = max(max_so_far, curr_max) 16 | 17 | return max_so_far 18 | 19 | 20 | -------------------------------------------------------------------------------- /data_structures/array/largest_element.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def largest(arr): 4 | l = -sys.maxsize 5 | for i in arr: 6 | if i > l: 7 | l = i 8 | print(l) 9 | 10 | arr = [1,2,3,4,5,6,7,8,2,4,5,7,34,56,23,1234,134,57,67,345] 11 | 12 | largest(arr) 13 | -------------------------------------------------------------------------------- /data_structures/array/majority_element.py: -------------------------------------------------------------------------------- 1 | # The only prerequisite condition of this algorithm is that the array 2 | # definitely contains a majority element, otherwise it will just return 3 | # the last element 4 | 5 | def majority(arr): 6 | maj_index = 0 7 | count = 1 8 | for i in range(1, len(arr)): 9 | if arr[i] == arr[maj_index]: 10 | count += 1 11 | else: 12 | count -= 1 13 | if count == 0: 14 | maj_index = i 15 | count = 1 16 | 17 | return arr[maj_index] 18 | 19 | 20 | arr = [3, 3, 1,5,6,8,3,0,7] 21 | 22 | print(majority(arr)) -------------------------------------------------------------------------------- /data_structures/array/max_consecutive_ones.py: -------------------------------------------------------------------------------- 1 | def max_ones(arr): 2 | max = 0 3 | count = 0 4 | for i in arr: 5 | if i == 1: 6 | count += 1 7 | if count > max: 8 | max = count 9 | if i == 0: 10 | count = 0 11 | return max 12 | -------------------------------------------------------------------------------- /data_structures/array/max_product.py: -------------------------------------------------------------------------------- 1 | def max_product(arr): 2 | n = len(arr) 3 | if n == 0: 4 | return 0 5 | if n == 1: 6 | return arr[0] 7 | a = arr[0] 8 | b = arr[1] 9 | maxprod = a * b 10 | for i in range(n): 11 | for j in range(i + 1, n): 12 | if (arr[i] * arr[j]) > maxprod: 13 | a = arr[i] 14 | b = arr[j] 15 | maxprod = a * b 16 | return maxprod 17 | -------------------------------------------------------------------------------- /data_structures/array/max_product_three_elements.py: -------------------------------------------------------------------------------- 1 | # Q - Find the max product of three elements of an array 2 | # A - Find the max 3 numbers and 2 min numbers. Then find the max of (min1*min2*max1, max1*max2*max3) 3 | 4 | import sys 5 | 6 | def product(arr): 7 | min1 = sys.maxsize 8 | min2 = sys.maxsize 9 | max1 = -sys.maxsize 10 | max2 = -sys.maxsize 11 | max3 = -sys.maxsize 12 | 13 | for n in arr: 14 | if n <= min1: 15 | min2 = min1 16 | min1 = n 17 | elif n <= min2: 18 | min2 = n 19 | 20 | if n >= max1: 21 | max3 = max2 22 | max2 = max1 23 | max1 = n 24 | elif n >= max2: 25 | max3 = max2 26 | max2 = n 27 | elif n >= max3: 28 | max3 = n 29 | 30 | return max(min1*min2*max1, max1*max2*max3) 31 | 32 | -------------------------------------------------------------------------------- /data_structures/array/max_triplet_sum.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def max_sum(arr): 4 | first = second = third = -sys.maxsize 5 | 6 | for i in arr: 7 | if i > first: 8 | third = second 9 | second = first 10 | first = i 11 | elif i > second: 12 | third = second 13 | second = i 14 | elif i > third: 15 | third = i 16 | 17 | return first + second + third 18 | 19 | 20 | arr = [1,3,6,3,6,8,3,3,7,9,4,-1,-1] 21 | 22 | print(max_sum(arr)) 23 | 24 | -------------------------------------------------------------------------------- /data_structures/array/min_product.py: -------------------------------------------------------------------------------- 1 | # Use these facts - 2 | 3 | # If there are even number of negative numbers and no zeros, 4 | # then the min product is the product of all except the max 5 | # negative value 6 | 7 | # If there are odd number of negative numbers and no zeros, 8 | # the result is simply the product of all 9 | 10 | # If there is a zero and all other are positive, then the result is zero 11 | 12 | # If there are only positive numbers, the the result is 13 | # the smallest positive number 14 | 15 | 16 | def find(arr): 17 | if len(arr) == 1: 18 | return arr[0] 19 | 20 | count_negative = 0 21 | count_zero = 0 22 | max_neg = float('-inf') 23 | min_pos = float('inf') 24 | 25 | prod = 1 26 | 27 | for num in arr: 28 | if num == 0: 29 | count_zero += 1 30 | continue 31 | 32 | if num < 0: 33 | count_negative += 1 34 | max_neg = max(max_neg, num) 35 | 36 | if num > 0: 37 | min_pos = min(min_pos, num) 38 | 39 | prod *= num 40 | 41 | if count_zero == len(arr) or (count_negative == 0 and count_zero > 0): 42 | return 0 43 | 44 | if count_negative == 0: 45 | return min_pos 46 | 47 | if count_negative & 1 == 0 and count_negative != 0: 48 | prod = int(prod / max_neg) 49 | 50 | return prod 51 | -------------------------------------------------------------------------------- /data_structures/array/min_swaps.py: -------------------------------------------------------------------------------- 1 | # Minimum swaps required to bring all elements less than or equal to k together 2 | 3 | def min_swaps(arr, k): 4 | # First find out how many elements are there which are less than or 5 | # equal to k 6 | count = 0 7 | for i in arr: 8 | if i <= k: 9 | count += 1 10 | 11 | # This count defines a window - inside this window all our elements should 12 | # be placed 13 | # Find the count of bad elements - elements which are more than k and that will be 14 | # our starting answer as we will have to swap them out 15 | bad = 0 16 | for i in range(0, count): 17 | if arr[i] > k: 18 | bad += 1 19 | 20 | ans = bad 21 | j = count 22 | 23 | for i in range(0, len(arr)): 24 | if j == len(arr): 25 | break 26 | 27 | if arr[i] > k: 28 | bad -= 1 # because we have moved the bad element out of the window 29 | 30 | if arr[j] > k: 31 | bad += 1 32 | 33 | ans = min(bad, ans) 34 | j += 1 35 | 36 | print('answer - ', ans) 37 | 38 | arr = [2,7,9,5,8,7,4] 39 | min_swaps(arr, 5) -------------------------------------------------------------------------------- /data_structures/array/moves_zeros_to_end.py: -------------------------------------------------------------------------------- 1 | # Move all zeros in an array to the end 2 | 3 | 4 | def move(arr): 5 | count = 0 6 | for a in arr: 7 | if not a == 0: 8 | arr[count] = a 9 | count += 1 10 | while count < len(nums): 11 | arr[count] = 0 12 | count += 1 13 | -------------------------------------------------------------------------------- /data_structures/array/number_of_1_in_sorted_array.py: -------------------------------------------------------------------------------- 1 | """ 2 | Count the number of 1s in a sorted array 3 | Instead of linearly searching the array to find the first occurence, 4 | do a binary search to find the first 0 5 | """ 6 | 7 | def count(arr): 8 | start = 0 9 | end = len(arr) - 1 10 | 11 | while start <= end: 12 | mid = (start + end) // 2 13 | if arr[mid] == 1 and (arr[mid + 1] == 0 or mid == high): 14 | return mid + 1 15 | if arr[mid] == 1: 16 | start = mid + 1 17 | else: 18 | end = mid - 1 19 | return 0 20 | 21 | 22 | arr = [0,0,0,0] 23 | 24 | print(count(arr)) 25 | -------------------------------------------------------------------------------- /data_structures/array/number_of_elements_that_can_searched_using_binary_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | In an input of unsorted integer array, find the number of elements 3 | that can be searched using binary search 4 | 5 | The idea is the an element is binary searchable if the elements to the 6 | left of it are smaller than it and the elements to the right of it 7 | are bigger than it 8 | 9 | So maintain two arrays - left_max and right_min such that in i'th index - 10 | 11 | * left_max[i] contains the max element between 0 and i-1 (left to right movement) 12 | * right_min[i] contains the min element between i+1 and n-1 (right to left movement) 13 | 14 | Now for every element in the array, if its index its i, then it is binary searchable 15 | if left_max[i] < arr[i] < right_min[i] 16 | """ 17 | import sys 18 | 19 | def get_searchable_numbers(arr, n): 20 | left_max = [None] * n 21 | right_min = [None] * n 22 | 23 | left_max[0] = float('-inf') 24 | right_min[n-1] = float('inf') 25 | 26 | for i in range(1, n): 27 | left_max[i] = max(left_max[i-1], arr[i-1]) 28 | 29 | for i in range(len(arr) - 2, -1, -1): 30 | right_min[i] = min(right_min[i+1], arr[i+1]) 31 | 32 | res = [] 33 | count = 0 34 | 35 | for i in range(0, n): 36 | num = arr[i] 37 | left = left_max[i] 38 | right = right_min[i] 39 | 40 | if left < num < right: 41 | res.append(num) 42 | count += 1 43 | 44 | return count, res 45 | 46 | 47 | if __name__ == '__main__': 48 | #arr = [5,1,4,3,6,8,10,7,9] 49 | arr = [4,1,3,9,8,10,11] 50 | count, res = get_searchable_numbers(arr, len(arr)) 51 | 52 | print(count, res) 53 | -------------------------------------------------------------------------------- /data_structures/array/partition_three_parts_equal_sum.py: -------------------------------------------------------------------------------- 1 | def partition(arr): 2 | s = sum(arr) 3 | if not s % 3 == 0: 4 | return False 5 | s = s / 3 6 | targets = [2*s, s] 7 | acc = 0 8 | 9 | for a in arr: 10 | acc += a 11 | if acc == targets[-1]: 12 | targets.pop() 13 | if not targets: 14 | return True 15 | 16 | return False 17 | -------------------------------------------------------------------------------- /data_structures/array/peak_element.py: -------------------------------------------------------------------------------- 1 | # A peak element is an element such that both of its neighbours are smaller than it 2 | # In case of corner elements, consider only one neighbour 3 | 4 | 5 | def peak(arr, low, high): 6 | n = len(arr) 7 | 8 | while low <= high: 9 | mid = (high - low) // 2 10 | 11 | if (mid == 0 or arr[mid-1] <= arr[mid]) and (mid == n-1 or arr[mid+1] <= arr[mid]): 12 | return(arr[mid]) 13 | 14 | elif mid > 0 and arr[mid-1] > arr[mid]: 15 | high = mid - 1 16 | 17 | else: 18 | low = mid + 1 19 | 20 | arr = [1, 3, 20, 4, 1, 0] 21 | print(peak(arr, 0, len(arr) - 1)) -------------------------------------------------------------------------------- /data_structures/array/permutations_of_word.py: -------------------------------------------------------------------------------- 1 | def permutation(lst): 2 | if len(lst) == 0: 3 | return [] 4 | if len(lst) == 1: 5 | return [lst] 6 | l = [] 7 | for i in range(len(lst)): 8 | m = lst[i] 9 | rem_lst = lst[:i] + lst[i+1:] 10 | for p in permutation(rem_lst): 11 | l.append([m] + p) 12 | return l 13 | 14 | 15 | data = list('hello world') 16 | for p in permutation(data): 17 | print(p) 18 | -------------------------------------------------------------------------------- /data_structures/array/pivot_index.py: -------------------------------------------------------------------------------- 1 | def pivot(arr): 2 | s = sum(arr) 3 | left_sum = 0 4 | for i, x in enumerate(arr): 5 | if left_sum == (s - x - left_sum): 6 | return i 7 | left_sum += x 8 | return -1 9 | -------------------------------------------------------------------------------- /data_structures/array/product_of_array_except_self.py: -------------------------------------------------------------------------------- 1 | def product(arr): 2 | prods = [1] * len(arr) 3 | 4 | temp = 1 5 | 6 | for i in range(len(arr)): 7 | prods[i] = temp 8 | temp = temp * prods[i] 9 | 10 | temp = 1 11 | 12 | for i in reversed(range(len(arr))): 13 | prods[i] = prods[i] * temp 14 | temp = temp * arr[i] 15 | 16 | return temp 17 | -------------------------------------------------------------------------------- /data_structures/array/quick_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Quicksort Running Time: 3 | Quick sort average case is O(n log n) 4 | each level takes O(n) but splitting the data is O(log n) 5 | O(n) * O(log n) = O(n log n) 6 | Worse case is O(log n2) 7 | if pivot is smallest value, each level is O(n) and splitting the data is O(n) 8 | O(n) * O(n) = O(n2) 9 | """ 10 | 11 | # quicksort function 12 | def quicksort(array): 13 | if len(array) < 2: 14 | return array 15 | else: 16 | pivot = array[0] 17 | less = [i for i in array[1:] if i <= pivot] 18 | greater = [i for i in array[1:] if i > pivot] 19 | return quicksort(less) + [pivot] + quicksort(greater) 20 | 21 | array = [100, 5, 72, 41, 80, 1, 99, 36, 27, 78] 22 | print(quicksort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] 23 | 24 | -------------------------------------------------------------------------------- /data_structures/array/random_matrix.py: -------------------------------------------------------------------------------- 1 | def random_matrix(n): 2 | """ Generate random n x n matrix without repeated entries in rows or columns. 3 | The entries are integers between 1 and n. 4 | """ 5 | from random import shuffle 6 | 7 | a = list(range(n + 1)) 8 | shuffle(a) 9 | 10 | # Use slicing to left rotate 11 | m = [a[i:] + a[:i] for i in range(n + 1)] 12 | 13 | # Shuffle rows in matrix 14 | shuffle(m) 15 | 16 | # Shuffle cols in matrix (optional) 17 | m = list(map(list, zip(*m))) # Transpose the matrix 18 | shuffle(m) 19 | 20 | return m 21 | 22 | 23 | m = random_matrix(9) 24 | print('\n'.join(map(str, m))) 25 | -------------------------------------------------------------------------------- /data_structures/array/rearrange_positive_negative.py: -------------------------------------------------------------------------------- 1 | # Rearragne positive and negative numbers in an array such that they appear 2 | # alternately. If there are more numbers of any one kind, put them at the end 3 | 4 | def rearrange(arr): 5 | i = -1 6 | 7 | for j in range(len(arr)): 8 | if arr[j] < 0: 9 | i += 1 # maintaining index of the last negative number 10 | arr[j], arr[i] = arr[i], arr[j] 11 | 12 | pos = i + 1 # index of first positive number 13 | neg = 0 # index of first negative number 14 | 15 | while pos < len(arr) and neg < pos and arr[neg] < 0: 16 | arr[pos], arr[neg] = arr[neg], arr[pos] 17 | pos += 1 18 | neg += 2 19 | 20 | print(arr) 21 | 22 | arr = [-1, 2, -3, 4, 5, 6, -7, 8, 9] 23 | rearrange(arr) -------------------------------------------------------------------------------- /data_structures/array/right_place.py: -------------------------------------------------------------------------------- 1 | def place(arr): 2 | for i in range(len(arr)): 3 | if arr[i] >= 0 and arr[i] != i: 4 | arr[arr[i]], arr[i] = arr[i], arr[arr[i]] 5 | else: 6 | i += 1 7 | 8 | arr = [-1,-1, 6,1,9,3,2,-1,4,-1] 9 | place(arr) 10 | print(arr) 11 | -------------------------------------------------------------------------------- /data_structures/array/rotation.py: -------------------------------------------------------------------------------- 1 | def gcd(a, b): 2 | if b == 0: 3 | return a 4 | return gcd(b, a%b) 5 | 6 | 7 | def rotate(arr, d): 8 | n = len(arr) 9 | g = gcd(n, d) 10 | 11 | for i in range(g): 12 | temp = arr[i] 13 | j = i 14 | while 1: 15 | k = j + d 16 | if k >= n: 17 | k -= n 18 | if k == i: 19 | break 20 | arr[j] = arr[k] 21 | j = k 22 | arr[j] = temp 23 | 24 | 25 | arr = [1,2,3,4,5] 26 | rotate(arr, 3) 27 | print(arr) 28 | -------------------------------------------------------------------------------- /data_structures/array/rotation_simplified.py: -------------------------------------------------------------------------------- 1 | def rotate(arr, d): 2 | return arr[d:]+arr[:d] 3 | 4 | arr = [1,2,3,4,5] 5 | print(rotate(arr, 1)) 6 | -------------------------------------------------------------------------------- /data_structures/array/sort_by_parity.py: -------------------------------------------------------------------------------- 1 | def sort_parity(arr): 2 | i = 0 3 | j = len(arr) - 1 4 | while i < j: 5 | if arr[i] % 2 > arr[j] % 2: 6 | arr[i], arr[j] = arr[j], arr[i] 7 | if arr[i] % 2 == 0: 8 | i += 1 9 | if arr[j] % 2 == 1: 10 | j -= 1 11 | 12 | arr = [5,6,4,3,2,4,5,6,7,8,9,8,7,4] 13 | 14 | sort_parity(arr) 15 | print(arr) 16 | -------------------------------------------------------------------------------- /data_structures/array/sorted_array_three_common_elements.py: -------------------------------------------------------------------------------- 1 | def common(arr1, arr2, arr3): 2 | res = [] 3 | i, j, k = 0, 0, 0 4 | 5 | while i < len(arr1) and j < len(arr2) and k < len(arr3): 6 | if arr1[i] == arr2[j] and arr2[j] == arr3[k]: 7 | res.append(arr1[i]) 8 | i += 1 9 | j += 1 10 | k += 1 11 | elif arr1[i] < arr2[j]: 12 | i += 1 13 | elif arr2[j] < arr3[k]: 14 | j += 1 15 | else: 16 | k += 1 17 | return res 18 | 19 | arr1 = [1,2,3,4,5,6,7,8,9, 10, 11 ,12] 20 | arr2 = [2,4,6,8, 10, 12] 21 | arr3 = [3,6,9, 12] 22 | 23 | print(common(arr1, arr2, arr3)) 24 | -------------------------------------------------------------------------------- /data_structures/array/square_of_sorted_array.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the square of all the numbers of a sorted array such that after finding the square of the sorted array, the 3 | resultant array containing the squared numbers remains sorted 4 | """ 5 | 6 | def square(arr): 7 | n = len(arr) 8 | j = 0 9 | 10 | # Counting the number of negative elements 11 | while j < n and arr[j] < 0: 12 | j += 1 13 | 14 | i = j - 1 # index of last negative element 15 | ans = [] 16 | 17 | while 0 <= i and j < n: 18 | if arr[i] ** 2 < arr[j] ** 2: 19 | ans.append(arr[i] ** 2) 20 | i -= 1 21 | else: 22 | ans.append(arr[j] ** 2) 23 | j += 1 24 | 25 | while i >= 0: 26 | ans.append(arr[i] ** 2) 27 | i -= 1 28 | 29 | while j < n: 30 | ans.append(arr[j] ** 2) 31 | j += 1 32 | 33 | return ans 34 | -------------------------------------------------------------------------------- /data_structures/array/three_largest_elements.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def three_largest(arr): 4 | first = second = third = -sys.maxsize 5 | for i in arr: 6 | if i > first: 7 | third = second 8 | second = first 9 | first = i 10 | elif i > second: 11 | third = second 12 | second = i 13 | elif i > third: 14 | third = i 15 | print(first, second, third) 16 | 17 | 18 | arr = [10,45,3,7,4,6,8,9,4,6,4,23,45,56,47,25,34,67,634] 19 | three_largest(arr) 20 | 21 | -------------------------------------------------------------------------------- /data_structures/array/transpose_matrix.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | def transpose(self, A: List[List[int]]) -> List[List[int]]: 3 | l=[] 4 | i=0 5 | while(i!=len(A[0])): 6 | x=[] 7 | j=0 8 | while(j arr2[j]: 9 | res.append(arr2[j]) 10 | j += 1 11 | else: 12 | res.append(arr1[i]) 13 | res.append(arr2[j]) 14 | i += 1 15 | j += 1 16 | 17 | while i < len(arr1): 18 | res.append(arr1[i]) 19 | i += 1 20 | while j < len(arr2): 21 | res.append(arr2[j]) 22 | j += 1 23 | return res 24 | 25 | 26 | arr1 = [1,3,5,6,7,9] 27 | arr2 = [2,4,5,8,10] 28 | 29 | print(union(arr1, arr2)) 30 | 31 | -------------------------------------------------------------------------------- /data_structures/binary_trees/array_to_binary_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Convert an array to a binary tree 3 | 4 | Sample input - 5 | [1,2,3,4,5,null,6,7,null,null,null,null,8] 6 | 7 | Note - 8 | if a tree has N nodes and is complete, then the no of internal 9 | nodes can be (N-1) / 2 10 | """ 11 | 12 | class Node: 13 | 14 | def __init__(self, val): 15 | self.val = val 16 | self.left = None 17 | self.right = None 18 | 19 | 20 | def create_tree(arr): 21 | curr_ptr = 0 22 | child_ptr = 0 23 | 24 | root = Node(arr[0]) 25 | curr_node = root 26 | 27 | while i < (len(arr) - 1)/2: 28 | curr_ptr = arr[i] 29 | child_ptr = i + 1 30 | 31 | left_child = arr[child_ptr] 32 | right_child = arr[child_ptr + 1] 33 | 34 | curr_node.left = Node(left_child) 35 | curr_node.right = Node(right_child) 36 | 37 | 38 | -------------------------------------------------------------------------------- /data_structures/binary_trees/check_all_leaves_at_same_level.py: -------------------------------------------------------------------------------- 1 | # check if all the leaves of a tree are at the same level 2 | 3 | class Node: 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def check_util(root, level): 12 | if root is None: 13 | return True 14 | 15 | if root.left is None and root.right is None: 16 | if check.leaf_level == 0: 17 | check.leaf_level = level 18 | return True 19 | 20 | return level == check.leaf_level 21 | 22 | return check_util(root.left, level + 1) and check_util(root.right, level + 1) 23 | 24 | 25 | def check(root): 26 | level = 0 27 | check.leaf_level = 0 28 | return check_util(root, level) 29 | -------------------------------------------------------------------------------- /data_structures/binary_trees/check_cousin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check if two nodes are cousin or not 3 | 4 | Condition for being cousin - 5 | 1. Level is same 6 | 2. They are not siblings 7 | 8 | """ 9 | 10 | class Node: 11 | 12 | def __init__(self, val): 13 | self.val = val 14 | self.left = None 15 | self.right = None 16 | 17 | 18 | def level(root, node, lev): 19 | if not root: 20 | return 0 21 | 22 | if root == node: 23 | return lev 24 | 25 | l = level(root.left, node, lev+1) 26 | 27 | if not l == 0: 28 | return l 29 | 30 | l = level(root.right, node, lev+1) 31 | 32 | 33 | def is_sibling(root, a, b): 34 | if not root: 35 | return False 36 | 37 | return (root.left == a and root.right == b) or \ 38 | (root.right == b and root.left == a) or \ 39 | is_sibling(root.left, a, b) or \ 40 | is_sibling(root.right, a, b) 41 | 42 | 43 | def is_cousin(root, a, b): 44 | if level(root, a, 1) == level(root, b, 1) and not is_sibling(root, a, b): 45 | return True 46 | else: 47 | return False 48 | 49 | 50 | root = Node(1) 51 | root.left = Node(2) 52 | root.right = Node(3) 53 | root.left.left = Node(4) 54 | root.left.right = Node(5) 55 | root.left.right.right = Node(15) 56 | root.right.left = Node(6) 57 | root.right.right = Node(7) 58 | root.right.left.right = Node(8) 59 | 60 | node1 = root.left.right 61 | node2 = root.right.right 62 | 63 | print(is_cousin(root, node1, node2)) -------------------------------------------------------------------------------- /data_structures/binary_trees/check_divide_in_two_halves.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check if removing an edge of a binary tree can divide 3 | the tree in two equal halves 4 | 5 | Solution - Count the number of nodes, say n. Then traverse the tree 6 | in bottom up manner and find the size of every subtree (s). Check if n - s = s 7 | """ 8 | 9 | class Node: 10 | 11 | def __init__(self, val): 12 | self.val = val 13 | self.left = None 14 | self.right = None 15 | 16 | 17 | def count(root): 18 | if not root: 19 | return 0 20 | return count(root.left) + count(root.right) + 1 21 | 22 | 23 | def check_util(root, n): 24 | if root == None: 25 | return False 26 | 27 | # Check for root 28 | if count(root) == n - count(root): 29 | return True 30 | 31 | # Check for all the other nodes 32 | return check_util(root.left, n) or check_util(root.right, n) 33 | 34 | 35 | def check(root): 36 | n = count(root) 37 | return check_util(root, n) 38 | 39 | 40 | root = Node(5) 41 | root.left = Node(1) 42 | root.right = Node(6) 43 | root.left.left = Node(3) 44 | root.right.left = Node(7) 45 | root.right.right = Node(4) 46 | 47 | print(check(root)) -------------------------------------------------------------------------------- /data_structures/binary_trees/check_full_binary_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | A full binary tree is a tree which has either 0 children or 2 children 3 | """ 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.left = None 10 | self.right = None 11 | 12 | 13 | def check(root): 14 | if not root: 15 | return True 16 | 17 | if not root.left and not root.right: 18 | return True 19 | 20 | if root.left and root.right: 21 | return check(root.left) and check(root.right) 22 | 23 | 24 | root = Node(0) 25 | root.left = Node(1) 26 | root.right = Node(2) 27 | 28 | if check(root): 29 | print('True') 30 | else: 31 | print("False") -------------------------------------------------------------------------------- /data_structures/binary_trees/check_if_path_exists.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given a path in an array form, check if this path leads to a leaf node 3 | 4 | """ 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.right = None 10 | self.left = None 11 | 12 | 13 | def check_path(root, arr, n, index): 14 | if root is None: 15 | return n == 0 16 | 17 | if root.left == None and root.right == None and root.val == arr[index] and index == n -1: 18 | return True 19 | 20 | return (index < n) and (root.val == arr[index]) and (check_path(root.left, arr, n, index + 1) or check_path(root.right, arr, n, index + 1)) 21 | -------------------------------------------------------------------------------- /data_structures/binary_trees/check_perfect_binary_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | A binary tree is perfect if all the internal nodes have 2 children and 3 | all the leaves are at the same level 4 | """ 5 | 6 | class Node: 7 | 8 | def __init__(self, val): 9 | self.val = val 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | # Returns depth of leftmost leaf 15 | def find_depth(root): 16 | d = 0 17 | while root: 18 | d += 1 19 | root = root.left 20 | return d 21 | 22 | 23 | def check_perfect(root, d, level=0): 24 | if not root: 25 | return True 26 | 27 | # If leaf node, then its depth must be same as that of other nodes 28 | if root.left is None and root.right is None: 29 | return d == (level + 1) 30 | 31 | # An internal node with only one child 32 | if root.left is None or root.right is None: 33 | return False 34 | 35 | return check_perfect(root.left, d, level+1) and check_perfect(root.right, d, level+1) 36 | 37 | 38 | def is_perfect(root): 39 | d = find_depth(root) 40 | return check_perfect(root, d) 41 | -------------------------------------------------------------------------------- /data_structures/binary_trees/children_sum_property.py: -------------------------------------------------------------------------------- 1 | # Check whether a tree statisfies children sum property 2 | # Children sum property is such that every partent = left + right child 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | 12 | def sum_prop(root): 13 | l = r = 0 14 | 15 | if not root or (not root.left and not root.right): 16 | return True 17 | 18 | else: 19 | if root.left: 20 | l = root.left.val 21 | 22 | if root.right: 23 | r = root.right.val 24 | 25 | if (root.val == l + r) and sum_prop(root.left) and sum_prop(root.right): 26 | return True 27 | else: 28 | return False 29 | -------------------------------------------------------------------------------- /data_structures/binary_trees/continuous_tree.py: -------------------------------------------------------------------------------- 1 | # A continuous tree is such that the absolute difference between two 2 | # adjacent nodes is 1 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | 12 | def continuous(root): 13 | # Can be continuous if 14 | # 1. Root is none 15 | # 2. Both left and right STs are none 16 | # 3. If left ST is none, check for right 17 | # 4. If right ST is none, check for left 18 | # 5. Else check for everything 19 | 20 | if root is None: 21 | return True 22 | 23 | if root.left == None and root.right == None: 24 | return True 25 | 26 | if root.left == None: 27 | return (abs(root.val - root.right.val) == 1) and continuous(root.right) 28 | 29 | if root.right == None: 30 | return (abs(root.val - root.left.val) == 1) and continuous(root.left) 31 | 32 | return (abs(root.val - root.right.val) == 1) and (abs(root.left.val - root.val) == 1) and continuous(root.left) and continuous(root.right) 33 | 34 | -------------------------------------------------------------------------------- /data_structures/binary_trees/diagonal_tree.py: -------------------------------------------------------------------------------- 1 | # Print tree in a diagonal form 2 | 3 | # We use the distance of slope from the rightmost slope as key 4 | # in a dict and then proceed 5 | 6 | class Node: 7 | 8 | def __init__(self, val): 9 | self.val = val 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | def diagonal_print_util(root, d, diagonal_map): 15 | if root is None: 16 | return 17 | 18 | try: 19 | diagonal_map[d].append(root.val) 20 | except: 21 | diagonal_map[d] = [root.val] 22 | 23 | # Increase vertical distance if left child 24 | diagonal_print_util(root.left, d+1, diagonal_map) 25 | 26 | # Vertical distance remains same for the right child 27 | diagonal_print_util(root.right, d, diagonal_map) 28 | 29 | 30 | def diagonal_print(root): 31 | diagonal_map = dict() 32 | 33 | diagonal_print_util(root, 0, diagonal_map) 34 | 35 | for i in diagonal_map: 36 | for j in diagonal_map[i]: 37 | print(j, end=" ") 38 | print('') 39 | -------------------------------------------------------------------------------- /data_structures/binary_trees/diameter.py: -------------------------------------------------------------------------------- 1 | # Diameter of a binary tree is the longest path between two leaf nodes of a binary tree 2 | # Diameter of a binary tree is maximum value of (left_height + right_height + 1) for each node 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | 12 | def height(root, ans): 13 | if not root: 14 | return 0 15 | 16 | lheight = height(root.left, ans) 17 | rheight = height(root.right, ans) 18 | 19 | ans[0] = max(ans[0], 1 + lheight + rheight) # This is for diameter 20 | 21 | return 1 + max(lheight, rheight) # This is for height 22 | 23 | 24 | def diameter(root): 25 | if not root: 26 | return 0 27 | 28 | ans = [-9999999999] 29 | 30 | h = height(root, ans) 31 | return ans[0] 32 | -------------------------------------------------------------------------------- /data_structures/binary_trees/evaluate_expresssion_tree.py: -------------------------------------------------------------------------------- 1 | # In expression tree, the integer values will be at the leaf nodes 2 | # The operands will be all the other internal nodes 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.right = None 9 | self.left = None 10 | 11 | 12 | def evaluate(root): 13 | if root is None: 14 | return 0 15 | 16 | # Check for leaf node 17 | if root.left is None and root.right is None: 18 | return int(root.data) 19 | 20 | # Evaluate left tree 21 | left_sum = evaluate(root.left) 22 | 23 | # Evaluate right tree 24 | right_sum = evaluate(root.right) 25 | 26 | # Check which operator to apply 27 | if root.data == '+': 28 | return left_sum + right_sum 29 | 30 | elif root.data == '-': 31 | return left_sum - right_sum 32 | 33 | elif root.data == '*': 34 | return left_sum * right_sum 35 | 36 | else: 37 | return left_sum / right_sum 38 | -------------------------------------------------------------------------------- /data_structures/binary_trees/flip_tree.py: -------------------------------------------------------------------------------- 1 | # Flip a tree such like here 2 | # https://www.geeksforgeeks.org/flip-binary-tree/ 3 | 4 | # Flipping subtree algorithm 5 | # 1. root->left->left = root->right 6 | # 2. root->left->right = root 7 | # 3. root->left = NULL 8 | # 4. root->right = NULL 9 | 10 | 11 | class Node: 12 | 13 | def __init__(self, val): 14 | self.val = val 15 | self.left = None 16 | self.right = None 17 | 18 | 19 | def flip_tree(root): 20 | if root is None: 21 | return root 22 | 23 | if root.left is None and root.right is None: 24 | return root 25 | 26 | flipped_root = flip_tree(root.left) 27 | 28 | root.left.left = root.right 29 | root.left.right = root 30 | root.left = None 31 | root.right = None 32 | 33 | return flipped_root 34 | -------------------------------------------------------------------------------- /data_structures/binary_trees/foldable_or_symmetric_tree.py: -------------------------------------------------------------------------------- 1 | # A foldable tree is one such that the mirror of left subtree is equal 2 | # to the right subtree 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.right = None 9 | self.left = None 10 | 11 | 12 | def foldable(left, right): 13 | if left is None and right is None: 14 | return True 15 | 16 | if left is None or right is None: 17 | return False 18 | 19 | return left.val == right.val and foldable(left.left, right.right) and foldable(left.right, right.left) 20 | 21 | 22 | root = Node(4) 23 | 24 | root.left = Node(2) 25 | root.left.left = Node(1) 26 | 27 | root.right = Node(2) 28 | root.right.right = Node(10) 29 | 30 | print(foldable(root.left, root.right)) 31 | 32 | -------------------------------------------------------------------------------- /data_structures/binary_trees/height_of_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the height of a binary tree 3 | 4 | Height - (max of left height and right height) + 1 5 | """ 6 | 7 | class Node: 8 | 9 | def __init__(self, val): 10 | self.val = val 11 | self.left = None 12 | self.right = None 13 | 14 | 15 | def height(root): 16 | if not root: 17 | return 0 18 | 19 | else: 20 | lheight = height(root.left) 21 | rheight = height(root.right) 22 | 23 | return 1 + max(lheight, rheight) 24 | 25 | 26 | root = Node(1) 27 | root.left = Node(2) 28 | root.right = Node(3) 29 | root.left.left = Node(7) 30 | root.left.right = Node(6) 31 | root.right.left = Node(5) 32 | root.right.right = Node(4) 33 | root.right.right.right = Node(40) 34 | 35 | print(height(root)) -------------------------------------------------------------------------------- /data_structures/binary_trees/identical_trees.py: -------------------------------------------------------------------------------- 1 | # Check if two trees are identical or not 2 | 3 | class Node: 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def identical(root1, root2): 12 | if root1 is None and root2 is None: 13 | return True 14 | 15 | if root1 is not None and root2 is not None: 16 | return root1.val == root2.val and identical(root1.left, root2.left) and identical(root1.right, root2.right) 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /data_structures/binary_trees/largest_subtree_sum.py: -------------------------------------------------------------------------------- 1 | # Find the largest subtree sum in a binary tree 2 | # Do a postorder traversal 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | 12 | def sum_util(root, ans): 13 | if root is None: 14 | return 0 15 | 16 | s = root.val + sum_util(root.left, ans) + sum_util(root.right, ans) 17 | 18 | ans[0] = max(ans[0], s) 19 | 20 | return s 21 | 22 | 23 | def find_sum(root): 24 | if root is None: 25 | return 0 26 | 27 | ans = [-99999999] 28 | 29 | sum_util(root, ans) 30 | 31 | return ans[0] 32 | -------------------------------------------------------------------------------- /data_structures/binary_trees/left_right_to_down_right.py: -------------------------------------------------------------------------------- 1 | # Convert a tree that is a left right representation to a 2 | # down right representation 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.rigt = None 10 | 11 | 12 | def convert(root): 13 | if root is None: 14 | return 15 | 16 | convert(root.left) 17 | convert(root.right) 18 | 19 | if root.left == None: 20 | root.left = root.right 21 | else: 22 | root.left.right = root.right 23 | 24 | root.right = None 25 | -------------------------------------------------------------------------------- /data_structures/binary_trees/left_view.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print left view of a binary tree 3 | """ 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.left = None 10 | self.right = None 11 | 12 | 13 | def left_view_util(root, max_level, level): 14 | if not root: 15 | return 16 | 17 | if max_level[0] < level: 18 | print(root.val) 19 | max_level[0] = level 20 | 21 | left_view_util(root.left, max_level, level+1) 22 | left_view_util(root.right, max_level, level+1) 23 | 24 | 25 | def left_view(root): 26 | max_level = [0] 27 | left_view_util(root, max_level, 1) 28 | 29 | 30 | root = Node(1) 31 | root.left = Node(2) 32 | root.right = Node(3) 33 | root.left.left = Node(4) 34 | root.left.left.left = Node(5) 35 | root.left.left.left.left = Node(6) 36 | root.right.right = Node(7) 37 | root.right.right.right = Node(8) 38 | left_view(root) -------------------------------------------------------------------------------- /data_structures/binary_trees/logical_and_tree.py: -------------------------------------------------------------------------------- 1 | # Given a tree that contains only 0s and 1s, convert it into 2 | # a logical AND tree. A logical AND tree is such that 3 | # left_child AND right_child = AND_VALUE 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.left = None 10 | self.right = None 11 | 12 | 13 | def convert(root): 14 | if root is None: 15 | return 16 | 17 | convert(root.left) 18 | convert(root.right) 19 | 20 | if root.left and root.right: 21 | root.val = root.left.val & root.right.val 22 | -------------------------------------------------------------------------------- /data_structures/binary_trees/longest_path_with_same_value.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the longest path in the tree with the same value 3 | """ 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.right = None 10 | self.left = None 11 | 12 | 13 | def length(root, ans): 14 | if not root: 15 | return 0 16 | 17 | left = length(root.left, ans) 18 | right = length(root.right, ans) 19 | 20 | left_max = 0 21 | right_max = 0 22 | 23 | if root.left and root.left.val == root.val: 24 | left_max += 1 25 | 26 | if root.right and root.right.val == root.val: 27 | right_max += 1 28 | 29 | ans[0] = max(ans[0], left_max + right_max) 30 | 31 | return max(left_max, right_max) 32 | 33 | 34 | def longest_path(root): 35 | ans = [0] 36 | length(root, ans) 37 | return ans[0] 38 | -------------------------------------------------------------------------------- /data_structures/binary_trees/maximum_left_node.py: -------------------------------------------------------------------------------- 1 | # Find the max value in only the left nodes of the tree 2 | 3 | # Do inorder traversal and just keep track of the values 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.right = None 10 | self.left = None 11 | 12 | 13 | def find(root): 14 | res = -999999999999 15 | 16 | if not root: 17 | return res 18 | 19 | if root.left != None: 20 | res = root.left.val 21 | 22 | return max(find(root.left), res, find(root.right)) 23 | -------------------------------------------------------------------------------- /data_structures/binary_trees/perfect_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check if a given binary tree is perfect or not 3 | """ 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.left = None 10 | self.right = None 11 | 12 | 13 | def find_depth(node): 14 | d = 0 15 | while node: 16 | d += 1 17 | node = node.left 18 | return d 19 | 20 | 21 | def is_perfect_util(root, d, level=0): 22 | if not root: 23 | return True 24 | 25 | if not root.left and not root.right: 26 | return d == level + 1 27 | 28 | if not root.left or not root.right: 29 | return False 30 | 31 | return is_perfect_util(root.left, d, level+1) and is_perfect_util(root.right, d, level+1) 32 | 33 | 34 | def is_perfect(root): 35 | depth = find_depth(root) 36 | return is_perfect_util(root, depth) 37 | 38 | 39 | root = Node(10) 40 | root.left = Node(20) 41 | root.right = Node(30) 42 | root.left.left = Node(40) 43 | root.left.right = Node(50) 44 | root.right.left = Node(60) 45 | root.right.right = Node(70) 46 | 47 | print(is_perfect(root)) -------------------------------------------------------------------------------- /data_structures/binary_trees/print_all_root_to_leaf_paths.py: -------------------------------------------------------------------------------- 1 | # Traverse the tree in preorder fashion 2 | # append value in stack 3 | # if root.left and root.right is None, then print all stack values 4 | # traverse left 5 | # traverse right 6 | # pop from stack 7 | 8 | class Node: 9 | 10 | def __init__(self, val): 11 | self.val = val 12 | self.right = None 13 | self.left = None 14 | 15 | 16 | def print_route(root, stack): 17 | if root == None: 18 | return 19 | 20 | stack.append(root.val) 21 | if root.left == None and root.right == None: 22 | for i in stack: 23 | print(i, end=' ') 24 | print() 25 | 26 | print_route(root.left, stack) 27 | print_route(root.right, stack) 28 | stack.pop() 29 | 30 | 31 | root = Node(1) 32 | root.left = Node(2) 33 | root.right = Node(3) 34 | root.left.left = Node(4) 35 | root.left.right = Node(5) 36 | 37 | print_route(root, []) 38 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_full_nodes.py: -------------------------------------------------------------------------------- 1 | # Given a tree, print all the full nodes of it 2 | # A full node is such that both its children are present 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.right = None 9 | self.left = None 10 | 11 | 12 | def print_full(root): 13 | if not root: 14 | return 15 | if root.left and root.right: 16 | print(root.val, end=' ') 17 | print_full(root.left) 18 | print_full(root.right) 19 | 20 | 21 | root = Node(10) 22 | root.left = Node(8) 23 | root.right = Node(2) 24 | 25 | root.left.left = Node(3) 26 | root.left.right = Node(5) 27 | 28 | root.right.left = Node(7) 29 | 30 | print_full(root) 31 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_nodes_at_k_distance_from_root.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def print_k(root, k): 10 | if root is None: 11 | return 12 | if k == 0: 13 | print(root.val) 14 | else: 15 | print_k(root.left, k-1) 16 | print_k(root.right, k-1) 17 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_nodes_with_no_sibling.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def no_siblings(root): 10 | if root is None: 11 | return 12 | 13 | if root.left is not None and root.right is not None: 14 | no_siblings(root.left) 15 | no_siblings(root.right) 16 | 17 | elif root.right is not None: 18 | print(root.right.val) 19 | no_siblings(root.right) 20 | 21 | elif root.left is not None: 22 | print(root.left.val) 23 | no_siblings(root.left) 24 | 25 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_odd_level_nodes.py: -------------------------------------------------------------------------------- 1 | # Print all the nodes at odd levels of a binary tree 2 | 3 | class Node: 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def print_odd(root, is_odd=True): 12 | if not root: 13 | return 14 | 15 | if is_odd: 16 | print(root.val, end=' ') 17 | 18 | print_odd(root, not is_odd) 19 | print_odd(root, not is_odd) 20 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_path_to_a_node.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def has_path(root, stack, x): 10 | if not root: 11 | return False 12 | 13 | stack.append(root.val) 14 | 15 | if root.val == x: 16 | return True 17 | 18 | if has_path(root.left, stack, x) or has_path(root.right, stack, x): 19 | return True 20 | 21 | stack.pop() 22 | return False 23 | 24 | 25 | def print_path(root, x): 26 | arr = [] 27 | 28 | if has_path(root, arr, x): 29 | for i in arr: 30 | print(i, end=' ') 31 | else: 32 | print('Path not present') 33 | -------------------------------------------------------------------------------- /data_structures/binary_trees/print_spiral_tree_two_stacks.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def print_spiral(root): 10 | 11 | s1 = [] 12 | s2 = [] 13 | 14 | s1.append(root) 15 | 16 | while not len(s1) == 0 or not len(s2) == 0: 17 | 18 | while not len(s1) == 0: 19 | temp = s1.pop() 20 | print(temp.data, end=' ') 21 | 22 | if temp.right: 23 | s2.append(temp.right) 24 | if temp.left: 25 | s2.append(temp.left) 26 | 27 | while not len(s2) == 0: 28 | temp = s2.pop() 29 | print(temp.data, end=' ') 30 | 31 | if temp.left: 32 | s1.append(temp.left) 33 | if temp.right: 34 | s1.append(temp.right) 35 | -------------------------------------------------------------------------------- /data_structures/binary_trees/right_view.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print right view of a binary tree 3 | 4 | The idea behind using max_level[0] is that - 5 | 1. Its value when changes will be reflected in every recursion call 6 | 2. We are visiting right side first. So this acts as a check that that level was done 7 | """ 8 | 9 | class Node: 10 | 11 | def __init__(self, val): 12 | self.val = val 13 | self.left = None 14 | self.right = None 15 | 16 | 17 | def right_view_util(root, max_level, level): 18 | if not root: 19 | return 20 | 21 | if max_level[0] < level: 22 | print(root.val) 23 | max_level[0] = level 24 | 25 | right_view_util(root.right, max_level, level+1) 26 | right_view_util(root.left, max_level, level+1) 27 | 28 | 29 | def right_view(root): 30 | max_level = [0] 31 | right_view_util(root, max_level, 1) 32 | 33 | 34 | root = Node(1) 35 | root.left = Node(2) 36 | root.right = Node(3) 37 | root.left.left = Node(4) 38 | root.left.right = Node(5) 39 | root.right.left = Node(6) 40 | root.right.right = Node(7) 41 | root.right.left.right = Node(8) 42 | right_view(root) -------------------------------------------------------------------------------- /data_structures/binary_trees/spiral_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Do level order traversal of a tree in spiral form 3 | """ 4 | 5 | class Node: 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.left = None 10 | self.right = None 11 | 12 | 13 | def height(root): 14 | if not root: 15 | return 0 16 | 17 | lheight = height(root.left) 18 | rheight = height(root.right) 19 | 20 | return 1 + max(lheight, rheight) 21 | 22 | 23 | def print_spiral(root): 24 | h = height(root) 25 | 26 | left_to_right = False 27 | 28 | for i in range(1, h+1): 29 | print_level(root, i, left_to_right) 30 | left_to_right = not left_to_right 31 | 32 | 33 | def print_level(root, level, left_to_right): 34 | if not root: 35 | return 36 | 37 | if level == 1: 38 | print(root.val, end=' ') 39 | elif level > 1: 40 | if left_to_right: 41 | print_level(root.left, level-1, left_to_right) 42 | print_level(root.right, level-1, left_to_right) 43 | else: 44 | print_level(root.right, level-1, left_to_right) 45 | print_level(root.left, level-1, left_to_right) 46 | 47 | 48 | root = Node(1) 49 | root.left = Node(2) 50 | root.right = Node(3) 51 | root.left.left = Node(7) 52 | root.left.right = Node(6) 53 | root.right.left = Node(5) 54 | root.right.right = Node(4) 55 | 56 | print_spiral(root) -------------------------------------------------------------------------------- /data_structures/binary_trees/sum_of_all_left_leaves.py: -------------------------------------------------------------------------------- 1 | # Find the sum of all left leaves of a binary tree 2 | 3 | 4 | class Node: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.left = None 9 | self.right = None 10 | 11 | 12 | def is_leaf(root): 13 | if root is None: 14 | return False 15 | if root.left is None and root.right is None: 16 | return True 17 | return False 18 | 19 | 20 | def sum_left(root): 21 | s = 0 22 | stack = [] 23 | 24 | while True: 25 | if root: 26 | stack.append(root) 27 | root = root.left 28 | else: 29 | if not stack: 30 | break 31 | root = stack.pop() 32 | if is_leaf(root.left): 33 | s += root.left.val 34 | root = root.right 35 | 36 | return s 37 | 38 | 39 | root = Node(9) 40 | root.left = Node(8) 41 | root.right = Node(6) 42 | root.right.left = Node(1) 43 | 44 | root.left.left = Node(5) 45 | root.left.right = Node(2) 46 | 47 | print(sum_left(root)) 48 | -------------------------------------------------------------------------------- /data_structures/binary_trees/sum_of_all_nodes.py: -------------------------------------------------------------------------------- 1 | # Find the sum of all nodes of a binary tree 2 | 3 | class Node: 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def sum_nodes(root): 12 | if root is None: 13 | return 0 14 | 15 | return root.val + sum_nodes(root.left) + sum_nodes(root.right) 16 | -------------------------------------------------------------------------------- /data_structures/binary_trees/sum_of_perfect_binary_tree.py: -------------------------------------------------------------------------------- 1 | # https://www.geeksforgeeks.org/find-sum-nodes-given-perfect-binary-tree/ 2 | 3 | import math 4 | 5 | def sum_nodes(l): 6 | leaf_nodes = math.pow(2, l-1) 7 | 8 | s = (leaf_nodes + (leaf_nodes + 1)) / 2 9 | 10 | res = s * l 11 | 12 | return int(res) 13 | -------------------------------------------------------------------------------- /data_structures/binary_trees/top_view.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print the top view of a binary tree 3 | 4 | Almost like vertical traversal 5 | """ 6 | 7 | class Node: 8 | 9 | def __init__(self, val): 10 | self.val = val 11 | self.left = None 12 | self.right = None 13 | self.col = None 14 | 15 | 16 | def top_view(root): 17 | if not root: 18 | return 19 | 20 | queue = [] 21 | col = 0 22 | d = {} 23 | 24 | queue.append(root) 25 | root.col = col 26 | 27 | while queue: 28 | root = queue.pop(0) 29 | col = root.col 30 | 31 | if col not in d: 32 | d[col] = root.val 33 | 34 | if root.left: 35 | queue.append(root.left) 36 | root.left.col = col - 1 37 | if root.right: 38 | queue.append(root.right) 39 | root.right.col = col + 1 40 | 41 | for i in sorted(d): 42 | print(d[i], end=" ") 43 | 44 | 45 | root = Node(1) 46 | root.left = Node(2) 47 | root.right = Node(3) 48 | root.left.right = Node(4) 49 | root.left.right.right = Node(5) 50 | root.left.right.right.right = Node(6) 51 | 52 | top_view(root) -------------------------------------------------------------------------------- /data_structures/binary_trees/traversals.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | class Tree: 10 | 11 | def __init__(self, root): 12 | self.root = root 13 | 14 | 15 | def inorder() 16 | -------------------------------------------------------------------------------- /data_structures/bst/average_of_levels.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the mathematical average of all levels of a BST 3 | """ 4 | 5 | import collections 6 | 7 | class Node(): 8 | 9 | def __init__(self, val): 10 | self.val = val 11 | self.left = None 12 | self.right = None 13 | 14 | 15 | def mean(arr): 16 | m = 0 17 | for x in arr: 18 | m += x 19 | return m / len(arr) 20 | 21 | 22 | def bfs(root): 23 | if not root: 24 | return 25 | queue = collections.deque([root]) 26 | result = [] 27 | while queue: 28 | next_queue = collections.deque() 29 | for node in queue: 30 | if node.left: 31 | next_queue.append(node.left) 32 | if node.right: 33 | next_queue.append(node.right) 34 | result.append(mean(queue)) 35 | queue = next_queue 36 | return result 37 | 38 | -------------------------------------------------------------------------------- /data_structures/bst/bfs.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | class Node(): 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def bfs(root): 12 | if not root: 13 | return 14 | 15 | queue = collections.deque([root]) 16 | 17 | while queue: 18 | temp = queue.popleft() 19 | print(temp.val) 20 | if temp.right: 21 | queue.append(temp.right) 22 | if temp.left: 23 | queue.append(temp.left) 24 | 25 | 26 | root = Node(3) 27 | root.right = Node(4) 28 | root.left = Node(2) 29 | 30 | root.left.left = Node(1) 31 | root.left.right = Node(2.5) 32 | 33 | root.right.left = Node(3.5) 34 | root.right.right = Node(5) 35 | 36 | bfs(root) 37 | -------------------------------------------------------------------------------- /data_structures/bst/binary_search_tree.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | class BST(): 10 | 11 | def __init__(self): 12 | self.root = Node(None) 13 | 14 | 15 | def insert(self, new_data): 16 | if self.root is None: 17 | self.root = Node(new_data) 18 | else: 19 | if self.root.val < new_data: 20 | 21 | self.insert(self.root.right, new_data) 22 | else: 23 | self.insert(self.root.left, new_data) 24 | 25 | 26 | def inorder(self): 27 | if self.root: 28 | self.inorder(self.root.left) 29 | print(self.root.val) 30 | self.inorder(self.root.right) 31 | 32 | 33 | if __name__ == '__main__': 34 | tree = BST() 35 | tree.insert(5) 36 | tree.insert(4) 37 | tree.insert(7) 38 | tree.inorder() 39 | -------------------------------------------------------------------------------- /data_structures/bst/bt_to_bst.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def store_inorder(root, inorder): 10 | if root is None: 11 | return 12 | 13 | store_inorder(root.left, inorder) 14 | inorder.append(root.data) 15 | store_inorder(root.right, inorder) 16 | 17 | 18 | def count_nodes(root): 19 | if root is None: 20 | return 0 21 | return count_nodes(root.left) + count_nodes(root.right) + 1 22 | 23 | 24 | def array_to_bst(arr, root): 25 | if root is None: 26 | return 27 | array_to_bst(arr, root.left) 28 | root.data = arr[0] 29 | arr.pop(0) 30 | array_to_bst(arr, root.right) 31 | 32 | 33 | def bt_to_bst(root): 34 | if root is None: 35 | return 36 | 37 | n = count_nodes(root) 38 | arr = [] 39 | store_inorder(root, arr) 40 | arr.sort() 41 | array_to_bst(arr, root) 42 | 43 | -------------------------------------------------------------------------------- /data_structures/bst/ceil.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def ceil(root, key): 10 | if not root: 11 | return -1 12 | if root.val == key: 13 | return root.val 14 | if root.val < key: 15 | return ceil(root.right, key) 16 | val = ceil(root.left, key) 17 | return val if val >= key else root.key 18 | 19 | 20 | -------------------------------------------------------------------------------- /data_structures/bst/check_bt_is_subtree_of_another_bt.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def check(root1, root2): 10 | if root1 == None: 11 | return True 12 | if root2 == None: 13 | return False 14 | if are_identical(root1, root2): 15 | return True 16 | return (check(root1.left, root2) or check(root1.right, root2)) 17 | 18 | 19 | def are_identical(root1, root2): 20 | if root1 == None and root2 == None: 21 | return True 22 | if root1 == None or root2 == None: 23 | return False 24 | return (root1.val == root2.val and are_identical(root1.left, root2.left) and are_identical(root1.right, root2.right)) 25 | 26 | 27 | -------------------------------------------------------------------------------- /data_structures/bst/check_if_bt_if_bst.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | MAX = sys.maxsize 4 | MIN = -sys.maxsize 5 | 6 | class Node: 7 | 8 | def __init__(self, val): 9 | self.val = val 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | def check_BST(root, min, max): 15 | if root is None: 16 | return True 17 | 18 | if root.val < min or root.val > max: 19 | return False 20 | 21 | return (check_BST(root.left, min, root.val - 1) and check_BST(root.right, root.val + 1, max)) 22 | 23 | root = Node(5) 24 | root.left = Node(4) 25 | root.right = Node(7) 26 | 27 | print(check_BST(root, MIN, MAX)) 28 | -------------------------------------------------------------------------------- /data_structures/bst/closest_element.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Node(): 4 | 5 | def __init__(self, val): 6 | self.val = val 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def closest_element(root, k): 12 | if not root: 13 | return None 14 | curr = root 15 | min_diff = sys.maxsize 16 | element = None 17 | while curr: 18 | if curr.val == k: 19 | return curr.val 20 | if abs(curr.val - k) < min_diff: 21 | min_diff = abs(curr.val - k) 22 | element = curr.val 23 | 24 | if curr.val > k: 25 | curr = curr.left 26 | else: 27 | curr = curr.right 28 | return element 29 | 30 | 31 | root = Node(5) 32 | root.left = Node(3) 33 | root.right = Node(10) 34 | 35 | root.left.left = Node(2) 36 | root.left.right = Node(4) 37 | 38 | root.right.right = Node(20) 39 | root.right.left = Node(8) 40 | 41 | print(closest_element(root, 18)) 42 | -------------------------------------------------------------------------------- /data_structures/bst/convert_bst_to_right_node_tree.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def increasing_bst(root): 10 | def inorder(node): 11 | if node: 12 | yield from inorder(node.left) 13 | yield node.val 14 | yield from inorder(node.right) 15 | ans = curr = Node(None) 16 | for v in inorder(root): 17 | curr.right = Node(v) 18 | curr = curr.right 19 | return ans.right 20 | -------------------------------------------------------------------------------- /data_structures/bst/deletion.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def inorder(root): 10 | if not root: 11 | return None 12 | stack = [] 13 | while True: 14 | if root: 15 | stack.append(root) 16 | root = root.left 17 | else: 18 | if not stack: 19 | break 20 | root = stack.pop() 21 | print(root.val, end=" ") 22 | root = root.right() 23 | 24 | 25 | def min_value_node(root): 26 | curr = root 27 | while curr.left: 28 | curr = curr.left 29 | return curr 30 | 31 | 32 | def delete(root, val): 33 | if not root: 34 | return root 35 | 36 | if val < root.val: 37 | root.left = delete(root.left, val) 38 | 39 | elif val > root.val: 40 | root.right = delete(root.right, val) 41 | 42 | else: 43 | # Root with one child or no child 44 | if root.left is None: 45 | temp = root.right 46 | root = None 47 | return temp 48 | 49 | elif root.right is None: 50 | temp = root.left 51 | root = None 52 | return temp 53 | 54 | temp = min_value_node(root.right) 55 | root.val = temp.val 56 | root.right = delete(root.right, temp.val) 57 | 58 | return root 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /data_structures/bst/dfs_recursion.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | # Depth First Search 9 | def inorder(root): 10 | if root: 11 | inorder(root.left) 12 | print(root.val) 13 | inorder(root.right) 14 | 15 | 16 | def postorder(root): 17 | if root: 18 | postorder(root.left) 19 | postorder(root.right) 20 | print(root.val) 21 | 22 | 23 | def preorder(root): 24 | if root: 25 | print(root.val) 26 | postorder(root.left) 27 | postorder(root.right) 28 | 29 | 30 | root = Node(3) 31 | root.left = Node(2) 32 | root.right = Node(4) 33 | root.left.left = Node(1) 34 | root.right.right = Node(5) 35 | 36 | inorder(root) 37 | 38 | 39 | -------------------------------------------------------------------------------- /data_structures/bst/diameter.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def height(root, ans): 10 | if not root: 11 | return 0 12 | lheight = height(root.left, ans) 13 | rheight = height(root.right, ans) 14 | 15 | # Diameter is basically the max of (1 + lheight + rheight) 16 | # So we are storing it here to reduce calling it again 17 | # O(n) 18 | ans[0] = max(ans[0], 1 + lheight + rheight) 19 | 20 | return 1 + max(lheight, rheight) 21 | 22 | 23 | def diameter(root): 24 | if not root: 25 | return 0 26 | ans =[-99999999999] 27 | h = height(root, ans) 28 | return ans[0] 29 | 30 | 31 | if __name__ == '__main__': 32 | 33 | root = Node(5) 34 | root.left = Node(3) 35 | root.right = Node(7) 36 | root.left.left = Node(2) 37 | root.left.right = Node(4) 38 | root.right.right = Node(8) 39 | root.right.left = Node(6) 40 | print(diameter(root)) 41 | -------------------------------------------------------------------------------- /data_structures/bst/insertion_iterative.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def insert(root, val): 10 | new_node = Node(val) 11 | parent = None 12 | curr = root 13 | while curr: 14 | parent = curr 15 | if curr.val <= val: 16 | curr = curr.right 17 | else: 18 | curr = curr.left 19 | 20 | if parent.val <= val: 21 | parent.right = new_node 22 | else: 23 | parent.left = new_node 24 | 25 | 26 | def inorder(root): 27 | if not root: 28 | return None 29 | stack = [] 30 | while True: 31 | if root: 32 | stack.append(root) 33 | root = root.left 34 | else: 35 | if not stack: 36 | break 37 | root = stack.pop() 38 | print(root.val, end=" ") 39 | root = root.right 40 | 41 | 42 | root = Node(4) 43 | insert(root, 2) 44 | insert(root, 6) 45 | insert(root, 1) 46 | insert(root, 8) 47 | inorder(root) 48 | -------------------------------------------------------------------------------- /data_structures/bst/insertion_recursive.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | def insertion_recursive(root, val): 9 | if not root: 10 | return Node(val) 11 | else: 12 | if root.val < val: 13 | if root.right is None: 14 | root.right = Node(val) 15 | else: 16 | insertion_recursive(root.right, val) 17 | else: 18 | if root.left is None: 19 | root.left = Node(val) 20 | else: 21 | insertion_recursive(root.left, val) 22 | 23 | 24 | def inorder(root): 25 | if root: 26 | inorder(root.left) 27 | print(root.val) 28 | inorder(root.right) 29 | 30 | 31 | 32 | root = Node(5) 33 | root.left = Node(3) 34 | root.right = Node(7) 35 | 36 | root.left.left = Node(1) 37 | root.left. right = Node(4) 38 | 39 | root.right.right = Node(8) 40 | inorder(root) 41 | insertion_recursive(root, 6) 42 | 43 | inorder(root) 44 | -------------------------------------------------------------------------------- /data_structures/bst/kth_largest_in_bst.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print the kth largest element in a BST 3 | 4 | The inorder traversal gives elements of BST in ascending order. Do reverse inorder 5 | (Right-Node-Left) and print the kth element 6 | """ 7 | 8 | 9 | class Node(): 10 | 11 | def __init__(self, val): 12 | self.val = val 13 | self.left = None 14 | self.right = None 15 | 16 | 17 | def reverse_inorder(root, k): 18 | if not root: 19 | return None 20 | counter = 1 21 | stack = [] 22 | while True: 23 | if root: 24 | stack.append(root) 25 | root = root.right 26 | else: 27 | if not stack: 28 | break 29 | root = stack.pop() 30 | if counter == k: 31 | return root.val 32 | else: 33 | counter += 1 34 | root = root.left 35 | 36 | return "not enough elements in BST" 37 | 38 | 39 | root = Node(5) 40 | root.left = Node(3) 41 | root.right = Node(7) 42 | 43 | root.left.left = Node(2) 44 | root.left.right = Node(4) 45 | 46 | root.right.right = Node(8) 47 | root.right.eft = Node(6) 48 | 49 | k = int(input("Enter K : ")) 50 | 51 | ans = reverse_inorder(root, k) 52 | print(ans) 53 | -------------------------------------------------------------------------------- /data_structures/bst/kth_smallest_in_bst.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print the kth smallest number in BST 3 | 4 | The inorder traversal of BST gives elements in ascending order. So do inorder, 5 | keep count and return the kth value 6 | """ 7 | 8 | 9 | class Node(): 10 | 11 | def __init__(self, val): 12 | self.val = val 13 | self.left = None 14 | self.right = None 15 | 16 | 17 | def inorder(root, k): 18 | if not root: 19 | return None 20 | stack = [] 21 | counter = 1 22 | while True: 23 | if root: 24 | stack.append(root) 25 | root = root.left 26 | else: 27 | if not stack: 28 | break 29 | root = stack.pop() 30 | if counter == k: 31 | return root.val 32 | else: 33 | counter += 1 34 | root = root.right 35 | return "tree not big enough" 36 | 37 | 38 | root = Node(5) 39 | root.left = Node(3) 40 | root.right = Node(7) 41 | 42 | root.left.left = Node(2) 43 | root.right.right = Node(4) 44 | 45 | root.right.left = Node(6) 46 | root.right.right = Node(8) 47 | 48 | print(inorder(root, 3)) 49 | 50 | -------------------------------------------------------------------------------- /data_structures/bst/linked_list_to_bst.py: -------------------------------------------------------------------------------- 1 | class TreeNode(): 2 | 3 | def __init__(self, val ,left, right): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | class LLNode(): 10 | 11 | def __init__(self, val, next): 12 | self.val = val 13 | self.next = None 14 | 15 | 16 | def linked_list_to_bst(head): 17 | if not head: 18 | return None 19 | curr = head 20 | n = 0 21 | while curr: 22 | n += 1 23 | curr = curr.next 24 | 25 | return ll_to_bst_recur(head, n) 26 | 27 | 28 | def ll_to_bst_recur(head, n): 29 | if n <= 0: 30 | return None 31 | # TODO: Fix me! 32 | # left = ll_to_bst_recur( 33 | 34 | -------------------------------------------------------------------------------- /data_structures/bst/lowest_common_ancestor.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def lca(root, n1, n2): 10 | while root: 11 | if root.val > n1 and root.val > n2: 12 | root = root.left 13 | elif root.val < n1 and root.val < n2: 14 | root = root.right 15 | else: 16 | return root.val 17 | return root 18 | 19 | 20 | root = Node(5) 21 | root.left = Node(3) 22 | root.right = Node(7) 23 | 24 | root.left.left = Node(2) 25 | root.left.right = Node(4) 26 | root.left.left.left = Node(1) 27 | 28 | root.right.right = Node(8) 29 | root.right.left = Node(6) 30 | 31 | print(lca(root, 2, 6)) 32 | -------------------------------------------------------------------------------- /data_structures/bst/merge_sum.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.right = None 6 | self.left = None 7 | 8 | 9 | def merge(t1, t2): 10 | if not t1: 11 | return t2 12 | if not t2: 13 | return t1 14 | t1.val = t1.val + t2.val 15 | t1.left = merge(t1.left, t2.left) 16 | t2.right = merge(t1.right, t2.right) 17 | return t1 18 | 19 | 20 | -------------------------------------------------------------------------------- /data_structures/bst/min_max_value_in_bst.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.right = None 6 | self.left = None 7 | 8 | 9 | def min_value(root): 10 | if not root: 11 | return None 12 | curr = root 13 | while curr.left: 14 | curr = curr.left 15 | return curr.val 16 | 17 | 18 | def max_value(root): 19 | if not root: 20 | return None 21 | curr = root 22 | while curr.right: 23 | curr = curr.right 24 | return curr.val 25 | 26 | 27 | root = Node(5) 28 | root.left = Node(3) 29 | root.right = Node(7) 30 | 31 | root.left.left = Node(1) 32 | root.left.right = Node(4) 33 | 34 | root.right.left = Node(6) 35 | 36 | print(max_value(root)) 37 | print(min_value(root)) 38 | -------------------------------------------------------------------------------- /data_structures/bst/print_ancestor.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def print_ancestor_recursive(root, key): 10 | if not root: 11 | return False 12 | if root.val == key: 13 | return True 14 | if print_ancestor_recursive(root.left, key) or print_ancestor_recursive(root.right, key): 15 | return root.data 16 | return False 17 | -------------------------------------------------------------------------------- /data_structures/bst/print_left_node.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.right = None 6 | self.left = None 7 | 8 | 9 | def print_leaves(root): 10 | 11 | def dfs(node): 12 | if not node.left and not node.right: 13 | yield node.val 14 | yield from dfs(node.left) 15 | yield from dfs(node.right) 16 | 17 | return list(dfs(root)) 18 | -------------------------------------------------------------------------------- /data_structures/bst/range_sum.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def range_sum_preorder(root, L, R): 10 | stack = [root] 11 | sum = 0 12 | while stack: 13 | node = stack.pop() 14 | if node: 15 | if L <= node.val <= R: 16 | sum += node.val 17 | if L < node.val: 18 | stack.append(node.left) 19 | if node.val < R: 20 | stack.append(node.right) 21 | return sum 22 | 23 | 24 | root = Node(5) 25 | root.left = Node(3) 26 | root.right = Node(7) 27 | 28 | root.left.left = Node(1) 29 | root.left.right = Node(4) 30 | 31 | root.right.right = Node(10) 32 | root.right.left = Node(6) 33 | 34 | print(range_sum_preorder(root, 7, 10)) 35 | -------------------------------------------------------------------------------- /data_structures/bst/reverse_inorder_traversal.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | def reverse_inorder(root): 10 | if not root: 11 | return None 12 | stack = [] 13 | arr = [] 14 | while True: 15 | if root: 16 | stack.append(root) 17 | root = root.right 18 | else: 19 | if not stack: 20 | break 21 | root = stack.pop() 22 | arr.append(root.val) 23 | root = root.left 24 | return arr 25 | 26 | 27 | root = Node(5) 28 | root.left = Node(3) 29 | root.right = Node(7) 30 | 31 | root.left.left = Node(2) 32 | root.left.right = Node(4) 33 | 34 | root.right.right = Node(8) 35 | root.right.left = Node(6) 36 | 37 | lst = reverse_inorder(root) 38 | print(lst) 39 | 40 | -------------------------------------------------------------------------------- /data_structures/bst/search.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | def search(root, val): 9 | if root is None or root.val == val: 10 | return root 11 | if root.val < val: 12 | return search(root.left, val) 13 | else: 14 | return search(root.right, val) 15 | 16 | 17 | def search_iterative(root, val): 18 | while root: 19 | if val > root.val: 20 | root = root.right 21 | elif val < root.val: 22 | root = root.left 23 | else: 24 | return True 25 | return False 26 | -------------------------------------------------------------------------------- /data_structures/bst/second_largest_in_bst.py: -------------------------------------------------------------------------------- 1 | """ 2 | There can be two condition for finding the second largest element in a BST 3 | 4 | 1. If right subtree does not exist, find the largest on the left side 5 | Otherwise, 6 | 2. If right exists but right's left and right's right do not, that means 7 | you are currently at the second largest element 8 | 9 | Move to right 10 | """ 11 | 12 | class Node: 13 | 14 | def __init__(self, val): 15 | self.val = val 16 | self.left = None 17 | self.right = None 18 | 19 | 20 | def find_largest(root): 21 | curr = root 22 | while curr: 23 | if not curr.right: 24 | return curr.val 25 | curr = curr.right 26 | 27 | 28 | def second_largest(root): 29 | if not root or (not root.left and not root.right): 30 | return "BST should have atleast 2 nodes" 31 | 32 | curr = root 33 | 34 | while curr: 35 | if curr.left and not curr.right: 36 | return find_largest(curr.left) 37 | 38 | if curr.right and not curr.right.left and not curr.right.right: 39 | return curr.val 40 | 41 | curr = curr.right 42 | 43 | 44 | def insert(root, key): 45 | if root == None: 46 | return Node(key) 47 | if key < root.val: 48 | root.left = insert(root.left, key) 49 | elif key > root.val: 50 | root.right = insert(root.right, key) 51 | return root 52 | 53 | 54 | if __name__ == '__main__': 55 | root = Node(6) 56 | insert(root, 5) 57 | insert(root, 3) 58 | insert(root, 10) 59 | insert(root, 4) 60 | insert(root, 11) 61 | insert(root, 14) 62 | insert(root, 1) 63 | 64 | print(second_largest(root)) 65 | 66 | 67 | -------------------------------------------------------------------------------- /data_structures/bst/sorted_array_to_bst.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.left = None 6 | self.right = None 7 | 8 | 9 | # get the middle of the array and make it root. 10 | # middle of the left becomes left child 11 | # middle of the right becomes right child 12 | 13 | def sorted_array_to_bst(arr): 14 | 15 | if not arr: 16 | return None 17 | 18 | mid = len(arr)//2 19 | 20 | root = Node(arr[mid]) 21 | 22 | root.left = sorted_array_to_bst(arr[:mid]) 23 | root.right = sorted_array_to_bst(arr[mid+1:]) 24 | return root 25 | 26 | 27 | def inorder(root): 28 | if not root: 29 | return None 30 | stack = [] 31 | while True: 32 | if root: 33 | stack.append(root) 34 | root = root.left 35 | else: 36 | if not stack: 37 | break 38 | root = stack.pop() 39 | print(root.val, end=" ") 40 | root = root.right 41 | 42 | 43 | arr = [1,2,3,4,5,6,7,8,9] 44 | root = sorted_array_to_bst(arr) 45 | inorder(root) 46 | -------------------------------------------------------------------------------- /data_structures/bst/trim_bst.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trim a BST so that all elements lie within a given high and low range 3 | """ 4 | 5 | class Node(): 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.right = None 10 | self.left = None 11 | 12 | 13 | def trim(root, L, R): 14 | if not root: 15 | return None 16 | if root.val > R: 17 | return trim(root.left, L, R) 18 | if root.val < L: 19 | return trim(root.right, L, R) 20 | root.left = trim(root.left, L, R) 21 | root.right = trim(root.right, L, R) 22 | return root 23 | -------------------------------------------------------------------------------- /data_structures/circular_linked_list/check_circular_linked_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check if a linked list is a circular linked list 3 | """ 4 | 5 | class Node(): 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.next = None 10 | 11 | 12 | def check(head): 13 | if not head: 14 | return True 15 | curr = head 16 | while curr: 17 | if curr.next == head: 18 | return True 19 | elif curr.next == None: 20 | return False 21 | curr = curr.next 22 | 23 | 24 | first = Node(1) 25 | second = Node(2) 26 | third = Node(3) 27 | 28 | first.next = second 29 | second.next = third 30 | 31 | print(check(first)) 32 | -------------------------------------------------------------------------------- /data_structures/circular_linked_list/delete.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | def delete(head, val): 8 | if head == None: 9 | return "List is empty" 10 | 11 | curr = head 12 | prev = None 13 | 14 | while curr.val != val: 15 | if curr.next == head: 16 | return "Val not in list" 17 | prev = curr 18 | curr = curr.next 19 | 20 | if curr.next == head: 21 | head = None 22 | return "Deleted" 23 | 24 | if curr == head: 25 | prev = head 26 | while prev.next != head: 27 | prev = prev.next 28 | head = curr.next 29 | prev.next = head 30 | 31 | elif curr.next == head: 32 | prev.next = head 33 | 34 | else: 35 | prev.next = curr.next 36 | 37 | return "Deleted" 38 | 39 | -------------------------------------------------------------------------------- /data_structures/circular_linked_list/traversal.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | def push(head, val): 8 | if not head: 9 | head = Node(val) 10 | head.next = head 11 | return 12 | curr = head 13 | while curr: 14 | if curr.next == head: 15 | break 16 | curr = curr.next 17 | curr.next = Node(val) 18 | curr.next.next = head 19 | 20 | 21 | def print_list(head): 22 | if not head: 23 | return 24 | curr = head 25 | while curr: 26 | print(curr.val, end=" ") 27 | curr = curr.next 28 | if curr == head: 29 | break 30 | 31 | first = Node(1) 32 | second = Node(2) 33 | third = Node(3) 34 | 35 | first.next = second 36 | second.next = third 37 | third.next = first 38 | 39 | print_list(first) 40 | 41 | push(first, 4) 42 | 43 | print_list(first) 44 | -------------------------------------------------------------------------------- /data_structures/deque/deque.py: -------------------------------------------------------------------------------- 1 | class Deque(): 2 | def __init__(self): 3 | self.data = list() 4 | 5 | def push_front(self, elem): 6 | temp = list() 7 | temp.append(elem) 8 | 9 | for i in self.data: 10 | temp.append(i) 11 | 12 | self.data = temp 13 | 14 | def push_back(self, elem): 15 | self.data.append(elem) 16 | 17 | def pop_front(self): 18 | temp = list() 19 | 20 | for i in range(0, len(self.data)): 21 | if not i==0: 22 | temp.append(self.data[i]) 23 | 24 | self.data = temp 25 | 26 | def pop_back(self): 27 | temp = list() 28 | 29 | for i in range(0, len(self.data)): 30 | if not i==len(self.data)-1: 31 | temp.append(self.data[i]) 32 | 33 | self.data = temp 34 | 35 | def get_first(self): 36 | if(len(self.data)>0): 37 | return self.data[0] 38 | else: 39 | return "Deque is empty" 40 | 41 | def get_last(self): 42 | if(len(self.data)>0): 43 | return self.data[len(self.data)-1] 44 | else: 45 | return "Deque is empty" 46 | 47 | def size(self): 48 | return len(self.data) 49 | 50 | def is_empty(self): 51 | if len(self.data) == 0: 52 | return True 53 | return False 54 | 55 | def contains(self, elem): 56 | for i in self.data: 57 | if i==elem: 58 | return True 59 | 60 | return False 61 | 62 | def print_elements(self): 63 | result = "" 64 | 65 | for i in self.data: 66 | result += str(i) + " | " 67 | 68 | print(result) -------------------------------------------------------------------------------- /data_structures/graphs/adjacency_list.py: -------------------------------------------------------------------------------- 1 | class AdjNode(): 2 | 3 | def __init__(self, val): 4 | self.vertex = val 5 | self.next = None 6 | 7 | 8 | class Graph: 9 | 10 | def __init__(self, vertices): 11 | self.V = vertices 12 | self.graph = [None] * self.V 13 | 14 | # adding in undirected graph 15 | def add_edge(self, src, dest): 16 | # adding node to the source node 17 | node = AdjNode(dest) 18 | node.next = self.graph[src] 19 | self.graph[src] = node 20 | 21 | # Adding source node to the destination as it is an 22 | # undirected graph 23 | node = AdjNode(src) 24 | node.next = self.graph[dest] 25 | self.graph[dest] = node 26 | 27 | 28 | def print_graph(self): 29 | for i in range(self.V): 30 | print("Adjacency list of vertex {}\n head".format(i), end="") 31 | temp = self.graph[i] 32 | while temp: 33 | print(" -> {}".format(temp.vertex), end="") 34 | temp = temp.next 35 | print(" \n") 36 | 37 | 38 | -------------------------------------------------------------------------------- /data_structures/graphs/adjacency_matrix.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | 3 | def __init__(self, vertices, directed: bool): 4 | self.V = vertices 5 | self.e = 0 6 | self.d = directed 7 | self.graph = [[0 for i in range(vertices)] for j in range(vertices)] 8 | 9 | def add_edge(self, ver1, ver2): 10 | if self.d: 11 | self.graph[ver1][ver2] = 1 12 | else: 13 | self.graph[ver1][ver2] = 1 14 | self.graph[ver2][ver1] = 1 15 | 16 | def remove_edge(self, ver1, ver2): 17 | if self.graph[ver1][ver2] == 0: 18 | print("No edge between %d and %d" % (ver1, ver2)) 19 | return 20 | if self.d: 21 | self.graph[ver1][ver2] = 0 22 | else: 23 | self.graph[ver1][ver2] = 0 24 | self.graph[ver2][ver1] = 0 25 | 26 | def print_graph(self): 27 | for i in self.graph: 28 | print(i) 29 | 30 | if __name__=="__main__": 31 | g1 = Graph(3,0) 32 | g1.add_edge(0,0) 33 | g1.add_edge(1,1) 34 | g1.add_edge(2,2) 35 | g1.remove_edge(2,1) 36 | g1.print_graph() 37 | 38 | -------------------------------------------------------------------------------- /data_structures/graphs/all_paths_between_two_vertices.py: -------------------------------------------------------------------------------- 1 | # Use backtracking 2 | # The only poroblem with this approach is that if there is a cycle, then 3 | # it can show infinitely many paths 4 | # Reference - https://www.geeksforgeeks.org/count-possible-paths-two-vertices/ 5 | 6 | class Graph: 7 | 8 | def __init__(self, vertices): 9 | self.vertices = vertices 10 | self.graph = [[] for i in range(vertices)] 11 | 12 | 13 | def add_edge(self, u, v): 14 | self.graph[u].append(v) 15 | 16 | 17 | def count_paths_util(self, u, v, visited, counter): 18 | visited[u] = True 19 | 20 | # If the destination vertex is found 21 | if u == v: 22 | counter[0] += 1 23 | 24 | else: 25 | for i in range(len(self.graph[u])): 26 | if not visited[self.graph[u][i]]: 27 | self.count_paths_util(self.graph[u][i], v, visited, counter) 28 | 29 | visited[u] = False 30 | 31 | 32 | def count_paths(self, u, v): 33 | visited = [False] * self.vertices 34 | 35 | counter = [0] 36 | 37 | self.count_paths_util(u, v, visited, counter) 38 | 39 | return counter[0] 40 | 41 | 42 | g = Graph(4) 43 | g.add_edge(0, 1) 44 | g.add_edge(0, 2) 45 | g.add_edge(0, 3) 46 | g.add_edge(2, 0) 47 | g.add_edge(2, 1) 48 | g.add_edge(1, 3) 49 | 50 | print(g.count_paths(2, 3)) 51 | -------------------------------------------------------------------------------- /data_structures/graphs/bfs.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class Graph: 4 | 5 | def __init__(self, vertices): 6 | self.graph = defaultdict(list) 7 | self.vertices = vertices 8 | 9 | 10 | def add_edge(self, u, v): 11 | self.graph[u].append(v) 12 | 13 | 14 | def bfs(self, s): 15 | visited = [False] * self.vertices 16 | 17 | queue = [] 18 | 19 | queue.append(s) 20 | visited[s] = True 21 | 22 | bfs = [] 23 | 24 | while queue: 25 | s = queue.pop(0) 26 | print(s, end=' ') 27 | for i in self.graph[s]: 28 | if visited[i] == False: 29 | queue.append(i) 30 | visited[i] = True 31 | 32 | g = Graph(6) 33 | 34 | g.add_edge(0, 1) 35 | g.add_edge(0, 2) 36 | g.add_edge(1, 3) 37 | g.add_edge(0, 3) 38 | g.add_edge(2, 4) 39 | g.add_edge(3, 4) 40 | g.add_edge(3, 5) 41 | 42 | g.bfs(0) 43 | -------------------------------------------------------------------------------- /data_structures/graphs/check_if_graph_is_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | A graph is a tree if - 3 | 1. It does not contain cycles 4 | 2. The graph is connected 5 | 6 | Do DFS and see if every vertex can be visited from a source vertex and check for cycle 7 | """ 8 | 9 | from collections import defaultdict 10 | 11 | class Graph: 12 | 13 | 14 | def __init__(self, vertices): 15 | self.vertices = vertices 16 | self.graph = defaultdict(list) 17 | 18 | 19 | def add_edge(self, u, v): 20 | self.graph[u].append(v) 21 | self.graph[v].append(u) 22 | 23 | 24 | def is_tree(self, s): 25 | visited = [False] * self.vertices 26 | parent = [-1] * self.vertices 27 | stack = [] 28 | 29 | visited[s] = True 30 | no_of_visited = 1 31 | stack.append(s) 32 | 33 | while stack: 34 | s = stack.pop() 35 | 36 | for i in self.graph[s]: 37 | if visited[i] == False: 38 | parent[i] = s 39 | visited[i] = True 40 | stack.append(i) 41 | no_of_visited += 1 42 | elif parent[s] != i: 43 | return "Not a tree" 44 | 45 | if no_of_visited == self.vertices: 46 | return "Graph is Tree" 47 | else: 48 | return "Not a tree" 49 | 50 | 51 | g = Graph(7) 52 | g.add_edge(0, 1) 53 | g.add_edge(0, 2) 54 | g.add_edge(1, 3) 55 | g.add_edge(2, 4) 56 | g.add_edge(4, 5) 57 | g.add_edge(1, 6) 58 | 59 | print(g.is_tree(0)) -------------------------------------------------------------------------------- /data_structures/graphs/connected_components_undirected_graphs.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.vertices = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | self.graph[v].append(u) 14 | 15 | 16 | def dfs(self, v, temp, visited): 17 | visited[v] = True 18 | temp.append(v) 19 | 20 | for i in self.graph[v]: 21 | if visited[i] == False: 22 | temp = self.dfs(i, temp, visited) 23 | 24 | return temp 25 | 26 | 27 | def connected_components(self): 28 | visited = [False] * self.vertices 29 | cc = [] 30 | 31 | for i in range(self.vertices): 32 | if visited[i] == False: 33 | temp = [] 34 | cc.append(self.dfs(i, temp, visited)) 35 | 36 | return cc 37 | 38 | 39 | g = Graph(5) 40 | g.add_edge(0, 1) 41 | g.add_edge(1, 2) 42 | g.add_edge(2, 0) 43 | g.add_edge(0, 3) 44 | g.add_edge(3, 4) 45 | 46 | print(g.connected_components()) -------------------------------------------------------------------------------- /data_structures/graphs/count_edges.py: -------------------------------------------------------------------------------- 1 | """ 2 | Count the number of edges in an undirected graph 3 | 4 | Use Handshaking Lemma (Note: Handshaking Lemma is only for undirected graph) 5 | 6 | For all v in V, deg(v) = 2|E| 7 | """ 8 | 9 | Time - O(V) 10 | 11 | class Graph: 12 | 13 | def __init__(self, vertices): 14 | self.V = vertices 15 | self.graph = [[] for i in range(vertices)] 16 | 17 | 18 | def add_edge(self, u, v): 19 | self.graph[u].append(v) 20 | self.graph[v].append(u) 21 | 22 | 23 | def count_edges(self): 24 | s = 0 25 | 26 | for i in range(self.V): 27 | s += len(self.graph[i]) 28 | 29 | return s // 2 30 | 31 | 32 | g = Graph(9) 33 | g.add_edge(0, 1 ) 34 | g.add_edge(0, 7 ) 35 | g.add_edge(1, 2 ) 36 | g.add_edge(1, 7 ) 37 | g.add_edge(2, 3 ) 38 | g.add_edge(2, 8 ) 39 | g.add_edge(2, 5 ) 40 | g.add_edge(3, 4 ) 41 | g.add_edge(3, 5 ) 42 | g.add_edge(4, 5 ) 43 | g.add_edge(5, 6 ) 44 | g.add_edge(6, 7 ) 45 | g.add_edge(6, 8 ) 46 | g.add_edge(7, 8 ) 47 | 48 | print(g.count_edges()) -------------------------------------------------------------------------------- /data_structures/graphs/count_paths_between_nodes.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class Graph: 4 | 5 | def __init__(self, vertices): 6 | self.V = vertices 7 | self.graph = defaultdict(list) 8 | 9 | 10 | def add_edge(self, u, v): 11 | self.graph[u].append(v) 12 | 13 | 14 | def count_paths_util(self, u, d, visited, path_count): 15 | visited[u] = True 16 | 17 | if u == d: 18 | path_count[0] += 1 19 | 20 | else: 21 | i = 0 22 | for i in range(len(self.graph[u])): 23 | if not visited[self.graph[u][i]]: 24 | self.count_paths_util(self.graph[u][i], d, visited, path_count) 25 | 26 | visited[u] = False 27 | 28 | 29 | def count_paths(self, s, d): 30 | visited = [False] * self.V 31 | path_count = [0] 32 | self.count_paths_util(s, d, visited, path_count) 33 | return path_count[0] 34 | 35 | 36 | g = Graph(4) 37 | g.add_edge(0, 1) 38 | g.add_edge(0, 2) 39 | g.add_edge(0, 3) 40 | g.add_edge(2, 0) 41 | g.add_edge(2, 1) 42 | g.add_edge(1, 3) 43 | 44 | s = 2 45 | d = 3 46 | 47 | print(g.count_paths(s, d)) 48 | -------------------------------------------------------------------------------- /data_structures/graphs/count_sink_nodes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Count the number of sink nodes 3 | 4 | Sink nodes are the nodes without any outgoing edge 5 | 6 | Solution - 7 | vertices contains the total number of edges and graph is an adjacency list. 8 | Simply subtract the vertices and len(graph) 9 | """ 10 | 11 | from collections import defaultdict 12 | 13 | class Graph: 14 | 15 | def __init__(self, vertices): 16 | self.graph = defaultdict(list) 17 | self.vertices = vertices 18 | 19 | 20 | def add_edge(self, u, v): 21 | self.graph[u].append(v) 22 | 23 | 24 | def count_sink_nodes(self): 25 | return self.vertices - len(self.graph) 26 | 27 | 28 | g = Graph(3) 29 | g.add_edge(0, 1) 30 | g.add_edge(0, 2) 31 | print(g.count_sink_nodes()) -------------------------------------------------------------------------------- /data_structures/graphs/count_trees.py: -------------------------------------------------------------------------------- 1 | """ 2 | Count the number of trees in a forest 3 | 4 | A forest is a collection of trees. Do a DFS. If any vertex if not reachable 5 | from any other, then it means it is not a part of that subgraph (tree). Hence 6 | it is a different tree, so increment the count 7 | """ 8 | 9 | from collections import defaultdict 10 | 11 | class Graph: 12 | 13 | def __init__(self, vertices): 14 | self.vertices = vertices 15 | self.graph = defaultdict(list) 16 | 17 | 18 | def add_edge(self, u, v): 19 | self.graph[v].append(u) 20 | self.graph[u].append(v) 21 | 22 | 23 | def count_trees(self): 24 | visited = [False] * self.vertices 25 | count = 0 26 | 27 | for s in range(self.vertices): 28 | if not visited[s]: 29 | visited[s] = True 30 | stack = [] 31 | stack.append(s) 32 | count += 1 33 | 34 | while stack: 35 | print(stack) 36 | s = stack.pop() 37 | 38 | for i in self.graph[s]: 39 | if not visited[i]: 40 | visited[i] = True 41 | stack.append(i) 42 | 43 | return count 44 | 45 | 46 | g = Graph(5) 47 | g.add_edge(0, 1) 48 | g.add_edge(0, 2) 49 | g.add_edge(3, 4) 50 | 51 | print('Count of trees - ', g.count_trees()) -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_directed_graph.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.V = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | 14 | 15 | def is_cyclic_util(self, v, visited, rec_stack): 16 | visited[v] = True 17 | rec_stack[v] = True 18 | 19 | for neighbour in self.graph[v]: 20 | if visited[neighbour] == False: 21 | if self.is_cyclic_util(neighbour, visited, rec_stack): 22 | return True 23 | 24 | elif rec_stack[neighbour] == True: 25 | return True 26 | 27 | rec_stack[v] = False 28 | return False 29 | 30 | 31 | def is_cyclic(self): 32 | visited = [False] * self.V 33 | rec_stack = [False] * self.V 34 | 35 | for i in range(self.V): 36 | if visited[i] == False: 37 | if self.is_cyclic_util(node, visited, rec_stack) == True: 38 | return True 39 | 40 | return False 41 | -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_directed_graph_iterative.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | 7 | def __init__(self, vertices): 8 | self.graph = defaultdict(list) 9 | self.vertices = vertices 10 | 11 | 12 | def add_edge(self, u, v): 13 | self.graph[u].append(v) 14 | 15 | 16 | def dfs(self): 17 | visited = [False] * self.vertices 18 | recursion_stack = [False] * self.vertices 19 | stack = [] 20 | 21 | for v in range(self.vertices): 22 | if visited[v] == False: 23 | visited[v] = True 24 | recursion_stack[v] = True 25 | 26 | stack.append(v) 27 | 28 | while stack: 29 | s = stack.pop() 30 | 31 | recursion_stack[s] = True 32 | 33 | for i in self.graph[s]: 34 | if visited[i] == False: 35 | stack.append(i) 36 | visited[i] = True 37 | recursion_stack[i] = True 38 | elif recursion_stack[i] == True: 39 | return "Contains Cycle" 40 | 41 | recursion_stack[v] = False 42 | 43 | return "No cycle" 44 | 45 | 46 | g = Graph(4) 47 | g.add_edge(0, 1) 48 | g.add_edge(0, 2) 49 | g.add_edge(1, 2) 50 | g.add_edge(2, 0) 51 | g.add_edge(2, 3) 52 | g.add_edge(3, 3) 53 | print(g.dfs()) -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_directed_graph_using_colors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Using three colors - white, gray and black 3 | White - vertices that are not processed (inital state of all vertices) 4 | Gray - vertices that are in DFS 5 | Black - fully traversed vertices (i.e its progenies are also done) 6 | 7 | If while traversing any adjacent node is colored Gray, that means cycle exists 8 | """ 9 | 10 | from collections import defaultdict 11 | 12 | class Graph: 13 | 14 | def __init__(self, vertices): 15 | self.vertices = vertices 16 | self.graph = defaultdict(list) 17 | 18 | 19 | def add_edge(self, u, v): 20 | self.graph[u].append(v) 21 | 22 | 23 | def detect_cycle(self): 24 | color = ['white'] * self.vertices 25 | visited = [False] * self.vertices 26 | 27 | stack = [] 28 | 29 | for v in range(self.vertices): 30 | if color[v] == 'white': 31 | color[v] = 'gray' 32 | 33 | stack.append(v) 34 | 35 | while stack: 36 | s = stack.pop() 37 | 38 | for i in self.graph[s]: 39 | if color[i] == 'white': 40 | stack.append(i) 41 | color[i] = 'gray' 42 | elif color[i] == 'gray': 43 | return "Cycle detected" 44 | 45 | color[v] = 'black' 46 | 47 | return "Cycle not present" 48 | 49 | 50 | g = Graph(4) 51 | g.add_edge(0, 1) 52 | g.add_edge(0, 2) 53 | g.add_edge(1, 2) 54 | g.add_edge(2, 0) 55 | g.add_edge(2, 3) 56 | g.add_edge(3, 3) 57 | # g.add_edge(0, 1) 58 | # g.add_edge(0, 2) 59 | # g.add_edge(1, 3) 60 | print(g.detect_cycle()) -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_directed_graph_using_colors_recursive.py: -------------------------------------------------------------------------------- 1 | """ 2 | Using three colors - white, gray and black 3 | White - vertices that are not processed (inital state of all vertices) 4 | Gray - vertices that are in DFS 5 | Black - fully traversed vertices (i.e its progenies are also done) 6 | 7 | If while traversing any adjacent node is colored Gray, that means cycle exists 8 | """ 9 | 10 | from collections import defaultdict 11 | 12 | class Graph: 13 | 14 | 15 | def __init__(self, vertices): 16 | self.graph = defaultdict(list) 17 | self.vertices = vertices 18 | 19 | 20 | def add_edge(self, u, v): 21 | self.graph[u].append(v) 22 | 23 | 24 | def dfs(self, vertex, colors): 25 | colors[vertex] = 'Gray' 26 | 27 | for v in self.graph[vertex]: 28 | 29 | if colors[v] == 'Gray': 30 | return True 31 | 32 | elif colors[v] == 'White' and self.dfs(v, colors) == True: 33 | return True 34 | 35 | colors[vertex] = 'Black' 36 | return False 37 | 38 | 39 | def is_cyclic(self): 40 | colors = ['White'] * self.vertices 41 | 42 | for vertex in self.graph.keys(): 43 | if colors[vertex] == 'White': 44 | if self.dfs(vertex, colors) == True: 45 | return True 46 | 47 | return False 48 | 49 | 50 | g = Graph(4) 51 | g.add_edge(0, 1) 52 | g.add_edge(0, 2) 53 | g.add_edge(1, 2) 54 | g.add_edge(2, 0) 55 | g.add_edge(2, 3) 56 | g.add_edge(3, 3) 57 | 58 | print(g.is_cyclic()) -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_undirected_graph.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.V = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | 14 | 15 | def is_cyclic_util(self, v, visited, parent): 16 | visited[v] = True 17 | 18 | for i in self.graph[v]: 19 | if visited[i] == False: 20 | if self.is_cyclic_util(i, visited, v): 21 | return True 22 | 23 | elif parent != i: 24 | return True 25 | 26 | return False 27 | 28 | 29 | def is_cyclic(self): 30 | visited = [False] * (self.V) 31 | 32 | for i in range(self.V): 33 | if visited[i] == False: 34 | if self.is_cyclic_util(i, visited, -1): 35 | return True 36 | 37 | return False 38 | -------------------------------------------------------------------------------- /data_structures/graphs/cycle_in_undirected_graph_union_find.py: -------------------------------------------------------------------------------- 1 | """ 2 | This method cannot be used in Directed graph because of the direction of the edge. 3 | In a union of set containing element A and B, you cannot specify whether A is going to B 4 | or vice versa 5 | """ 6 | 7 | from collections import defaultdict 8 | 9 | 10 | class Graph: 11 | 12 | 13 | def __init__(self, vertices): 14 | self.vertices = vertices 15 | self.graph = defaultdict(list) 16 | 17 | 18 | def add_edge(self, u, v): 19 | self.graph[u].append(v) 20 | 21 | 22 | def union(self, parent, x, y): 23 | parent[x] = y 24 | 25 | 26 | def find_parent(self, parent, i): 27 | if parent[i] == -1: 28 | return i 29 | else: 30 | return self.find_parent(parent, parent[i]) 31 | 32 | 33 | def check_cyclic(self): 34 | parent = [-1] * self.vertices 35 | 36 | for i in self.graph.keys(): 37 | for j in self.graph[i]: 38 | x = self.find_parent(parent, i) 39 | y = self.find_parent(parent, j) 40 | 41 | if x == y: 42 | return "Cyclic" 43 | 44 | self.union(parent, x, y) 45 | 46 | return "Not cyclic" 47 | 48 | 49 | g = Graph(4) 50 | g.add_edge(0, 1) 51 | g.add_edge(0, 2) 52 | g.add_edge(1, 2) 53 | g.add_edge(2, 0) 54 | g.add_edge(2, 3) 55 | g.add_edge(3, 3) 56 | # g.add_edge(0, 1) 57 | # g.add_edge(0, 2) 58 | # g.add_edge(1, 3) 59 | print(g.check_cyclic()) -------------------------------------------------------------------------------- /data_structures/graphs/dfs.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class Graph: 4 | 5 | def __init__(self): 6 | self.graph = defaultdict(list) 7 | 8 | 9 | def add_edge(self, u, v): 10 | self.graph[u].append(v) 11 | self.graph[v].append(u) 12 | 13 | 14 | def dfs_util(self, v, visited): 15 | visited[v] = True 16 | print(v, end=' ') 17 | 18 | for i in self.graph[v]: 19 | if visited[i] == False: 20 | self.dfs_util(i, visited) 21 | 22 | 23 | def dfs(self, v): 24 | visited = [False] * (len(self.graph)) 25 | self.dfs_util(v, visited) 26 | 27 | 28 | g = Graph() 29 | g.add_edge(0, 1) 30 | g.add_edge(0, 2) 31 | g.add_edge(0, 3) 32 | g.add_edge(1, 4) 33 | g.add_edge(2, 5) 34 | g.add_edge(3, 6) 35 | print(g.dfs(0)) 36 | -------------------------------------------------------------------------------- /data_structures/graphs/dijsktra_algorithm.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhupant/python-ds/35d3556a992ceccc5b925afa892fae3ba01a0e81/data_structures/graphs/dijsktra_algorithm.py -------------------------------------------------------------------------------- /data_structures/graphs/iterative_dfs.py: -------------------------------------------------------------------------------- 1 | # The code is almost same to BFS, the only difference being queue 2 | # is replaced by stack 3 | 4 | from collections import defaultdict 5 | 6 | class Graph: 7 | 8 | def __init__(self, vertices): 9 | self.vertices = vertices 10 | self.graph = defaultdict(list) 11 | 12 | 13 | def add_edge(self, u, v): 14 | self.graph[u].append(v) 15 | 16 | 17 | def dfs(self): 18 | visited = [False] * self.vertices 19 | stack = [] 20 | 21 | for s in range(self.vertices): 22 | if visited[s] == False: 23 | visited[s] = True 24 | 25 | stack.append(s) 26 | 27 | while stack: 28 | s = stack.pop() 29 | print(s, end=' ') 30 | 31 | for i in self.graph[s]: 32 | if visited[i] == False: 33 | stack.append(i) 34 | visited[i] = True 35 | 36 | 37 | # g = Graph(4) 38 | # g.add_edge(0, 1) 39 | # g.add_edge(0, 2) 40 | # g.add_edge(1, 2) 41 | # g.add_edge(2, 0) 42 | # g.add_edge(2, 3) 43 | # g.add_edge(3, 3) 44 | g = Graph(5) 45 | g.add_edge(0, 1) 46 | g.add_edge(0, 2) 47 | g.add_edge(3, 4) 48 | 49 | g.dfs() 50 | -------------------------------------------------------------------------------- /data_structures/graphs/level_of_nodes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Level is the distance of a node from a source node. This concept can be used to find 3 | the distance between 2 nodes in an unweighted graph as well. A simple BFS traversal 4 | between these 2 nodes will give the level and level will always be the shortest distance 5 | between nodes. 6 | """ 7 | 8 | from collections import defaultdict 9 | 10 | class Graph: 11 | 12 | def __init__(self, vertices): 13 | self.vertices = vertices 14 | self.graph = defaultdict(list) 15 | 16 | 17 | def add_edge(self, u, v): 18 | self.graph[u].append(v) 19 | self.graph[v].append(u) 20 | 21 | 22 | def print_levels(self, s): 23 | levels = [None] * self.vertices 24 | levels[s] = 0 25 | queue = [] 26 | queue.append(s) 27 | 28 | while queue: 29 | s = queue.pop(0) 30 | 31 | for i in self.graph[s]: 32 | if levels[i] == None: 33 | levels[i] = levels[s] + 1 34 | queue.append(i) 35 | 36 | print('Node \t Level') 37 | for node, level in enumerate(levels): 38 | print(f'{node} \t {level}') 39 | 40 | g = Graph(8) 41 | g.add_edge(0, 1) 42 | g.add_edge(0, 2) 43 | g.add_edge(1, 3) 44 | g.add_edge(1, 4) 45 | g.add_edge(1, 5) 46 | g.add_edge(2, 5) 47 | g.add_edge(2, 6) 48 | g.add_edge(6, 7) 49 | g.print_levels(0) -------------------------------------------------------------------------------- /data_structures/graphs/longest_path.py: -------------------------------------------------------------------------------- 1 | # https://www.geeksforgeeks.org/longest-path-between-any-pair-of-vertices/ 2 | -------------------------------------------------------------------------------- /data_structures/graphs/min_edges_between_two_vertices.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.vertices = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | self.graph[v].append(u) 14 | 15 | 16 | def find_min_edges(self, u, v): 17 | visited = [False] * self.vertices 18 | distance = [0] * self.vertices 19 | queue = [] 20 | 21 | queue.append(u) 22 | visited[u] = True 23 | 24 | while queue: 25 | s = queue.pop(0) 26 | 27 | for i in self.graph[s]: 28 | if visited[i] == False: 29 | distance[i] = distance[s] + 1 30 | queue.append(i) 31 | visited[i] = True 32 | 33 | return distance[v] 34 | 35 | 36 | g = Graph(9) 37 | 38 | g.add_edge(0, 1) 39 | g.add_edge(0, 7) 40 | g.add_edge(1, 7) 41 | g.add_edge(1, 2) 42 | g.add_edge(2, 3) 43 | g.add_edge(2, 5) 44 | g.add_edge(2, 8) 45 | g.add_edge(3, 4) 46 | g.add_edge(3, 5) 47 | g.add_edge(4, 5) 48 | g.add_edge(5, 6) 49 | g.add_edge(6, 7) 50 | g.add_edge(7, 8) 51 | 52 | print(g.find_min_edges(0, 5)) 53 | -------------------------------------------------------------------------------- /data_structures/graphs/min_nodes_to_reach_all_nodes.py: -------------------------------------------------------------------------------- 1 | """ 2 | We are given a DAG. Find the smallest set of vertices from which all 3 | nodes in the graph are reachable. It's guaranteed that a unique solution exists. 4 | 5 | Solution - 6 | The crux of the question is that - 7 | 1. If any node has an indegree > 1 (i.e it is reachable from any other node), then it means 8 | in a connected graph it will be possible to reach here if its parent node also has an indegree 9 | 2. So the min will be the set of nodes with indegree = 0 10 | """ 11 | 12 | -------------------------------------------------------------------------------- /data_structures/graphs/min_number_of_operations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimum number of operations required to transform a number x to y 3 | Valid operations - multiplication by 2, subtraction by 1 4 | Ex - x = 4, y = 7 5 | 1. 4 * 2 = 8 6 | 2. 8 - 1 = 7 7 | Answer = 2 8 | """ 9 | 10 | 11 | from collections import defaultdict 12 | 13 | 14 | class Node: 15 | 16 | 17 | def __init__(self, value, level): 18 | self.value = value 19 | self.level = level 20 | 21 | 22 | def min_steps(x, y): 23 | node_x = Node(x, 0) 24 | 25 | visited = [] 26 | queue = [] 27 | queue.append(node_x) 28 | 29 | while queue: 30 | s = queue.pop(0) 31 | 32 | if s.value == y: 33 | return s.level 34 | 35 | visited.append(s.value) 36 | 37 | if s.value * 2 == y or s.value - 1 == y: 38 | return s.level + 1 39 | 40 | # If not visited already, add its children 41 | 42 | if s.value * 2 not in visited: 43 | new_node = Node(s.value * 2, s.level + 1) 44 | queue.append(new_node) 45 | 46 | if s.value - 1 not in visited: 47 | new_node = Node(s.value - 1, s.level + 1) 48 | queue.append(new_node) 49 | 50 | 51 | x = 2 52 | y = 5 53 | 54 | print(min_steps(x, y)) -------------------------------------------------------------------------------- /data_structures/graphs/mother_vertex.py: -------------------------------------------------------------------------------- 1 | """ 2 | A mother vertex is a vertex such that all other vertices 3 | can be reached by a path from this vertex 4 | 5 | Reference - https://www.geeksforgeeks.org/find-a-mother-vertex-in-a-graph/ 6 | 7 | Time complexity - 2 * O(V + E) = O(V + E) 8 | """ 9 | 10 | from collections import defaultdict 11 | 12 | class Graph: 13 | 14 | def __init__(self, vertices): 15 | self.V = vertices 16 | self.graph = defaultdict(list) 17 | 18 | 19 | def dfs_util(self, v, visited): 20 | visited[v] = True 21 | 22 | for i in self.graph[v]: 23 | if visited[i] == False: 24 | self.dfs_util(i, visited) 25 | 26 | 27 | def add_edge(self, v, w): 28 | self.graph[v].append(w) 29 | 30 | 31 | def find_mother(self): 32 | visited = [False] * self.V 33 | 34 | v = 0 35 | 36 | for i in range(self.V): 37 | if visited[i] == False: 38 | self.dfs_util(i, visited) 39 | v = i 40 | 41 | # Now check if v is one of the mother vertex 42 | # Reset all the values of visited and do DFS beginning from v 43 | # to check if all the vertices are reachable from it or not 44 | 45 | visited = [False] * self.V 46 | self.dfs_util(v, visited) 47 | 48 | if any(i == False for i in visited): 49 | return -1 50 | 51 | else: 52 | return v 53 | 54 | 55 | g = Graph(7) 56 | g.add_edge(0, 1) 57 | g.add_edge(0, 2) 58 | g.add_edge(1, 3) 59 | g.add_edge(4, 1) 60 | g.add_edge(6, 4) 61 | g.add_edge(5, 6) 62 | g.add_edge(5, 2) 63 | g.add_edge(6, 0) 64 | 65 | print("Mother vertex is - ", g.find_mother()) 66 | -------------------------------------------------------------------------------- /data_structures/graphs/not_reachable_nodes.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class Graph: 4 | 5 | def __init__(self, vertices): 6 | self.V = vertices 7 | self.graph = defaultdict(list) 8 | 9 | 10 | def add_edge(self, u, v): 11 | self.graph[u].append(v) 12 | self.graph[v].append(u) 13 | 14 | 15 | def dfs_util(self, v, visited): 16 | visited[v] = True 17 | 18 | for i in self.graph[v]: 19 | if visited[i] == False: 20 | self.dfs_util(i, visited) 21 | 22 | 23 | def count_nodes(self, v): 24 | visited = [False] * self.V 25 | 26 | self.dfs_util(v, visited) 27 | 28 | count = 0 29 | 30 | for i in range(self.V): 31 | if visited[i] == False: 32 | count += 1 33 | 34 | return count 35 | 36 | g = Graph(8) 37 | g.add_edge(0, 1) 38 | g.add_edge(0, 2) 39 | g.add_edge(1, 2) 40 | g.add_edge(3, 4) 41 | g.add_edge(4, 5) 42 | g.add_edge(6, 7) 43 | 44 | print(g.count_nodes(0)) 45 | -------------------------------------------------------------------------------- /data_structures/graphs/number_of_triangles.py: -------------------------------------------------------------------------------- 1 | # Reference - https://www.geeksforgeeks.org/number-of-triangles-in-a-undirected-graph/ 2 | # Reference - https://www.geeksforgeeks.org/number-of-triangles-in-directed-and-undirected-graphs/ 3 | 4 | # In case of undirected graph - trace(A^3)/6 5 | # For undirected graph, just check if an edge exists among all the three vertices 6 | 7 | class Graph: 8 | 9 | def __init__(self, vertices, is_directed): 10 | self.V = vertices 11 | self.graph = [[] for i in range(vertices)] 12 | self.is_directed = is_directed 13 | 14 | 15 | def add_edge(self, u, v): 16 | self.graph[u].append(v) 17 | self.graph[v].append(u) 18 | 19 | 20 | def count_triangles(self): 21 | nodes = len(self.graph) 22 | count = 0 23 | 24 | for i in range(nodes): 25 | for j in range(nodes): 26 | for k in range(nodes): 27 | if i != j and j != k and k != i and self.graph[i][j] and self.graph[j][k] and self.graph[k][i]: 28 | count += 1 29 | 30 | return count // 3 if is_directed else count // 6 31 | -------------------------------------------------------------------------------- /data_structures/graphs/print_all_paths_between_nodes.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class Graph: 4 | 5 | 6 | def __init__(self, vertices): 7 | self.graph = defaultdict(list) 8 | self.vertices = vertices 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | 14 | 15 | def print_path(self, s, d, visited, path): 16 | visited[s] = True 17 | path.append(s) 18 | 19 | if s == d: 20 | print(path) 21 | 22 | else: 23 | for i in self.graph[s]: 24 | if visited[i] == False: 25 | self.print_path(i, d, visited, path) 26 | 27 | # If path from this node does not lead to the destination, remove it 28 | # from the path stack and mark it as not visited 29 | path.pop() 30 | visited[s] = False 31 | 32 | 33 | def print_all_paths(self, s, d): 34 | visited = [False] * self.vertices 35 | path = [] 36 | self.print_path(s, d, visited, path) 37 | 38 | 39 | g = Graph(4) 40 | 41 | g.add_edge(0, 3) 42 | g.add_edge(0, 1) 43 | g.add_edge(0, 2) 44 | g.add_edge(1, 3) 45 | g.add_edge(2, 0) 46 | g.add_edge(2, 1) 47 | 48 | s = 2 49 | d = 3 50 | 51 | print(f'Paths from {s} to {d} are - ') 52 | g.print_all_paths(s, d) -------------------------------------------------------------------------------- /data_structures/graphs/root_which_gives_min_height.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the node in an undirected graph which gives the minimum height 3 | Reference - https://www.geeksforgeeks.org/roots-tree-gives-minimum-height/ 4 | """ 5 | 6 | from collections import defaultdict 7 | from queue import Queue 8 | 9 | class Graph: 10 | 11 | def __init__(self, vertices): 12 | self.V = vertices 13 | self.graph = defaultdict(list) 14 | self.degree = [0] * vertices 15 | 16 | 17 | def add_edge(self, v, w): 18 | self.graph[v].append(w) 19 | self.graph[w].append(v) 20 | self.degree[v] += 1 21 | self.degree[w] += 1 22 | 23 | 24 | def root_min_height(self): 25 | q = Queue() 26 | 27 | for i in range(self.V): 28 | if self.degree[i] == 1: # To identify leaf nodes 29 | q.put(i) 30 | 31 | # now move inwards from the leaf node 32 | while self.V > 2: 33 | for i in range(q.qsize()): 34 | t = q.get() 35 | self.V -= 1 36 | 37 | # For each neighbour decrease its degree and if it becomes 38 | # leaf, insert into the queue 39 | for j in self.graph[t]: 40 | self.degree[j] -= 1 41 | if self.degree[j] == 1: 42 | q.put(j) 43 | 44 | res = list() 45 | while q.qsize() > 0: 46 | res.append(q.get()) 47 | 48 | 49 | return res 50 | 51 | g = Graph(6) 52 | g.add_edge(0, 3) 53 | g.add_edge(1, 3) 54 | g.add_edge(2, 3) 55 | g.add_edge(4, 3) 56 | g.add_edge(5, 4) 57 | 58 | print(g.root_min_height()) 59 | -------------------------------------------------------------------------------- /data_structures/graphs/topological_sort.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.V = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | 14 | 15 | def topo_sort_util(self, v, visited, stack): 16 | visited[v] = True 17 | 18 | for i in self.graph[v]: 19 | if visited[i] == False: 20 | self.topo_sort_util(i, visited, stack) 21 | 22 | stack.insert(0, v) 23 | 24 | 25 | def topo_sort(self): 26 | visited = [False] * self.V 27 | stack = [] 28 | 29 | for i in range(self.V): 30 | if visited[i] == False: 31 | self.topo_sort_util(i, visited, stack) 32 | 33 | print(stack) 34 | -------------------------------------------------------------------------------- /data_structures/graphs/transpose.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Graph: 5 | 6 | def __init__(self, vertices): 7 | self.V = vertices 8 | self.graph = defaultdict(list) 9 | 10 | 11 | def add_edge(self, u, v): 12 | self.graph[u].append(v) 13 | 14 | 15 | def print_graph(self): 16 | for i in self.graph: 17 | print(i, " --> ", end=" ") 18 | for j in self.graph[i]: 19 | print(j, end=" ") 20 | print() 21 | 22 | 23 | def transpose(self): 24 | g = Graph(self.V) 25 | 26 | for i in self.graph: 27 | for j in self.graph[i]: 28 | g.add_edge(j, i) 29 | 30 | return g 31 | 32 | 33 | g = Graph(5) 34 | g.add_edge(1, 0) 35 | g.add_edge(0, 2) 36 | g.add_edge(2, 1) 37 | g.add_edge(0, 3) 38 | g.add_edge(3, 4) 39 | g.print_graph() 40 | 41 | print('---------') 42 | 43 | t = g.transpose() 44 | t.print_graph() 45 | -------------------------------------------------------------------------------- /data_structures/graphs/tree_stays_bipartite.py: -------------------------------------------------------------------------------- 1 | # All trees are bipartite graphs 2 | # Reference - https://www.geeksforgeeks.org/maximum-number-edges-added-tree-stays-bipartite-graph/ 3 | 4 | # Traverse the tree (dfs or bfs) and color nodes with two colors and count each like 5 | # count_color1 and count_color2. Then the max edges that the graph can have are 6 | # count_color1 * count_color2, and the max edges of a tree are n-1 7 | 8 | # Therefore the answer is = count_color1 * count_color2 - (n-1) 9 | 10 | class Graph: 11 | 12 | def __init__(self, vertices): 13 | self.vertices = vertices 14 | self.graph = [[] for i in range(vertices)] 15 | 16 | 17 | def add_edge(self, u, v): 18 | self.graph[u].append(v) 19 | 20 | 21 | def bfs(self, s): 22 | visited = [False] * self.vertices 23 | color_count = [0, 0] 24 | 25 | colors = [-1] * self.vertices 26 | colors[s] = 1 27 | 28 | queue = [] 29 | 30 | queue.append(s) 31 | visited[s] = True 32 | 33 | while queue: 34 | u = queue.pop(0) 35 | 36 | for v in self.graph[u]: 37 | if colors[v] == -1: # This is a tree. So not visited and not colored is same as there is no cycle 38 | colors[v] = 1 - colors[u] 39 | queue.append(v) 40 | color_count[colors[v]] += 1 41 | visited[v] = True 42 | 43 | # Counting the max number of edges for graph 44 | graph_edges = color_count[0] * color_count[1] 45 | 46 | return graph_edges 47 | 48 | 49 | # Number of tree nodes 50 | n = 5 51 | 52 | g = Graph(5) 53 | g.add_edge(1, 2) 54 | g.add_edge(1, 3) 55 | g.add_edge(2, 4) 56 | g.add_edge(3, 5) 57 | -------------------------------------------------------------------------------- /data_structures/hash/hash_table.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a hash table from scratch. Use chaining for hash collision 3 | """ 4 | 5 | class HashTable: 6 | 7 | 8 | def __init__(self): 9 | self.hash_table = 10 | 11 | 12 | def check_collision(self): 13 | pass 14 | 15 | 16 | def add_to_linked_list(self): 17 | pass 18 | 19 | 20 | def insert(self): 21 | pass 22 | 23 | 24 | def delete(self): 25 | pass 26 | 27 | 28 | def get(self): 29 | pass 30 | -------------------------------------------------------------------------------- /data_structures/heap/heap_using_heapq.py: -------------------------------------------------------------------------------- 1 | """ 2 | Heap in python using heapq library function 3 | 4 | Note: by default, heapq creates a min-heap. To make it a 5 | max-heap, add items after multiplying them by -1 6 | """ 7 | 8 | from heapq import heappop, heappush, heapify 9 | 10 | heap = [] 11 | heapify(heap) 12 | 13 | heappush(heap, 10) 14 | heappush(heap, 11) 15 | heappush(heap, 2) 16 | heappush(heap, 4) 17 | heappush(heap, 14) 18 | heappush(heap, 1) 19 | 20 | print('first element - ', heap[0]) 21 | print('popping min element - ', heappop(heap)) 22 | print('first element - ', heap[0]) 23 | 24 | # Heap prints as an array and can be access using indexes 25 | print(heap) 26 | print(heap[2]) 27 | -------------------------------------------------------------------------------- /data_structures/heap/sum_elements_range.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the sum of elements between k1th and k2th smallest elements 3 | """ 4 | 5 | from heapq import heappush, heapify, heappop 6 | 7 | heap = [20, 8, 22, 4, 12, 10, 14] 8 | k1 = 3 9 | k2 = 6 10 | 11 | heapify(heap) 12 | 13 | # extracting min k1 times 14 | 15 | for i in range(k1): 16 | heappop(heap) 17 | 18 | # now do extract min k2 - (k1 + 1) times 19 | s = 0 20 | 21 | for i in range(k2 - k1 - 1): 22 | s += heappop(heap) 23 | 24 | print(s) 25 | -------------------------------------------------------------------------------- /data_structures/linked_list/cycle_detection.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def detect_cycle(head): 9 | if not head or not head.next: 10 | return False 11 | 12 | slow = head 13 | fast = head.next 14 | 15 | while slow != fast: 16 | if fast == None or fast.next == None: 17 | return False 18 | slow = slow.next 19 | fast = fast.next.next 20 | 21 | return True 22 | 23 | 24 | head = Node(1) 25 | second = Node(2) 26 | third = Node(3) 27 | fourth = Node(4) 28 | 29 | 30 | head.next = second 31 | second.next = third 32 | third.next = fourth 33 | 34 | print(detect_cycle(head)) 35 | -------------------------------------------------------------------------------- /data_structures/linked_list/delete_last_occurrence.py: -------------------------------------------------------------------------------- 1 | """ 2 | Delete last occurence of a number in linked list. 3 | """ 4 | 5 | class Node(): 6 | 7 | def __init__(self, val): 8 | self.val = val 9 | self.next = None 10 | 11 | def delete_last_occurrence(head, val): 12 | if not head: 13 | return None 14 | 15 | curr = head 16 | prev = None 17 | final_prev = None 18 | final_occ = None 19 | 20 | while curr != None: 21 | if curr.val == val: 22 | final_prev = prev 23 | final_occ = curr 24 | 25 | prev = curr 26 | curr = curr.next 27 | 28 | 29 | if final_occ: 30 | # special case that checks for a 1 node list that equals the val 31 | if final_prev: 32 | final_prev.next = final_occ.next 33 | else: 34 | head = None 35 | 36 | return head -------------------------------------------------------------------------------- /data_structures/linked_list/index.md: -------------------------------------------------------------------------------- 1 | # Index of linked list 2 | 3 | * [Remove](remove.py) 4 | * [Pair Swap](pair_swap.py) 5 | * [Reverse](reverse.py) 6 | * [Linked List](linked_list.py) 7 | * [Cycle Detection](cycle_detection.py) 8 | * [Remove nth Node from End](remove_nth_node_from_end.py) 9 | * [Odd-Even Arrangement](odd_even_arrangement.py) 10 | * [Merge Linked List](merge_linked_list.py) 11 | * [Remove Duplicates](remove_duplicates.py) 12 | * [Delete Last Occurrence](delete_last_occurrence.py) 13 | -------------------------------------------------------------------------------- /data_structures/linked_list/merge_linked_list.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def merge(l1, l2): 9 | if not l1 and not l2: 10 | return 11 | elif not l2: 12 | return l1 13 | elif not l1: 14 | return l2 15 | 16 | if (l1.val < l2.val): 17 | l1.next = merge(l1.next, l2) 18 | return l1 19 | l2.next = merge(l1, l2.next) 20 | return l2 21 | 22 | head1 = Node(1) 23 | head1.next = Node(3) 24 | 25 | head2 = Node(2) 26 | head2.next = Node(4) 27 | 28 | head_merged = merge(head1,head2) 29 | 30 | ptr = head_merged 31 | while ptr: 32 | print(ptr.val) 33 | ptr = ptr.next 34 | 35 | -------------------------------------------------------------------------------- /data_structures/linked_list/merge_list_of_linked_lists.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, x): 4 | self.val = x 5 | self.next = None 6 | 7 | 8 | def merge_two_lists(l1, l2): 9 | if not l1 and not l2: 10 | return 11 | elif not l2: 12 | return l1 13 | elif not l1: 14 | return l2 15 | 16 | if (l1.val < l2.val): 17 | l1.next = merge_two_lists(l1.next, l2) 18 | return l1 19 | l2.next = merge_two_lists(l1, l2.next) 20 | return l2 21 | 22 | 23 | def merge_k_lists(lists): 24 | length = len(lists) 25 | if length == 0: 26 | return; 27 | elif length == 1: 28 | return lists[0] 29 | elif length == 2: 30 | return merge_two_lists(lists[0], lists[1]) 31 | 32 | mid = length // 2 33 | 34 | left_half = lists[:mid] 35 | right_half = lists[mid:] 36 | 37 | return merge_two_lists(merge_k_lists(left_half), merge_k_lists(right_half)) 38 | -------------------------------------------------------------------------------- /data_structures/linked_list/odd_even_arrangement.py: -------------------------------------------------------------------------------- 1 | # Number at odd places should come first 2 | # followed by even places numbers 3 | 4 | class Node(): 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.next = None 9 | 10 | 11 | def arrange(head): 12 | if not head: 13 | return None 14 | odd = head 15 | even = head.next 16 | even_head = even 17 | while even and even.next: 18 | odd.next = even.next 19 | odd = odd.next 20 | even.next = odd.next 21 | even = even.next 22 | odd.next = even_head 23 | return head 24 | 25 | -------------------------------------------------------------------------------- /data_structures/linked_list/pair_swap.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def pair_swap(head): 9 | if head == None or head.next == None: 10 | return head 11 | root = head.next 12 | curr = head 13 | prev = Node(0) 14 | 15 | while curr.next: 16 | curr.next = curr.next.next 17 | curr.next.next = curr 18 | prev.next = curr.next.next 19 | prev = curr.next 20 | curr = curr.next.next 21 | return root 22 | 23 | 24 | -------------------------------------------------------------------------------- /data_structures/linked_list/remove.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def remove(head, val): 9 | while head and head.val == val: 10 | head = head.next 11 | if not head: 12 | return None 13 | curr = head 14 | while curr.next: 15 | if curr.next.val == val: 16 | curr.next = curr.next.next 17 | else: 18 | curr = curr.next 19 | return head 20 | 21 | -------------------------------------------------------------------------------- /data_structures/linked_list/remove_duplicates.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | def __str__(self): 8 | return "{}".format(self.val) 9 | 10 | 11 | def remove_duplicates(head): 12 | dummy = Node(0) 13 | dummy.next = head 14 | prev = dummy 15 | while head and head.next: 16 | if head.val == head.next.val: 17 | temp = head 18 | while temp.next and temp.next.val == temp.val: 19 | temp = temp.next 20 | prev.next = temp.next 21 | head = temp.next 22 | else: 23 | head = head.next 24 | prev = prev.next 25 | return dummy.next 26 | -------------------------------------------------------------------------------- /data_structures/linked_list/remove_nth_node_from_end.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def remove(head, n): 9 | res = head 10 | slow = head 11 | fast = head 12 | 13 | for i in range(n+1): 14 | fast = fast.next 15 | while fast: 16 | fast = fast.next 17 | slow = slow.next 18 | slow.next = slow.next.next 19 | return res 20 | 21 | def print_list(head): 22 | curr = head 23 | while curr: 24 | print(curr.val, end=" ") 25 | curr = curr.next 26 | 27 | head = Node(1) 28 | head.next = Node(2) 29 | head.next.next = Node(3) 30 | head.next.next.next = Node(4) 31 | head.next.next.next.next = Node(5) 32 | print_list(head) 33 | remove(head, 2) 34 | print_list(head) 35 | 36 | 37 | -------------------------------------------------------------------------------- /data_structures/linked_list/reverse.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | def reverse(head): 8 | if not head: 9 | return None 10 | prev = None 11 | curr = head 12 | while curr: 13 | next = curr.next 14 | curr.next = prev 15 | prev = curr 16 | curr = next 17 | return prev 18 | 19 | head = Node(1) 20 | head.next = Node(2) 21 | head.next.next = Node(3) 22 | 23 | curr = head 24 | while curr: 25 | print(curr.val) 26 | curr = curr.next 27 | 28 | new_head = reverse(head) 29 | curr = new_head 30 | while curr: 31 | print(curr.val) 32 | curr = curr.next 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /data_structures/linked_list/sum_of_two_numbers.py: -------------------------------------------------------------------------------- 1 | class Node(): 2 | 3 | def __init__(self, val): 4 | self.val = val 5 | self.next = None 6 | 7 | 8 | def push(head, data): 9 | node = Node(data) 10 | curr = head 11 | while curr.next: 12 | curr = curr.next 13 | curr.next = node 14 | 15 | 16 | def print_list(head): 17 | curr = head 18 | while curr: 19 | print(curr.val, end=' ') 20 | curr = curr.next 21 | 22 | 23 | def sum_numbers(head1, head2): 24 | carry = 0 25 | prev = None 26 | res = None 27 | while head1 is not None or head2 is not None: 28 | data1 = 0 if head1 is None else head1.val 29 | data2 = 0 if head2 is None else head2.val 30 | s = carry + data1 + data2 31 | carry = 1 if s >= 10 else 0 32 | s = s if s < 10 else s % 10 33 | 34 | temp = Node(s) 35 | 36 | # if this is the first node, make it head 37 | if res is None: 38 | res = temp 39 | else: 40 | prev.next = temp 41 | 42 | prev = temp 43 | 44 | # move pointers ahead 45 | if head1 is not None: 46 | head1 = head1.next 47 | if head2 is not None: 48 | head2 = head2.next 49 | 50 | if carry > 0: 51 | temp.next = Node(carry) 52 | 53 | return res 54 | 55 | 56 | 57 | 58 | head1 = Node(1) 59 | push(head1, 2) 60 | push(head1, 3) 61 | push(head1, 4) 62 | print_list(head1) 63 | print() 64 | head2 = Node(5) 65 | push(head2, 2) 66 | push(head2, 1) 67 | push(head2, 3) 68 | print_list(head2) 69 | print() 70 | summmed_list = sum_numbers(head1, head2) 71 | print_list(summmed_list) 72 | -------------------------------------------------------------------------------- /data_structures/matrix/find_in_row_and_column_wise_sorted_matrix.py: -------------------------------------------------------------------------------- 1 | def search(mat, n, x): 2 | 3 | i = 0 4 | 5 | j = n - 1 6 | 7 | while i < n and j >=0: 8 | if mat[i][j] == x: 9 | print("Found at - ", i, j) 10 | return 1 11 | if mat[i][j] > x: 12 | j -= 1 13 | else: 14 | i += 1 15 | 16 | print("Element not found") 17 | return 0 18 | -------------------------------------------------------------------------------- /data_structures/matrix/find_pair.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhupant/python-ds/35d3556a992ceccc5b925afa892fae3ba01a0e81/data_structures/matrix/find_pair.py -------------------------------------------------------------------------------- /data_structures/matrix/flood_fill_algorithm.py: -------------------------------------------------------------------------------- 1 | # Reference - https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/ 2 | 3 | # Dimensions of paint screen 4 | M = 8 5 | N = 8 6 | 7 | def flood_fill_util(screen, x, y, prev_color, new_color): 8 | if x < 0 or x >= M or y < 0 or y >= N or screen[x][y] != prev_color or screen[x][y] == new_color: 9 | return 10 | 11 | screen[x][y] = new_color 12 | flood_fill_util(screen, x + 1, y, prev_color, new_color) 13 | flood_fill_util(screen, x - 1, y, prev_color, new_color) 14 | flood_fill_util(screen, x, y + 1, prev_color, new_color) 15 | flood_fill_util(screen, x, y - 1, prev_color, new_color) 16 | 17 | 18 | def flood_fill(screen, x, y, new_color): 19 | prev_color = screen[x][y] # Finding the previous color on x and y 20 | flood_fill_util(screen, x, y, prev_color, new_color) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /data_structures/queue/queue.py: -------------------------------------------------------------------------------- 1 | class Queue: 2 | """ 3 | Queue is an abstract data structure, somewhat similar to Stacks. 4 | Unlike stacks, a queue is open at both its ends. One end is always used to insert data (enqueue) 5 | and the other is used to remove data (dequeue). 6 | Queue follows First-In-First-Out methodology, i.e., the data item stored first will be accessed first. 7 | 8 | """ 9 | 10 | def __init__(self): 11 | self.entries = [] 12 | self.length = 0 13 | self.front = 0 14 | 15 | def put(self, item): 16 | self.entries.append(item) 17 | self.length += 1 18 | 19 | def get(self): 20 | if self.length <= 0: 21 | return 22 | self.length -= 1 23 | de_queued = self.entries[self.front] 24 | self.entries = self.entries[1:] 25 | return de_queued 26 | 27 | def rotate(self, rotation): 28 | for i in range(rotation): 29 | self.put(self.get()) 30 | 31 | def size(self): 32 | return self.length 33 | -------------------------------------------------------------------------------- /data_structures/segment_tree/index.md: -------------------------------------------------------------------------------- 1 | # Index of segment_tree 2 | 3 | * min_in_range_queries.py 4 | -------------------------------------------------------------------------------- /data_structures/segment_tree/seg_tree_max.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class MaxSegTree(): 4 | 5 | def __init__(self, n, arr = None): 6 | self._t = [-sys.maxsize - 1] * (4 * n) 7 | self._n = n 8 | if arr: self._build(arr, 1 , 0 , n - 1) 9 | 10 | def _build(self, a, v, tl, tr): 11 | if tl == tr: 12 | self._t[v] = a[tl] 13 | else: 14 | tm = (tl + tr) // 2 15 | self._build(a, v*2, tl, tm) 16 | self._build(a, v*2+1, tm+1, tr) 17 | self._t[v] = max(self._t[v*2] ,self._t[v*2+1]) 18 | 19 | def _max_util(self, v, tl, tr, l, r): 20 | if l > r: return -sys.maxsize - 1 21 | 22 | if l == tl and r == tr : return self._t[v] 23 | 24 | tm = (tl + tr) // 2 25 | return max(self._max_util(v*2, tl, tm, l, min(r, tm)) ,self._max_util(v*2+1, tm+1, tr, max(l, tm+1), r)) 26 | 27 | def _update_util(self, v, tl, tr, pos, new_val): 28 | if tl == tr: 29 | self._t[v] = new_val 30 | else: 31 | tm = (tl + tr) // 2 32 | if pos <= tm: self._update_util(v*2, tl, tm, pos, new_val) 33 | else: self._update_util(v*2+1, tm+1, tr, pos, new_val) 34 | self._t[v] = max(self._t[v*2] , self._t[v*2+1]) 35 | 36 | def max_element(self, l, r): 37 | return self._max_util(1 , 0 , self._n - 1 , l , r) 38 | 39 | def update(self, pos, new_val): 40 | self._update_util(1 , 0 , self._n - 1 , pos , new_val) 41 | 42 | def add(self, pos, change): 43 | value = self.max_element(pos , pos) 44 | self._update_util(1 , 0 , self._n - 1 , pos , value + change) -------------------------------------------------------------------------------- /data_structures/segment_tree/seg_tree_sum.py: -------------------------------------------------------------------------------- 1 | class SumSegTree(): 2 | 3 | def __init__(self, n, arr = None): 4 | self._t = [0] * (4 * n) 5 | self._n = n 6 | if arr: self._build(arr, 1 , 0 , n - 1) 7 | 8 | def _build(self, a, v, tl, tr): 9 | if tl == tr: 10 | self._t[v] = a[tl] 11 | else: 12 | tm = (tl + tr) // 2 13 | self._build(a, v*2, tl, tm) 14 | self._build(a, v*2+1, tm+1, tr) 15 | self._t[v] = self._t[v*2] + self._t[v*2+1] 16 | 17 | def _sum_util(self, v, tl, tr, l, r): 18 | if l > r: return 0 19 | 20 | if l == tl and r == tr : return self._t[v] 21 | 22 | tm = (tl + tr) // 2 23 | return self._sum_util(v*2, tl, tm, l, min(r, tm)) + self._sum_util(v*2+1, tm+1, tr, max(l, tm+1), r) 24 | 25 | def _update_util(self, v, tl, tr, pos, new_val): 26 | if tl == tr: 27 | self._t[v] = new_val 28 | else: 29 | tm = (tl + tr) // 2 30 | if pos <= tm: self._update_util(v*2, tl, tm, pos, new_val) 31 | else: self._update_util(v*2+1, tm+1, tr, pos, new_val) 32 | self._t[v] = self._t[v*2] + self._t[v*2+1] 33 | 34 | def sum(self, l, r): 35 | return self._sum_util(1 , 0 , self._n - 1 , l , r) 36 | 37 | def update(self, pos, new_val): 38 | self._update_util(1 , 0 , self._n - 1 , pos , new_val) 39 | 40 | def add(self, pos, change): 41 | value = self._sum(pos , pos) 42 | self._update_util(1 , 0 , self._n - 1 , pos , value + change) -------------------------------------------------------------------------------- /data_structures/stack/balanced_expression.py: -------------------------------------------------------------------------------- 1 | stack = [] 2 | def checkBalanced(expr): 3 | for i in expr: 4 | if i == "{" or i == "[" or i == "(": 5 | stack.append(i) 6 | elif i == "}" or i == "]" or i == ")": 7 | if not stack: 8 | return False 9 | top = stack.pop() 10 | if i == "}" and top != "{": 11 | return False 12 | elif i == "]" and top != "[": 13 | return False 14 | elif i == ")" and top != "(": 15 | return False 16 | else: 17 | print("Invalid Expression") 18 | return False 19 | 20 | if not len(stack): 21 | return True 22 | else: 23 | return False 24 | 25 | # main function 26 | expr = input() 27 | if not checkBalanced(expr): 28 | print("Not Balanced") 29 | else: 30 | print('Balanced') 31 | -------------------------------------------------------------------------------- /data_structures/stack/index.md: -------------------------------------------------------------------------------- 1 | # Index of stack 2 | 3 | * [Valid Parentheses](valid_parenthesis.py) 4 | * [Largest Rectangle Area in a Histogram](largest_rectangle_area_in_histogram.py) 5 | * [Remove Duplicates Adjacent](remove_duplicates_adjacent.py) 6 | * [Validate Stack Sequence](validate_stack_sequence.py) 7 | -------------------------------------------------------------------------------- /data_structures/stack/largest_rectangle_area_in_histogram.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Largest rectangle area in a histogram:: 3 | Find the largest rectangular area possible in a given histogram where the largest rectangle can be made of a number of contiguous bars. 4 | For simplicity, assume that all bars have same width and the width is 1 unit. 5 | ''' 6 | 7 | def max_area_histogram(histogram): 8 | 9 | stack = list() 10 | 11 | max_area = 0 # Initialize max area 12 | 13 | index = 0 14 | 15 | while index < len(histogram): 16 | 17 | if (not stack) or (histogram[stack[-1]] <= histogram[index]): 18 | stack.append(index) 19 | index += 1 20 | else: 21 | top_of_stack = stack.pop() 22 | area = (histogram[top_of_stack] * ((index - stack[-1] - 1) if stack else index)) 23 | max_area = max(max_area, area) 24 | 25 | while stack: 26 | top_of_stack = stack.pop() 27 | area = (histogram[top_of_stack] * ((index - stack[-1] - 1) if stack else index)) 28 | 29 | max_area = max(max_area, area) 30 | 31 | return max_area 32 | 33 | 34 | hist = [4, 7, 1, 8, 4, 9, 5] 35 | print("Maximum area is", 36 | max_area_histogram(hist)) 37 | -------------------------------------------------------------------------------- /data_structures/stack/remove_duplicates_adjacent.py: -------------------------------------------------------------------------------- 1 | def remove(s): 2 | stack = [] 3 | for element in s: 4 | if stack and element == stack[-1]: 5 | stack.pop() 6 | else: 7 | stack.append(element) 8 | 9 | -------------------------------------------------------------------------------- /data_structures/stack/stack_using_list.py: -------------------------------------------------------------------------------- 1 | class Stack(): 2 | """ 3 | Stack follows Last-In-First-Out methodology 4 | """ 5 | 6 | def __init__(self): 7 | self.entries = [] 8 | 9 | def size(self): 10 | return len(self.entries) 11 | 12 | def push(self, val): 13 | self.entries.append(val) 14 | 15 | def pop(self): 16 | if self.size() > 0: 17 | self.entries.pop(self.size() - 1) 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /data_structures/stack/valid_parenthesis.py: -------------------------------------------------------------------------------- 1 | def check(s): 2 | stack = [] 3 | for element in s: 4 | if element == '(': 5 | stack.append(')') 6 | elif element == '[': 7 | stack.append(']') 8 | elif element == '{': 9 | stack.append('}') 10 | elif not stack or stack.pop() != element: 11 | return False 12 | return not stack 13 | -------------------------------------------------------------------------------- /data_structures/stack/validate_stack_sequence.py: -------------------------------------------------------------------------------- 1 | # pushed = [1,2,3,4,5] 2 | # popped = [4,5,3,2,1] 3 | 4 | # this sequence is possible 5 | 6 | def verify(pushed, popped): 7 | i = 0 8 | stack = [] 9 | for x in pushed: 10 | stack.append(x) 11 | while stack and i < len(popped) and stack[-1] == popped[i]: 12 | stack.pop() 13 | i += 1 14 | return i == len(popped) 15 | -------------------------------------------------------------------------------- /data_structures/strings/KMP_Pattern_Search.py: -------------------------------------------------------------------------------- 1 | #Python program for KMP Algorithm 2 | def KMP_pattern_search(pattern, text): 3 | P = len(pattern) 4 | Q = len(text) 5 | 6 | # create long_prefix_suffix[] that will hold the longest prefix suffix 7 | # values for pattern 8 | long_prefix_suffix = [0]*P 9 | 10 | # index for pattern[] 11 | j=0 12 | 13 | # preprocess the pattern (caluclate long_prefix_suffix[] array) 14 | long_Prefix_Suffix_Array(pattern, P, long_prefix_suffix) 15 | 16 | # index for text[] 17 | i=0 18 | 19 | while i < Q: 20 | if pattern[j] == text[i]: 21 | i += 1 22 | j += 1 23 | 24 | if j == P: 25 | print("Pattern found at index " + str(i-j)) 26 | j=long_prefix_suffix[j-1] 27 | 28 | # mismatch after j matches 29 | elif i < Q and pattern[j] != text[i]: 30 | if j != 0: 31 | j=long_prefix_suffix[j-1] 32 | else: 33 | i += 1 34 | 35 | def long_Prefix_Suffix_Array(pattern, P, long_prefix_suffix): 36 | # length of the previous longest prefix suffix 37 | l=0 38 | long_prefix_suffix[0]=0 39 | i=1 40 | 41 | # the loop calculates long_prefix_suffix[i] for i = 1 to P-1 42 | while i < P: 43 | if pattern[i] == pattern[l]: 44 | l += 1 45 | long_prefix_suffix[i] = l 46 | i += 1 47 | else: 48 | if l != 0: 49 | l=long_prefix_suffix[l-1] 50 | else: 51 | long_prefix_suffix[i] = 0 52 | i += 1 53 | 54 | text = "ABABDABACDABABCABAB" 55 | pattern = "ABABCABAB" 56 | KMP_pattern_search(pattern, text) 57 | 58 | -------------------------------------------------------------------------------- /data_structures/strings/adjacent_vowel_pairs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Question : 3 | Given a string consisting of English alphabets, the task is to count the number of adjacent pairs of vowels. 4 | For example : 5 | Input: str = “abaebio” 6 | Output: 2 7 | (a, e) and (i, o) are the only valid pairs. 8 | Source:A very Common Interview Question. 9 | 10 | Time Complexity : The goal is to complete this question in O(n). 11 | ''' 12 | 13 | #function to check whether a character is vowel or not 14 | def is_vowel(character): 15 | if character.lower() in ['a', 'e', 'i', 'o', 'u']: 16 | return True 17 | else: 18 | return False 19 | 20 | 21 | #function to find the number of adjacent vowel pairs. 22 | def adjacent_pairs(string): 23 | string=string.lower() 24 | n=len(string) 25 | count = 0 26 | for i in range(0,n): 27 | if ((is_vowel(string[i]) and is_vowel(string[i + 1]))): 28 | count += 1 29 | return count 30 | 31 | #driver code 32 | string=input("enter string") 33 | print (adjacent_pairs(string),"is the number of adjacent pairs of vowels") 34 | 35 | -------------------------------------------------------------------------------- /data_structures/strings/finding_all_substrings.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Question : Print and Count all the substrings of a given string. 3 | Sample Input : hello 4 | Sample Output : ['h', 'he', 'hel', 'hell', 'hello', 'e', 'el', 'ell', 'ello', 'l', 'll', 'llo', 'l', 'lo', 'o'] 5 | 15 6 | Source : Hackerrank''' 7 | 8 | def finding_substrings(string): 9 | 10 | # Get all substrings of string and store them in an empty list 11 | list =[] 12 | count=0 13 | for i in range(len(string)): 14 | for j in range(i+1,(len(string)+1)): 15 | list.append(string[i:j]) 16 | 17 | count=count+1 18 | 19 | # printing result 20 | print(list) 21 | print(count) 22 | 23 | #driver code 24 | string = input("enter string: ") 25 | finding_substrings(string) 26 | 27 | -------------------------------------------------------------------------------- /data_structures/strings/index.md: -------------------------------------------------------------------------------- 1 | # Index of strings 2 | 3 | * [Check Permutations](check_permutations.py) 4 | * [Are Anagrams](are_anagrams.py) 5 | * [Adjacent Vowel Pairs](adjacent_vowel_pairs.py) 6 | * [Toggle String](toggle_string.py) 7 | * [Is Rotate String](rotate_string.py) 8 | -------------------------------------------------------------------------------- /data_structures/strings/reverse_with_special_chars.py: -------------------------------------------------------------------------------- 1 | # Reverse a string making sure that special characters remain 2 | # in their place 3 | 4 | # a#bcd#efg -> g#fed#cba 5 | 6 | 7 | def reverse(s): 8 | i = 0 9 | j = len(s) - 1 10 | 11 | while i < j: 12 | if s[i].isalnum() and s[j].isalnum(): 13 | s[i], s[j] = s[j], s[i] 14 | i += 1 15 | j -= 1 16 | if not s[i].isalnum(): 17 | i += 1 18 | if not s[j].isalnum(): 19 | j -= 1 20 | return ''.join(s) 21 | 22 | 23 | s = 'a#bcd#efg' 24 | print(s) 25 | print(reverse(list(s))) 26 | -------------------------------------------------------------------------------- /data_structures/strings/roman_to_integer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Question: 3 | Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M and their values are 1,5,10,50,100,500,1000 respectively.Given a roman numeral,convert it to an integer.Input is guaranteed to be within the range from 1 to 3999. 4 | 5 | Example 1: 6 | Input : 'XI' 7 | Output : 11 8 | 9 | Example 2: 10 | Input : 'CCCXL' 11 | Output : 340 12 | 13 | Source: Interview question. 14 | 15 | Time Complexity : O(n) 16 | Space Complexity : O(1) 17 | 18 | ''' 19 | 20 | # Function to convert a roman numeral to an integer 21 | def roman_to_integer(input): 22 | romans={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000} 23 | sum=0 24 | for i in range(input): 25 | # Getting the value of the symbol 26 | value=romans[input[i]] 27 | # Comparing if the next value is bigger or smaller than the current value 28 | if i+1value: 30 | # If it is big, the value is subtracted from the sum 31 | sum-=value 32 | else: 33 | sum+=value 34 | return sum 35 | 36 | # Main code 37 | print("Roman to integer conversion of") 38 | input=input.upper() 39 | roman_to_integer(input) 40 | 41 | -------------------------------------------------------------------------------- /data_structures/strings/rotate_string.py: -------------------------------------------------------------------------------- 1 | def is_rotate_string(A, B): 2 | """ 3 | Given two strings, A and B. 4 | 5 | A shift on A consists of taking string A and moving the leftmost character to the rightmost position. For example, if A = 'abcde', then it will be 'bcdea' after one shift on A. Return True if and only if A can become B after some number of shifts on A. 6 | 7 | :type A: str 8 | :type B: str 9 | :rtype: bool 10 | """ 11 | len_a = len(A) 12 | len_b = len(B) 13 | if len_a != len_b: 14 | return False 15 | if len_a == 0: 16 | return True 17 | for i in range(len_a): 18 | A = A[-1:] + A[:-1] 19 | if A == B: 20 | return True 21 | return False 22 | 23 | 24 | # Test 25 | print(is_rotate_string('abcde', 'cdeab')) 26 | print(is_rotate_string('abcde', 'abced')) 27 | -------------------------------------------------------------------------------- /data_structures/strings/toggle_string.py: -------------------------------------------------------------------------------- 1 | """ 2 | Question 3 | You have been given a String S consisting of uppercase and lowercase English alphabets. You need to change the case of each alphabet 4 | in this String. That is, all the uppercase letters should be converted to lowercase and all the lowercase letters should be converted 5 | to uppercase. You need to then print the resultant String to output. 6 | Source Hackerearth 7 | 8 | SAMPLE INPUT 9 | abcdE 10 | SAMPLE OUTPUT 11 | ABCDe 12 | """ 13 | 14 | def toggle_string_1(string): 15 | return string.swapcase() 16 | 17 | def toggle_string_2(string): 18 | toggle_string='' 19 | for s in string: 20 | if s.isupper(): 21 | toggle_string+=s.lower() 22 | elif s.islower(): 23 | toggle_string+=s.upper() 24 | else: 25 | toggle_string+=s 26 | return toggle_string 27 | 28 | string=input() 29 | 30 | # method 1 31 | print(toggle_string_1(string)) 32 | # method 2 33 | print(toggle_string_2(string)) 34 | -------------------------------------------------------------------------------- /data_structures/strings/unique_char_check.py: -------------------------------------------------------------------------------- 1 | """ 2 | Question 3 | You are given a string S, check if all characters are unique. 4 | 5 | SAMPLE INPUT 1 6 | abcd 7 | SAMPLE OUTPUT 1 8 | True 9 | 10 | SAMPLE INPUT 2 11 | aabc 12 | SAMPLE OUTPUT 2 13 | False 14 | """ 15 | from collections import Counter 16 | def unique_char_check(S): 17 | character_count = Counter(S) 18 | 19 | if len(character_count) == len(S): 20 | return True 21 | return False 22 | 23 | S = input() 24 | print(unique_char_check(S)) --------------------------------------------------------------------------------