├── __init__.py ├── graphs ├── __init__.py ├── detect_cycle.py ├── adjacency_list.py ├── topological_sort.py ├── adjacency_matrix.py └── bidirectional_search.py ├── trees ├── __init__.py ├── __pycache__ │ └── binary_tree.cpython-37.pyc ├── height_of_tree.py ├── random_node.py ├── minimal_tree.py ├── validate_bst.py ├── check_balanced.py ├── first_common_ancestor.py ├── paths_with_sum.py ├── check_subtree.py ├── bst_sequence.py ├── list_of_depths.py └── binary_tree.py ├── trie ├── __init__.py └── trie_operations.py ├── binary_heap ├── __init__.py └── min_heap.py ├── linked_list ├── __init__.py ├── delete_middle_node.py ├── loop_detection.py ├── kth_to_last.py ├── intersection.py ├── palindrome.py ├── merge_two_lists.py ├── partition.py ├── sum_lists.py ├── merge_k_sorted_lists.py └── linked_list_operations.py ├── hard_problems ├── __init__.py ├── circus_tower.py ├── shuffle.py ├── add_without_plus.py ├── random_set.py ├── count_2s.py ├── majority_element.py ├── re-space.py ├── longest_word.py ├── BiNode.py ├── missing_two.py ├── kth_multiple.py ├── the_masseuse.py ├── sparse_similarity.py ├── word_distance.py ├── volume_of_histogram.py ├── missing_number.py ├── baby_names.py ├── smallest_k.py ├── max_submatrix.py ├── continuous_median.py ├── multi_search.py ├── letters_numbers.py ├── max_black_square.py └── shortest_supersequence.py ├── recursion_and_dp ├── __init__.py ├── tower_of_hanoi.py ├── coins.py ├── power_set.py ├── paint_fill.py ├── recursive_multiply.py ├── triple_step.py ├── permutation_with_dups.py ├── parens.py ├── robot_grid.py ├── permutation_without_dups.py ├── magic_index.py ├── eight_queens.py └── boolean_parenthesization.py ├── stacks_and_queues ├── __init__.py ├── sort_stack.py ├── stack.py ├── three_in_one.py ├── stack_of_plates.py ├── queue_via_stacks.py └── animal_shelter.py ├── arrays_and_strings ├── check_permutation.py ├── string_rotation.py ├── string_compression.py ├── rotate_matrix.py ├── urlify.py ├── zero_matrix.py ├── one_away.py ├── is_unique.py └── palindrome_permutation.py ├── moderate_problems ├── diving_board.py ├── word_frequencies.py ├── number_swapper.py ├── contiguous_sequence.py ├── factorial_zeros.py ├── rand_7.py ├── sum_swap.py ├── number_max.py ├── T9.py ├── smallest_difference.py ├── pond_size.py ├── living_people.py ├── operations.py ├── pairs_with_sum.py ├── master_mind.py ├── tic_tac_toe.py ├── best_line.py ├── pattern_matching.py ├── sub_sort.py ├── english_int.py ├── line_segment.py ├── calculator.py └── lru_cache.py ├── top_k_frequent_words.py ├── README.md ├── sorting ├── insertion_sort.py ├── quick_sort.py ├── bubble_sort.py ├── selection_sort.py └── merge_sort.py ├── rope_cost.py ├── queue.py ├── hashing.py └── .gitignore /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /graphs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trees/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trie/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_heap/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hard_problems/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /recursion_and_dp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks_and_queues/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hard_problems/circus_tower.py: -------------------------------------------------------------------------------- 1 | # same as longest increasing sequence problem 2 | -------------------------------------------------------------------------------- /trees/__pycache__/binary_tree.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirjharij/cracking-the-coding-interview-in-python/HEAD/trees/__pycache__/binary_tree.cpython-37.pyc -------------------------------------------------------------------------------- /hard_problems/shuffle.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def shuffle(cards): 5 | for i in range(len(cards)): 6 | k = random.sample(range(i), 1) 7 | cards[k], cards[i] = cards[i], cards[k] 8 | 9 | -------------------------------------------------------------------------------- /hard_problems/add_without_plus.py: -------------------------------------------------------------------------------- 1 | def add(a, b): 2 | if b == 0: 3 | return a 4 | sum_num = a ^ b 5 | carry = (a & b) << 1 6 | return add(sum_num, carry) 7 | 8 | 9 | print(add(12, 14)) 10 | -------------------------------------------------------------------------------- /arrays_and_strings/check_permutation.py: -------------------------------------------------------------------------------- 1 | def check_str_permutation(str1, str2): 2 | sorted_str1 = ''.join(sorted(str1)) 3 | sorted_str2 = ''.join(sorted(str2)) 4 | if sorted_str1 == sorted_str2: 5 | return True 6 | else: 7 | return False 8 | 9 | -------------------------------------------------------------------------------- /moderate_problems/diving_board.py: -------------------------------------------------------------------------------- 1 | def get_all_length(k, shorter, longer): 2 | length_list = list() 3 | for i in range(k+1): 4 | length = shorter * i + (k - i) * longer 5 | length_list.append(length) 6 | print(length_list) 7 | 8 | 9 | get_all_length(5, 2, 5) 10 | -------------------------------------------------------------------------------- /top_k_frequent_words.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | 4 | class Solution: 5 | def topKFrequent(self, words, k): 6 | word_dict = Counter(words) 7 | sorted_list = sorted(word_dict.items(), key=lambda x: x[1], reverse=True) 8 | top_k = sorted_list[:k] 9 | return [item[0] for item in top_k] 10 | -------------------------------------------------------------------------------- /moderate_problems/word_frequencies.py: -------------------------------------------------------------------------------- 1 | word_dict = dict() 2 | 3 | 4 | def get_word_freq(word): 5 | if word in word_dict: 6 | return word_dict[word] 7 | 8 | 9 | def count_frequencies(book): 10 | for word in book: 11 | if word in word_dict: 12 | word_dict[word] += 1 13 | else: 14 | word_dict[word] = 1 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cracking-the-coding-interview-in-python 2 | 3 | One stop solution for all problems from the famous book by Gayle Laakmann "Cracking the Coding Interview". 4 | 5 | This repo contains solutions to all the data structures(arrays, linked list, stacks, queues, trees, graphs, trie) questions solved using Python3. 6 | 7 | Please feel free to contribute to the repo. 8 | -------------------------------------------------------------------------------- /recursion_and_dp/tower_of_hanoi.py: -------------------------------------------------------------------------------- 1 | # Move n-1 disks from A to B using C as the temp pillar 2 | # Move nth disk from A to C 3 | # Move n-1 disk from B to C using A as the temp pillar 4 | 5 | 6 | def toh(n, src=None, temp=None, dest=None): 7 | if n == 1: 8 | print(src, dest) 9 | 10 | toh(n-1, src, dest, temp) 11 | toh(n, src, dest) 12 | toh(n-1, temp, src, dest) 13 | -------------------------------------------------------------------------------- /moderate_problems/number_swapper.py: -------------------------------------------------------------------------------- 1 | def swap_numbers(n1, n2): 2 | n1 = n2 - n1 3 | n2 = n2 - n1 4 | n1 = n1 + n2 5 | print(n1, n2) 6 | 7 | 8 | def swap_numbers_with_xor(n1, n2): 9 | n1 = n1 ^ n2 10 | n2 = n1 ^ n2 11 | n1 = n1 ^ n2 12 | print(n1, n2) 13 | 14 | 15 | if __name__ == "__main__": 16 | swap_numbers(1, 5) 17 | swap_numbers_with_xor(7, 10) 18 | -------------------------------------------------------------------------------- /recursion_and_dp/coins.py: -------------------------------------------------------------------------------- 1 | def get_ways(n, denoms, index): 2 | if index >= len(denoms): 3 | return 1 4 | denom = denoms[index] 5 | i = 0 6 | ways = 0 7 | while i * denom <= n: 8 | amount_rem = n - i * denom 9 | ways += get_ways(amount_rem, denoms, index+1) 10 | i += 1 11 | return ways 12 | 13 | 14 | print(get_ways(5, [25, 10, 5, 1], 0)) 15 | -------------------------------------------------------------------------------- /hard_problems/random_set.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def find_random_set(arr, m): 5 | subset = [] 6 | for i in range(0, m): 7 | subset.append(arr[i]) 8 | 9 | for i in range(m, len(arr)): 10 | rand = random.sample(range(i), 1) 11 | if rand[0] < m: 12 | subset[rand[0]] = arr[i] 13 | 14 | return subset 15 | 16 | 17 | print(find_random_set([1,7,9,10, 2, 3,4], 2)) 18 | -------------------------------------------------------------------------------- /arrays_and_strings/string_rotation.py: -------------------------------------------------------------------------------- 1 | def str_rotation(str1, str2): 2 | if len(str1) == len(str2): 3 | new_str = str2 + str2 4 | if str1 in new_str: 5 | return True 6 | else: 7 | return False 8 | 9 | 10 | if __name__ == "__main__": 11 | out = str_rotation("waterbottle", "erbottlewat") 12 | if out: 13 | print("Is rotated") 14 | else: 15 | print("Not rotated") 16 | -------------------------------------------------------------------------------- /moderate_problems/contiguous_sequence.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def sequence(arr): 5 | max_so_far = -sys.maxsize 6 | max_ending_here = 0 7 | for item in arr: 8 | max_ending_here += item 9 | if max_ending_here > max_so_far: 10 | max_so_far = max_ending_here 11 | 12 | if max_ending_here < 0: 13 | max_ending_here = 0 14 | 15 | return max_so_far 16 | 17 | 18 | print(sequence([2, -8, 3, -2, 4, -10])) 19 | -------------------------------------------------------------------------------- /moderate_problems/factorial_zeros.py: -------------------------------------------------------------------------------- 1 | def count_zeros(num): 2 | count = 0 3 | for i in range(2, num+1): 4 | count += factors_of_5(i) 5 | return count 6 | 7 | 8 | def factors_of_5(num): 9 | count = 0 10 | while num % 5 == 0: 11 | count += 1 12 | num /= 5 13 | return count 14 | 15 | 16 | def multiples_of_5(num): 17 | i = 5 18 | count = 0 19 | while num/i > 0: 20 | count += num / i 21 | i = i * 5 22 | return count 23 | -------------------------------------------------------------------------------- /sorting/insertion_sort.py: -------------------------------------------------------------------------------- 1 | # Insertion sort is a simple sorting algorithm that works the way we sort playing cards in our hands. 2 | # Best case O(n). Worst Case: O(n^2) 3 | 4 | 5 | def insertion_sort(data): 6 | for i in range(1, len(data)): 7 | key = data[i] 8 | 9 | j = i-1 10 | while j >= 0 and key < data[j]: 11 | data[j+1] = data[j] 12 | j -= 1 13 | data[j+1] = key 14 | print(data) 15 | 16 | 17 | insertion_sort([20, 7, 10, 3, 8]) 18 | -------------------------------------------------------------------------------- /recursion_and_dp/power_set.py: -------------------------------------------------------------------------------- 1 | def get_subset(str_set, index): 2 | if len(str_set) == index: 3 | new_set = [] 4 | subsets = [] 5 | subsets.append(new_set) 6 | return subsets 7 | else: 8 | subsets = get_subset(str_set, index+1) 9 | new_item = str_set[index] 10 | new_subset = [] 11 | for s in subsets: 12 | new_set = s.copy() 13 | new_set.append(new_item) 14 | new_subset.append(new_set) 15 | subsets.extend(new_subset) 16 | return subsets 17 | 18 | print(get_subset(["a", "b", "c"], 0)) 19 | -------------------------------------------------------------------------------- /sorting/quick_sort.py: -------------------------------------------------------------------------------- 1 | def partition(arr, low, high): 2 | pivot = arr[high] 3 | i = low - 1 4 | for j in range(low, high): 5 | if arr[j] < pivot: 6 | i += 1 7 | arr[i], arr[j] = arr[j], arr[i] 8 | 9 | arr[i+1], arr[high] = arr[high], arr[i+1] 10 | return i+1 11 | 12 | 13 | def quick_sort(arr, low, high): 14 | if low < high: 15 | partition_index = partition(arr, low, high) 16 | 17 | quick_sort(arr, low, partition_index-1) 18 | quick_sort(arr, partition_index+1, high) 19 | print(arr) 20 | 21 | 22 | quick_sort([10, 7, 8, 9, 1, 5], 0, 5) 23 | -------------------------------------------------------------------------------- /moderate_problems/rand_7.py: -------------------------------------------------------------------------------- 1 | # 1. Expanding a zero based random 2 | # rand 10 3 | # rand 100 from rand10 4 | # rand_100 = 10*rand_10 + rand_10 5 | # rand_1000 = 100*rand_10 + 10*rand_10 + rand_10 6 | # rand_10 --- 1, 10 7 | # rand_100 = 10*(rand_10-1) + (rand_10 - 1) 8 | # 9 | # 2. Discard and roll again 10 | # 6 sided die and coin 11 | # 1 - H, 2 - T anything else roll again 12 | # 13 | # 3. mapping Rule: 14 | # 1,2,3: H 4,5,6 T 15 | 16 | 17 | def rand_5(): 18 | pass 19 | 20 | 21 | def rand_7(): 22 | while True: 23 | num = 5 * rand_5() + rand_5() 24 | if num < 21: 25 | return num % 7 26 | -------------------------------------------------------------------------------- /moderate_problems/sum_swap.py: -------------------------------------------------------------------------------- 1 | # sum1 -a + b = sum2 -b + a 2 | # sum1 - sum2 = 2a - 2b 3 | # a - b = sum1 - sum2 /2 4 | 5 | 6 | def equal_sum(arr1, arr2): 7 | s1 = sum(arr1) 8 | s2 = sum(arr2) 9 | diff = s1 - s2 10 | if diff % 2 != 0: 11 | print("No pair found") 12 | return None 13 | target = diff / 2 14 | return find_difference(arr1, arr2, target) 15 | 16 | 17 | def find_difference(arr1, arr2, target): 18 | for one in arr1: 19 | two = int(one - target) 20 | if two in arr2: 21 | return one, two 22 | 23 | 24 | print(equal_sum([4, 1, 2, 1, 1, 2], [3, 6, 3, 3])) 25 | -------------------------------------------------------------------------------- /recursion_and_dp/paint_fill.py: -------------------------------------------------------------------------------- 1 | def paint_fill(mat, r, c, colour): 2 | if mat[r][c] == colour: 3 | return False 4 | return fill_paint(mat, r, c, mat[r][c], colour) 5 | 6 | 7 | def fill_paint(mat, r, c, orig_color, colour): 8 | if r < 0 or r >= len(mat) or c < 0 or c >= len(mat): 9 | return False 10 | if mat[r][c] == orig_color: 11 | mat[r][c] = colour 12 | fill_paint(mat, r-1, c, orig_color, colour) 13 | fill_paint(mat, r+1, c, orig_color, colour) 14 | fill_paint(mat, r, c-1, orig_color, colour) 15 | fill_paint(mat, r, c+1, orig_color, colour) 16 | return True 17 | -------------------------------------------------------------------------------- /recursion_and_dp/recursive_multiply.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | def multiply(bigger, smaller, memo): 5 | if smaller == 0: 6 | return 0 7 | elif smaller == 1: 8 | return bigger 9 | 10 | if memo[smaller] > 0: 11 | return memo[smaller] 12 | 13 | s = smaller // 2 14 | 15 | out = multiply(bigger, s, memo) 16 | if smaller % 2 == 1: 17 | out_num = out + out + bigger 18 | else: 19 | out_num = out + out 20 | 21 | memo[smaller] = out_num 22 | return memo[smaller] 23 | 24 | 25 | memo = defaultdict(lambda: 0) 26 | print(multiply(9, 8, memo)) 27 | -------------------------------------------------------------------------------- /arrays_and_strings/string_compression.py: -------------------------------------------------------------------------------- 1 | def compress_str(input_str): 2 | output_str = list() 3 | chr_count = 0 4 | for i in range(len(input_str)): 5 | if i+1 >= len(input_str) or input_str[i] != input_str[i+1]: 6 | output_str.append(input_str[i]) 7 | output_str.append(str(chr_count)) 8 | chr_count = 0 9 | chr_count += 1 10 | 11 | return ''.join(output_str) 12 | 13 | 14 | if __name__ == "__main__": 15 | inp_str = "aabbc" 16 | comp_str = compress_str(inp_str) 17 | if len(inp_str) <= len(comp_str): 18 | print(inp_str) 19 | else: 20 | print(comp_str) 21 | -------------------------------------------------------------------------------- /hard_problems/count_2s.py: -------------------------------------------------------------------------------- 1 | def count_2s(num): 2 | count = 0 3 | for i in range(len(num)): 4 | count += count_2s_in_digits(num, i) 5 | return count 6 | 7 | 8 | def count_2s_in_digits(num, d): 9 | power_of_10 = 10 ** d 10 | next_power_of_10 = 10 * power_of_10 11 | right = num % power_of_10 12 | 13 | round_down = num - (num % next_power_of_10) 14 | round_up = round_down + next_power_of_10 15 | 16 | digit = (num / power_of_10) % 10 17 | if digit < 2: 18 | return round_down / 10 19 | elif digit == 2: 20 | return round_up / 10 + right + 1 21 | else: 22 | return round_up / 10 23 | -------------------------------------------------------------------------------- /recursion_and_dp/triple_step.py: -------------------------------------------------------------------------------- 1 | # recursion 2 | def count_ways(n): 3 | if n == 0: 4 | return 1 5 | elif n < 0: 6 | return 0 7 | else: 8 | return count_ways(n-1) + count_ways(n-2) + count_ways(n-3) 9 | 10 | 11 | # DP 12 | def count_ways_dp(n, memo): 13 | if n == 0: 14 | return 1 15 | elif n < 0: 16 | return 0 17 | elif memo[n] > -1: 18 | return memo[n] 19 | else: 20 | memo[n] = count_ways_dp(n-1, memo) + count_ways_dp(n-2, memo) + count_ways_dp(n-3, memo) 21 | return memo[n] 22 | 23 | 24 | n = 4 25 | memo = [-1] * (n+1) 26 | print(count_ways_dp(n, memo)) 27 | -------------------------------------------------------------------------------- /arrays_and_strings/rotate_matrix.py: -------------------------------------------------------------------------------- 1 | def matrix_rotate(mat): 2 | n = len(mat) 3 | 4 | for layer in range(0, n//2): 5 | first = layer 6 | last = n - 1 - layer 7 | for i in range(first, last): 8 | offset = i - first 9 | 10 | top = mat[first][i] 11 | mat[first][i] = mat[last - offset][first] 12 | mat[last - offset][first] = mat[last][last - offset] 13 | mat[last][last - offset] = mat[i][last] 14 | mat[i][last] = top 15 | print(mat) 16 | 17 | 18 | if __name__ == "__main__": 19 | matrix_rotate([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) 20 | -------------------------------------------------------------------------------- /arrays_and_strings/urlify.py: -------------------------------------------------------------------------------- 1 | def urlify_string(str, true_length): 2 | str_array = list(str) 3 | index = len(str) 4 | for i in range(true_length-1, 0, -1): 5 | if str_array[i] == ' ': 6 | str_array[index - 1] = '0' 7 | str_array[index - 2] = '2' 8 | str_array[index - 3] = '%' 9 | index = index - 3 10 | else: 11 | str_array[index - 1] = str_array[i] 12 | index = index - 1 13 | 14 | return ''.join(str_array) 15 | 16 | 17 | if __name__ == "__main__": 18 | s = "Mr John Smith " 19 | length = 13 20 | out = urlify_string(s, length) 21 | print(out) 22 | -------------------------------------------------------------------------------- /hard_problems/majority_element.py: -------------------------------------------------------------------------------- 1 | def find_maj(arr): 2 | maj = 0 3 | count = 0 4 | for i in range(len(arr)): 5 | if count == 0: 6 | maj = arr[i] 7 | if arr[i] == maj: 8 | count += 1 9 | else: 10 | count -= 1 11 | return maj 12 | 13 | 14 | def validate(arr, majority): 15 | count = 0 16 | for i in arr: 17 | if i == majority: 18 | count += 1 19 | 20 | if count > len(arr)//2: 21 | return majority 22 | else: 23 | return -1 24 | 25 | 26 | arr = [1, 2, 5, 9, 5, 9, 5, 5, 5] 27 | maj_element = find_maj(arr) 28 | print(validate(arr, maj_element)) 29 | -------------------------------------------------------------------------------- /recursion_and_dp/permutation_with_dups.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | str_dict = defaultdict(lambda: 0) 4 | 5 | 6 | def get_permutations(input_str): 7 | for item in input_str: 8 | str_dict[item] += 1 9 | permute(str_dict, '', len(input_str)) 10 | 11 | 12 | def permute(str_dict, prefix, remaining): 13 | if remaining == 0: 14 | print(prefix) 15 | return 16 | for c in str_dict: 17 | count = str_dict[c] 18 | if count != 0: 19 | str_dict[c] = count - 1 20 | permute(str_dict, prefix + c, remaining-1) 21 | str_dict[c] = count 22 | 23 | 24 | get_permutations("ABA") 25 | -------------------------------------------------------------------------------- /linked_list/delete_middle_node.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def deletenode(node): 5 | if node is None or node.next is None: 6 | return 7 | node.data = node.next.data 8 | node.next = node.next.next 9 | 10 | 11 | if __name__ == "__main__": 12 | llist = LinkedList() 13 | llist.push(4) 14 | llist.push(7) 15 | llist.push(1) 16 | llist.push(2) 17 | llist.push(3) 18 | llist.push(5) 19 | llist.push(6) 20 | llist.push(8) 21 | print("Before Deletion") 22 | llist.traverse() 23 | deletenode(llist.head.next.next) 24 | print("\nAfter Deletion") 25 | llist.traverse() 26 | 27 | 28 | -------------------------------------------------------------------------------- /moderate_problems/number_max.py: -------------------------------------------------------------------------------- 1 | def flip(bit): 2 | return 1 ^ bit 3 | 4 | 5 | def sign(number): 6 | return flip((number >> 31) & 1) 7 | 8 | 9 | def find_max(a, b): 10 | c = a - b 11 | 12 | sa = sign(a) 13 | sb = sign(b) 14 | sc = sign(c) 15 | 16 | # when a & b are of opposite signs there is a chance of overflow so use sign of a 17 | # else use sign of c 18 | use_sign_of_a = sa ^ sb 19 | 20 | use_sign_of_c = flip(sa ^ sb) 21 | 22 | k = use_sign_of_a * sa + use_sign_of_c * sc 23 | q = flip(k) 24 | return a*k + b*q 25 | 26 | 27 | if __name__ == "__main__": 28 | max_num = find_max(2, 10097) 29 | print(max_num) 30 | -------------------------------------------------------------------------------- /recursion_and_dp/parens.py: -------------------------------------------------------------------------------- 1 | def generate_parens(input): 2 | output = [] 3 | get_all_valid_parens(input, input, output, '') 4 | print(output) 5 | 6 | 7 | def get_all_valid_parens(left_paren, right_paren, out, final_str): 8 | if left_paren < 0 or left_paren > right_paren: 9 | return 10 | if left_paren == 0 and right_paren == 0: 11 | out.append(final_str) 12 | else: 13 | if left_paren > 0: 14 | get_all_valid_parens(left_paren-1, right_paren, out, final_str + '(') 15 | if right_paren > left_paren: 16 | get_all_valid_parens(left_paren, right_paren-1, out, final_str + ')') 17 | 18 | 19 | generate_parens(3) -------------------------------------------------------------------------------- /recursion_and_dp/robot_grid.py: -------------------------------------------------------------------------------- 1 | def find_path(mat, row, col, cache, path): 2 | if row < 0 or col < 0: 3 | return False 4 | 5 | if (row, col) in cache: 6 | return cache.get((row, col)) 7 | 8 | is_origin = True if row == 0 and col == 0 else False 9 | flag = False 10 | if is_origin or find_path(mat, row - 1, col, cache, path) or find_path(mat, row, col - 1, cache, path): 11 | path.append((row, col)) 12 | flag = True 13 | 14 | cache[(row, col)] = flag 15 | return flag 16 | 17 | 18 | mat = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 19 | path = [] 20 | cache = {} 21 | if find_path(mat, 2, 2, cache, path): 22 | print(path) 23 | -------------------------------------------------------------------------------- /recursion_and_dp/permutation_without_dups.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | def permute(self, nums): 3 | output = [] 4 | out = self.get_permutations(nums, 0, len(nums) - 1, output) 5 | return out 6 | 7 | def get_permutations(self, a, l, r, out): 8 | if l == r: 9 | n = a.copy() 10 | out.append(n) 11 | return out 12 | else: 13 | for i in range(l, r + 1): 14 | a[l], a[i] = a[i], a[l] 15 | out = self.get_permutations(a, l + 1, r, out) 16 | a[l], a[i] = a[i], a[l] 17 | return out 18 | 19 | 20 | s = Solution() 21 | print(s.permute([1, 2, 3])) 22 | -------------------------------------------------------------------------------- /trees/height_of_tree.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | 4 | def height(tree): 5 | if not tree: 6 | return 0 7 | left_tree_depth = height(tree.left_child) 8 | right_tree_depth = height(tree.right_child) 9 | 10 | return max(left_tree_depth, right_tree_depth) + 1 11 | 12 | 13 | if __name__ == "__main__": 14 | bst_obj = BinarySearchTree() 15 | bst_obj.insert(20) 16 | bst_obj.insert(30) 17 | bst_obj.insert(10) 18 | bst_obj.insert(15) 19 | bst_obj.insert(12) 20 | bst_obj.insert(6) 21 | bst_obj.insert(2) 22 | # bst_obj.in_order(bst_obj.root) 23 | tree_height = height(bst_obj.root) 24 | print(tree_height) 25 | -------------------------------------------------------------------------------- /sorting/bubble_sort.py: -------------------------------------------------------------------------------- 1 | # Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements 2 | # if they are in wrong order. 3 | # Time complexity n^2 4 | 5 | 6 | def bubble_sort(data): 7 | for i in range(len(data)-1, 0, -1): 8 | for j in range(i): 9 | if data[j] > data[j+1]: 10 | data[j], data[j+1] = data[j+1], data[j] 11 | print(data) 12 | 13 | 14 | def bubble_sort_new(data): 15 | for i in range(len(data)): 16 | for j in range(0, len(data)-i-1): 17 | if data[j] > data[j+1]: 18 | data[j], data[j+1] = data[j+1], data[j] 19 | print(data) 20 | 21 | 22 | bubble_sort_new([10, 7, 4, 3, 11]) 23 | 24 | -------------------------------------------------------------------------------- /sorting/selection_sort.py: -------------------------------------------------------------------------------- 1 | # The good thing about selection sort is it never makes more than O(n) swaps and 2 | # can be useful when memory write is a costly operation. 3 | 4 | 5 | def sort(data): 6 | """ 7 | 8 | :param data: list of data to be sorted 9 | :return: sorted list 10 | """ 11 | length = len(data) 12 | for i in range(length): 13 | min_data = data[i] 14 | min_index = i 15 | for j in range(i+1, length): 16 | if min_data > data[j]: 17 | min_data = data[j] 18 | min_index = j 19 | # temp = data[i] 20 | data[i], data[min_index] = min_data, data[i] 21 | 22 | print(data) 23 | 24 | 25 | sort([4, 7, 2, 1, 9, 0, 2]) 26 | -------------------------------------------------------------------------------- /rope_cost.py: -------------------------------------------------------------------------------- 1 | rope_list = [2, 3, 4, 6] 2 | rope_cost = 0 3 | for i in range(2, len(rope_list)+1): 4 | rope_sum = 0 5 | for j in range(0, i): 6 | rope_sum += rope_list[j] 7 | rope_cost += rope_sum 8 | 9 | 10 | def maxSubArraySum(a, size): 11 | max_so_far = 0 12 | max_ending_here = 0 13 | 14 | for i in range(0, size): 15 | max_ending_here = max_ending_here + a[i] 16 | if max_so_far < max_ending_here: 17 | max_so_far = max_ending_here 18 | 19 | if max_ending_here < 0: 20 | max_ending_here = 0 21 | return max_so_far 22 | 23 | 24 | a = [-13, -3, -25, -20, -3, -16, -23, 12, -5, -22, -15, -4, -7] 25 | max_sum = maxSubArraySum(a, len(a)) 26 | print(max_sum) 27 | -------------------------------------------------------------------------------- /queue.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | 7 | class Queue: 8 | def __init__(self): 9 | self.first = None 10 | self.last = None 11 | 12 | def add(self, item): 13 | new_node = Node(item) 14 | if self.last is not None: 15 | self.last.next = new_node 16 | self.last = new_node 17 | if self.first is None: 18 | self.first = self.last 19 | 20 | def remove(self): 21 | if self.first is None: 22 | print("Queue is empty") 23 | return 24 | item = self.first.data 25 | self.first = self.first.next 26 | if self.first is None: 27 | self.last = None 28 | return item 29 | -------------------------------------------------------------------------------- /hard_problems/re-space.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def split_str(dictionary, sentence, start): 5 | if start > len(sentence): 6 | return 0, "" 7 | 8 | index = start 9 | best_parsing = None 10 | best_invalid = sys.maxsize 11 | partial = "" 12 | while index < len(sentence): 13 | c = sentence[index] 14 | partial += c 15 | invalid = 0 if partial in dictionary else len(partial) 16 | if invalid < best_invalid: 17 | inv, parsed = split_str(dictionary, sentence, index+1) 18 | if invalid + inv < best_invalid: 19 | best_invalid = invalid + inv 20 | best_parsing = partial + " " + parsed 21 | if best_invalid == 0: 22 | break 23 | index += 1 24 | return best_invalid, best_parsing 25 | -------------------------------------------------------------------------------- /moderate_problems/T9.py: -------------------------------------------------------------------------------- 1 | # brute force 2 | t9_dict = {2: ['a', 'b', 'c'], 3: ['d', 'e', 'f'], 4: ['g', 'h', 'i'], 5: ['j', 'k', 'l'], 6: ['m', 'n', 'o'], 3 | 7: ['p', 'q', 'r', 's'], 8: ['t', 'u', 'v'], 9: ['w', 'x', 'y', 'z']} 4 | 5 | 6 | def get_all_str(inp, final_str='', final_str_list=[]): 7 | if inp is None: 8 | return final_str 9 | st = t9_dict[inp.data] 10 | for item in st: 11 | final_str += item 12 | final_str_list.append(get_all_str(inp.next, final_str, final_str_list)) 13 | 14 | # optimized solution: 15 | # check for words prefix if prefix exist in trie then only move to next step else discard that word 16 | # More optimal solution 17 | # map each word in dictionary to its t9 repr like APPLE-27753 and store in a dict 18 | # get the word from the dict by querying the inp 19 | 20 | -------------------------------------------------------------------------------- /hard_problems/longest_word.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | def find_longest(arr): 5 | arr = sorted(arr, key=len, reverse=True) 6 | map_dict = defaultdict(lambda: False) 7 | for i in arr: 8 | map_dict[i] = True 9 | 10 | for s in arr: 11 | if can_build(s, True, map_dict): 12 | print(s) 13 | return s 14 | 15 | 16 | def can_build(s, is_original, map_dict): 17 | if s in map_dict and not is_original: 18 | return map_dict[s] 19 | 20 | for i in range(1, len(s)): 21 | left = s[0: i] 22 | right = s[i:] 23 | if map_dict[left] and can_build(right, False, map_dict): 24 | return True 25 | 26 | map_dict[s] = False 27 | return False 28 | 29 | find_longest(['cat', 'banana', 'dog', 'nana', 'walk', 'walker', 'dogwalker']) 30 | -------------------------------------------------------------------------------- /moderate_problems/smallest_difference.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def smallest_difference(arr1, arr2): 5 | min_diff = sys.maxsize 6 | arr1.sort() 7 | arr2.sort() 8 | 9 | i = 0 10 | j = 0 11 | 12 | while i < len(arr1) and j < len(arr2): 13 | if arr1[i] == arr2[j]: 14 | min_diff = 0 15 | return min_diff 16 | if arr1[i] > arr2[j]: 17 | diff = arr1[i] - arr2[j] 18 | if diff < min_diff: 19 | min_diff = diff 20 | j += 1 21 | else: 22 | diff = arr2[j] - arr1[i] 23 | if diff < min_diff: 24 | min_diff = diff 25 | i += 1 26 | return min_diff 27 | 28 | 29 | if __name__ == "__main__": 30 | diff = smallest_difference([1, 3, 15, 11, 2], [23, 127, 235, 19, 8]) 31 | print(diff) 32 | -------------------------------------------------------------------------------- /hard_problems/BiNode.py: -------------------------------------------------------------------------------- 1 | class Binode: 2 | def __init__(self, val=None): 3 | self.node1 = Binode() 4 | self.node2 = Binode() 5 | self.val = val 6 | 7 | 8 | class NodePair: 9 | def __init__(self, head, tail): 10 | self.head = head 11 | self.tail = tail 12 | 13 | 14 | def convert(root): 15 | if root is None: 16 | return None 17 | 18 | left_part = convert(root.node1) 19 | right_part = convert(root.node2) 20 | 21 | if left_part: 22 | concat(left_part.tail, root) 23 | 24 | if right_part: 25 | concat(root, right_part.head) 26 | 27 | head = root if left_part is None else left_part.head 28 | tail = root if right_part is None else right_part.tail 29 | return NodePair(head, tail) 30 | 31 | 32 | def concat(x, y): 33 | x.node2 = y 34 | y.node1 = x 35 | -------------------------------------------------------------------------------- /trees/random_node.py: -------------------------------------------------------------------------------- 1 | class TreeNode: 2 | def __init__(self, data): 3 | self.data = data 4 | self.left = None 5 | self.right = None 6 | self.children = None 7 | self.parent = None 8 | self.size = 0 9 | 10 | 11 | class Tree: 12 | def __init__(self): 13 | self.root = None 14 | 15 | def get_count(self, root): 16 | if root is None: 17 | return 0 18 | left_count = self.get_count(root.left) 19 | right_count = self.get_count(root.right) 20 | return left_count + right_count + 1 21 | 22 | def get_children_count(self, root): 23 | if root is None: 24 | return 25 | root.children = self.get_count(self.root) 26 | self.get_children_count(root.left) 27 | self.get_children_count(root.right) 28 | 29 | def get_random_node(self): 30 | pass 31 | -------------------------------------------------------------------------------- /hard_problems/missing_two.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def find_missing(arr, n): 5 | sum_num = 0 6 | square_sum = 0 7 | for i in arr: 8 | sum_num += i 9 | square_sum += i*i 10 | 11 | total_sum = (n * (n+1)) // 2 12 | total_sum_squares = 0 13 | 14 | for i in range(1, n+1): 15 | total_sum_squares += i*i 16 | 17 | rem_squares = total_sum_squares - square_sum 18 | rem_sum = total_sum - sum_num 19 | 20 | x, y = solve_quadratic(rem_sum, rem_squares) 21 | print(x, y) 22 | 23 | 24 | def solve_quadratic(rem_sum, rem_squares): 25 | a = 2 26 | b = -2 * rem_sum 27 | c = rem_sum * rem_sum - rem_squares 28 | 29 | part1 = -1 * b 30 | part2 = math.sqrt(b*b - 4 * a * c) 31 | part3 = 2 * a 32 | 33 | x = int((part1 + part2) // part3) 34 | y = rem_sum - x 35 | return x, y 36 | 37 | 38 | find_missing([1, 2, 4, 6, 7], 7) -------------------------------------------------------------------------------- /hard_problems/kth_multiple.py: -------------------------------------------------------------------------------- 1 | def find_kth_number(k): 2 | numbers = [1] 3 | queue_3 = [3] 4 | queue_5 = [5] 5 | queue_7 = [7] 6 | 7 | for i in range(1, k): 8 | q3 = queue_3[0] 9 | q5 = queue_5[0] 10 | q7 = queue_7[0] 11 | 12 | min_val = min(q3, q5, q7) 13 | if min_val == q3: 14 | val = queue_3.pop(0) 15 | numbers.append(min_val) 16 | queue_3.append(3*val) 17 | queue_5.append(5*val) 18 | queue_7.append(7*val) 19 | elif min_val == q5: 20 | val = queue_5.pop(0) 21 | numbers.append(min_val) 22 | queue_5.append(5 * val) 23 | queue_7.append(7 * val) 24 | elif min_val == q7: 25 | val = queue_7.pop(0) 26 | numbers.append(min_val) 27 | queue_7.append(7 * val) 28 | return numbers[-1] 29 | 30 | 31 | print(find_kth_number(7)) 32 | -------------------------------------------------------------------------------- /hard_problems/the_masseuse.py: -------------------------------------------------------------------------------- 1 | # recursion with memoization 2 | def max_minutes(minutes, memo, index): 3 | if index >= len(minutes): 4 | return 0 5 | if memo[index] == 0: 6 | best_with = minutes[index] + max_minutes(minutes, memo, index+2) 7 | best_without = max_minutes(minutes, memo, index+1) 8 | memo[index] = max(best_with, best_without) 9 | return memo[index] 10 | 11 | 12 | # iterative solution 13 | def max_min_iter(minutes): 14 | one_away = 0 15 | two_away = 0 16 | for i in range(len(minutes)-1, -1, -1): 17 | best_with = minutes[i] + two_away 18 | best_without = one_away 19 | best = max(best_with, best_without) 20 | two_away = one_away 21 | one_away = best 22 | return one_away 23 | 24 | 25 | minutes = [30, 15, 60, 75, 45, 15, 15, 45] 26 | memo = [0] * len(minutes) 27 | print(max_minutes(minutes, memo, 0)) 28 | print(max_min_iter(minutes)) 29 | -------------------------------------------------------------------------------- /hard_problems/sparse_similarity.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict, Counter 2 | 3 | word_map = defaultdict(list) 4 | 5 | 6 | def sparse_sim(data): 7 | for i, v in data.items(): 8 | for word in v: 9 | word_map[word].append(i) 10 | 11 | output = {} 12 | for i, v in data.items(): 13 | docs_list = [] 14 | for word in v: 15 | docs_list.extend(word_map[word]) 16 | 17 | doc_counts = Counter(docs_list) 18 | 19 | for doc, val in doc_counts.items(): 20 | similarity = val / (len(v) + len(data[doc])) 21 | if similarity > 0: 22 | if i == doc: 23 | continue 24 | elif (i, doc) in output or (doc, i) in output: 25 | continue 26 | output[(i, doc)] = similarity 27 | 28 | print(output) 29 | 30 | 31 | sparse_sim({13: [14, 15, 100, 9, 3], 16: [32, 1, 9, 3, 5], 19: [15, 29, 2, 6, 8, 7], 24: [7, 10]}) 32 | -------------------------------------------------------------------------------- /hard_problems/word_distance.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | def find_word_distance(words, word1, word2): 5 | word_dict = defaultdict(list) 6 | for i in range(len(words)): 7 | word_dict[words[i]].append(i) 8 | 9 | word1_list = word_dict[word1] 10 | word2_list = word_dict[word2] 11 | 12 | i = 0 13 | j = 0 14 | 15 | min_dist = 99999999 16 | while i < len(word1_list) and j < len(word2_list): 17 | dist1 = word1_list[i] 18 | dist2 = word2_list[j] 19 | 20 | if dist1 > dist2: 21 | dist = dist1 - dist2 - 1 22 | j += 1 23 | else: 24 | dist = dist2 - dist1 - 1 25 | i += 1 26 | 27 | if dist < min_dist: 28 | min_dist = dist 29 | 30 | if min_dist < 0: 31 | min_dist = 0 32 | 33 | return min_dist 34 | 35 | 36 | print(find_word_distance(['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'e', 'k', 'j', 'n', 'w', 'e', 'f', 'n'], 'e', 'f')) 37 | -------------------------------------------------------------------------------- /moderate_problems/pond_size.py: -------------------------------------------------------------------------------- 1 | def get_pond_size(mat): 2 | row = len(mat) 3 | col = len(mat[0]) 4 | visited = [[0 for i in range(col)] for j in range(row)] 5 | final_sizes = list() 6 | for i in range(0, row): 7 | for j in range(0, col): 8 | if mat[i][j] == 0: 9 | if not visited[i][j]: 10 | pond_size = compute_size(mat, i, j, visited) 11 | final_sizes.append(pond_size) 12 | return final_sizes 13 | 14 | 15 | def compute_size(mat, row, col, visited, size=1): 16 | if row < 0 or col < 0 or row >= len(mat) or col >= len(mat[0]): 17 | return 0 18 | if visited[row][col] or mat[row][col]: 19 | return 0 20 | visited[row][col] = 1 21 | for i in range(-1, 2): 22 | for j in range(-1, 2): 23 | size += compute_size(mat, row+i, col+j, visited, size=size) 24 | return size 25 | 26 | 27 | print(get_pond_size([[0, 2, 1, 0], [0, 1, 0, 1], [1, 1, 0, 1], [0, 1, 0, 1]])) 28 | -------------------------------------------------------------------------------- /trees/minimal_tree.py: -------------------------------------------------------------------------------- 1 | # Given sorted array create a bst with minimal height 2 | 3 | 4 | from binary_tree import BinarySearchTree 5 | 6 | 7 | bst_obj = BinarySearchTree() 8 | 9 | 10 | def minimal_tree(array): 11 | start = 0 12 | end = len(array) - 1 13 | mid = int((start+end)/2) 14 | 15 | left_array = array[start:mid] 16 | right_array = array[mid+1:end+1] 17 | 18 | if start > end: 19 | return 20 | 21 | if left_array == right_array: 22 | # print("Found 1") 23 | return array[mid] 24 | 25 | bst_obj.insert(array[mid]) 26 | print("Root") 27 | print(array[mid]) 28 | left_child = minimal_tree(left_array) 29 | right_child = minimal_tree(right_array) 30 | print("Left and Right children") 31 | print(left_child, right_child) 32 | if left_child: 33 | bst_obj.insert(left_child) 34 | if right_child: 35 | bst_obj.insert(right_child) 36 | 37 | 38 | minimal_tree([1, 2, 3, 4, 5, 6, 7, 8, 9]) 39 | bst_obj.in_order(bst_obj.root) 40 | -------------------------------------------------------------------------------- /recursion_and_dp/magic_index.py: -------------------------------------------------------------------------------- 1 | # sorted array with distinct values 2 | def find_magic_index(arr, start, end): 3 | mid = (start + end) // 2 4 | 5 | if arr[mid] == mid: 6 | return mid 7 | elif arr[mid] < mid: 8 | index = find_magic_index(arr, mid + 1, end) 9 | else: 10 | index = find_magic_index(arr, start, mid - 1) 11 | 12 | return index 13 | 14 | 15 | # sorted array with non distinct values 16 | def magic_index(arr, start, end): 17 | if end < start: 18 | return -1 19 | mid = (start + end) // 2 20 | 21 | if arr[mid] == mid: 22 | return mid 23 | 24 | left_index = min(mid - 1, arr[mid]) 25 | left = magic_index(arr, start, left_index) 26 | if left >= 0: 27 | return left 28 | 29 | right_index = max(arr[mid], mid+1) 30 | right = magic_index(arr, right_index, end) 31 | return right 32 | 33 | 34 | # print(find_magic_index([-10, -5, 0, 3, 5, 6, 7, 8, 9], 0, 9)) 35 | print(magic_index([-1, 0, 1, 2, 6, 6, 6, 7, 8, 9], 0, 9)) 36 | -------------------------------------------------------------------------------- /recursion_and_dp/eight_queens.py: -------------------------------------------------------------------------------- 1 | GRID_SIZE = 4 2 | 3 | 4 | def place_queens(row, board): 5 | if row == GRID_SIZE: 6 | return True 7 | else: 8 | for col in range(GRID_SIZE): 9 | if check_valid(board, row, col): 10 | board[row][col] = 1 11 | if place_queens(row+1, board): 12 | return True 13 | board[row][col] = 0 14 | return False 15 | 16 | 17 | def check_valid(board, row, col): 18 | for i in range(row): 19 | if board[i][col] == 1: 20 | return False 21 | 22 | for i, j in zip(range(row, -1, -1), 23 | range(col, -1, -1)): 24 | if board[i][j] == 1: 25 | return False 26 | 27 | for i, j in zip(range(row, GRID_SIZE, 1), 28 | range(col, -1, -1)): 29 | if board[i][j] == 1: 30 | return False 31 | 32 | return True 33 | 34 | 35 | board = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] 36 | place_queens(0, board) 37 | print(board) 38 | -------------------------------------------------------------------------------- /trees/validate_bst.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | 4 | def check_bst(root, min, max): 5 | if not root: 6 | return True 7 | 8 | if (min and root.data <= min) or (max and root.data >= max): 9 | return False 10 | 11 | if not check_bst(root.left_child, min, root.data) or not check_bst(root.right_child, root.data, max): 12 | return False 13 | 14 | return True 15 | 16 | 17 | if __name__ == "__main__": 18 | bst_obj = BinarySearchTree() 19 | bst_obj.level_order_insert(20) 20 | bst_obj.level_order_insert(30) 21 | bst_obj.level_order_insert(10) 22 | bst_obj.level_order_insert(15) 23 | bst_obj.level_order_insert(12) 24 | bst_obj.level_order_insert(6) 25 | # bst_obj.insert(2) 26 | # bst_obj.insert(40) 27 | # bst_obj.insert(50) 28 | # bst_obj.insert(28) 29 | # bst_obj.insert(25) 30 | bst_obj.in_order(bst_obj.root) 31 | is_bst = check_bst(bst_obj.root, None, None) 32 | if is_bst: 33 | print("Is a BST") 34 | else: 35 | print("Is not a BST") 36 | -------------------------------------------------------------------------------- /moderate_problems/living_people.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | def __init__(self, birth, death): 3 | self.birth = birth 4 | self.death = death 5 | 6 | 7 | people = [Person(1901, 1910), Person(1910, 1947), Person(1905, 1919), Person(1907, 1988), Person(1902, 1975)] 8 | min_year = 1900 9 | max_year = 2000 10 | 11 | 12 | def get_population_deltas(people, min_year, max_year): 13 | deltas = [0] * (max_year - min_year + 1) 14 | for person in people: 15 | deltas[person.birth - min_year] += 1 16 | deltas[person.death - min_year + 1] -= 1 17 | return deltas 18 | 19 | 20 | def find_max_year(deltas): 21 | import sys 22 | max_val = -sys.maxsize 23 | currently_alive = 0 24 | for i in range(len(deltas)): 25 | currently_alive += deltas[i] 26 | if currently_alive > max_val: 27 | max_val = currently_alive 28 | year = i + min_year 29 | return year 30 | 31 | 32 | population_deltas = get_population_deltas(people, min_year, max_year) 33 | year = find_max_year(population_deltas) 34 | print(year) 35 | -------------------------------------------------------------------------------- /hard_problems/volume_of_histogram.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def find_volume(arr): 5 | left_max = [] 6 | right_max = [] 7 | min_val = [] 8 | delta = [] 9 | 10 | for i in range(len(arr)): 11 | if not left_max: 12 | left_max.append(arr[i]) 13 | else: 14 | if left_max[i-1] < arr[i]: 15 | left_max.append(arr[i]) 16 | else: 17 | left_max.append(left_max[i-1]) 18 | 19 | for i in range(len(arr)-1, -1, -1): 20 | if not right_max: 21 | right_max.append(arr[i]) 22 | else: 23 | if right_max[0] < arr[i]: 24 | right_max.insert(0, arr[i]) 25 | else: 26 | right_max.insert(0, right_max[0]) 27 | 28 | for i in range(len(arr)): 29 | min_val.insert(i, min(left_max[i], right_max[i])) 30 | 31 | for i in range(len(arr)): 32 | delta.insert(i, min_val[i] - arr[i]) 33 | 34 | return sum(delta) 35 | 36 | 37 | print(find_volume([0, 0, 4, 0, 0, 6, 0, 0, 3, 0, 8, 0, 2, 0, 5, 2, 0, 3, 0, 0])) 38 | -------------------------------------------------------------------------------- /arrays_and_strings/zero_matrix.py: -------------------------------------------------------------------------------- 1 | row_has_zero = False 2 | col_has_zero = False 3 | 4 | mat = [[1, 2, 3, 4], [5, 6, 7, 0], [3, 9, 0, 1]] 5 | 6 | 7 | def nullify_row_matrix(row): 8 | for j in range(len(mat[0])): 9 | mat[row][j] = 0 10 | 11 | 12 | def nullify_col_matrix(col): 13 | for i in range(len(mat)): 14 | mat[i][col] = 0 15 | 16 | 17 | for j in range(len(mat[0])): 18 | if mat[0][j] == 0: 19 | row_has_zero = True 20 | break 21 | 22 | for i in range(len(mat)): 23 | if mat[i][0] == 0: 24 | col_has_zero = True 25 | break 26 | 27 | for i in range(1, len(mat)): 28 | for j in range(1, len(mat[0])): 29 | if mat[i][j] == 0: 30 | mat[i][0] = 0 31 | mat[0][j] = 0 32 | 33 | for i in range(1, len(mat)): 34 | if mat[i][0] == 0: 35 | nullify_row_matrix(i) 36 | 37 | for j in range(1, len(mat[0])): 38 | if mat[0][j] == 0: 39 | nullify_col_matrix(j) 40 | 41 | if row_has_zero: 42 | nullify_row_matrix(0) 43 | if col_has_zero: 44 | nullify_col_matrix(0) 45 | 46 | print(mat) 47 | -------------------------------------------------------------------------------- /moderate_problems/operations.py: -------------------------------------------------------------------------------- 1 | def negate(a): 2 | num = 0 3 | neg = -1 if a > 0 else 1 4 | neg_num = 0 if a > 0 else 1 5 | while a != 0: 6 | if a + neg < 0 and not neg_num: 7 | neg = -1 8 | elif a + neg > 0 and neg_num: 9 | neg = 1 10 | a += neg 11 | num += neg 12 | neg += neg 13 | return num 14 | 15 | 16 | def subtract(a, b): 17 | return a + negate(b) 18 | 19 | 20 | def multiply(a, b): 21 | if a < b: 22 | return multiply(b, a) 23 | 24 | sum = 0 25 | for i in range(abs(b)): 26 | sum += abs(a) 27 | 28 | if b < 0: 29 | sum = negate(sum) 30 | 31 | return sum 32 | 33 | 34 | def divide(a, b): 35 | if b == 0: 36 | raise ZeroDivisionError 37 | 38 | count = 0 39 | sum = 0 40 | while sum != abs(a): 41 | if sum > abs(a): 42 | break 43 | sum += abs(b) 44 | count += 1 45 | 46 | if a < 0 and b > 0 or a > 0 and b < 0: 47 | count = negate(count) 48 | return count 49 | 50 | 51 | print(divide(100, -2)) 52 | -------------------------------------------------------------------------------- /trees/check_balanced.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | 4 | def check_balanced(tree): 5 | if not tree: 6 | return 0 7 | left_tree_depth = check_balanced(tree.left_child) 8 | if left_tree_depth == -1: 9 | return -1 10 | right_tree_depth = check_balanced(tree.right_child) 11 | if right_tree_depth == -1: 12 | return -1 13 | if abs(left_tree_depth - right_tree_depth) > 1: 14 | return -1 15 | else: 16 | return max(left_tree_depth, right_tree_depth) + 1 17 | 18 | 19 | if __name__ == "__main__": 20 | bst_obj = BinarySearchTree() 21 | bst_obj.insert(20) 22 | bst_obj.insert(30) 23 | bst_obj.insert(10) 24 | bst_obj.insert(15) 25 | bst_obj.insert(12) 26 | bst_obj.insert(6) 27 | bst_obj.insert(2) 28 | bst_obj.insert(40) 29 | bst_obj.insert(50) 30 | bst_obj.insert(28) 31 | bst_obj.insert(25) 32 | # bst_obj.in_order(bst_obj.root) 33 | tree_height = check_balanced(bst_obj.root) 34 | if tree_height == -1: 35 | print("Not balanced") 36 | else: 37 | print("Balanced") 38 | -------------------------------------------------------------------------------- /arrays_and_strings/one_away.py: -------------------------------------------------------------------------------- 1 | def one_edit_away(str1, str2): 2 | if len(str1) == len(str2): 3 | return one_replace_away(str1, str2) 4 | elif len(str1) - 1 == len(str2): 5 | return one_insert_away(str2, str1) 6 | elif len(str1) + 1 == len(str2): 7 | return one_insert_away(str1, str2) 8 | else: 9 | return False 10 | 11 | 12 | def one_insert_away(str1, str2): 13 | found_diff = False 14 | i = 0 15 | j = 0 16 | while i < len(str1) and j < len(str2): 17 | if str1[i] != str2[j]: 18 | if found_diff: 19 | return False 20 | found_diff = True 21 | j += 1 22 | else: 23 | i += 1 24 | j += 1 25 | return True 26 | 27 | 28 | def one_replace_away(str1, str2): 29 | found_diff = False 30 | for i in range(len(str1)): 31 | if str1[i] != str2[i]: 32 | if found_diff: 33 | return False 34 | found_diff = True 35 | return True 36 | 37 | 38 | if __name__ == "__main__": 39 | print(one_edit_away("apple", "appple")) 40 | 41 | -------------------------------------------------------------------------------- /moderate_problems/pairs_with_sum.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | # Approach 1 4 | def find_pairs(arr, target): 5 | hash_table = defaultdict(lambda: 0) 6 | for i in range(len(arr)): 7 | hash_table[arr[i]] += 1 8 | 9 | final_pairs = list() 10 | for i in range(len(arr)): 11 | comp = target - arr[i] 12 | if hash_table[comp]: 13 | final_pairs.append((arr[i], comp)) 14 | hash_table[comp] = 0 15 | hash_table[arr[i]] = 0 16 | return final_pairs 17 | 18 | 19 | # Approach 2: 20 | def find_pairs_with_sum(arr, target): 21 | arr.sort() 22 | i = 0 23 | j = len(arr) - 1 24 | pairs = list() 25 | while i < j: 26 | if arr[i] + arr[j] < target: 27 | i += 1 28 | elif arr[i] + arr[j] > target: 29 | j -= 1 30 | else: 31 | pairs.append((arr[i], arr[j])) 32 | i += 1 33 | j -= 1 34 | return pairs 35 | 36 | 37 | print(find_pairs_with_sum([-1, 2, 5, -10, 20, 8, 11, 15, 5], 10)) 38 | # print(find_pairs([-1, 2, 5, -10, 20, 8, 11, 15, 5], 10)) 39 | -------------------------------------------------------------------------------- /linked_list/loop_detection.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def detect_loop(llist): 5 | found = False 6 | if llist is None: 7 | return None 8 | slow_ptr = llist 9 | fast_ptr = llist 10 | while fast_ptr and fast_ptr.next: 11 | slow_ptr = slow_ptr.next 12 | fast_ptr = fast_ptr.next.next 13 | if slow_ptr is fast_ptr: 14 | found = True 15 | break 16 | if found: 17 | slow_ptr = llist 18 | while slow_ptr != fast_ptr: 19 | slow_ptr = slow_ptr.next 20 | fast_ptr = fast_ptr.next 21 | return fast_ptr 22 | else: 23 | return None 24 | 25 | 26 | if __name__ == "__main__": 27 | llist = LinkedList() 28 | llist.insertatend(4) 29 | llist.insertatend(7) 30 | llist.insertatend(1) 31 | llist.insertatend(2) 32 | llist.insertatend(3) 33 | llist.tail.next = llist.head.next.next 34 | node = detect_loop(llist.head) 35 | if node: 36 | print("Loop present") 37 | print(node.data) 38 | else: 39 | print("Loop not present") 40 | -------------------------------------------------------------------------------- /moderate_problems/master_mind.py: -------------------------------------------------------------------------------- 1 | def result(guess, soln): 2 | guess_dict = {} 3 | for i in range(len(guess)): 4 | item = guess[i] 5 | if item in guess_dict: 6 | guess_dict[item].append(i) 7 | else: 8 | guess_dict[item] = [i] 9 | 10 | soln_dict = {} 11 | for i in range(len(soln)): 12 | item = soln[i] 13 | if item in soln_dict: 14 | soln_dict[item].append(i) 15 | else: 16 | soln_dict[item] = [i] 17 | 18 | hits = 0 19 | pseudo_hits = 0 20 | for item in soln_dict: 21 | soln_index = soln_dict[item] 22 | guess_index = guess_dict.get(item, None) 23 | if guess_index: 24 | match_index = set(soln_index).intersection(set(guess_index)) 25 | if match_index: 26 | hits += len(match_index) 27 | if len(match_index) < len(guess_index): 28 | pseudo_hits += len(guess_index) - len(match_index) 29 | else: 30 | pseudo_hits += len(guess_index) 31 | 32 | print(hits) 33 | print(pseudo_hits) 34 | 35 | 36 | result("RGGB", "RGGB") 37 | -------------------------------------------------------------------------------- /hard_problems/missing_number.py: -------------------------------------------------------------------------------- 1 | # we can find missing number by adding all the numbers of the list from 0 to n and 2 | # computing n*(n+1)/2 then subtracting from this number to get the missing number 3 | # TC: O(n * length(n)) = O(n log n) 4 | 5 | # if n % 2 == 0 count(0s) = 1 + count(1s) 6 | # if n % 2 == 1 count(0s) = count(1s) 7 | # if missing number v is odd and LSB i (v) = 1: count(0s) > count(1s) for even n 8 | # if missing number v is even and LSB i (v) = 0: count(0s) = count(1s) for even n 9 | # if missing number v is odd and LSB i (v) = 1: count(0s) > count(1s) for odd n 10 | # if missing number v is even and LSB i (v) = 0: count(0s) < count(1s) for odd n 11 | 12 | 13 | def find_missing(arr, col): 14 | zero_bits = [] 15 | one_bits = [] 16 | if col >= INTEGER.SIZE: 17 | return 0 18 | for bit in arr: 19 | if bit.fetch(col) == 0: 20 | zero_bits.append(bit) 21 | else: 22 | one_bits.append(bit) 23 | 24 | if len(zero_bits) <= len(one_bits): 25 | v = find_missing(arr, col+1) 26 | return v << 1 | 0 27 | else: 28 | v = find_missing(arr, col + 1) 29 | return v << 1 | 1 30 | -------------------------------------------------------------------------------- /moderate_problems/tic_tac_toe.py: -------------------------------------------------------------------------------- 1 | class TicTacToe: 2 | def __init__(self, board_size): 3 | self.board_size = board_size 4 | self.row = [0] * board_size 5 | self.col = [0] * board_size 6 | self.diag = [0] 7 | self.anti_diag = [0] 8 | 9 | def has_won(self, row, col): 10 | self.row[row] += 1 11 | self.col[col] += 1 12 | if row == col: 13 | self.diag[0] += 1 14 | if row + col == self.board_size-1: 15 | self.anti_diag[0] += 1 16 | 17 | if self.row[row] == self.board_size or self.col[col] == self.board_size or \ 18 | self.diag[0] == self.board_size or self.anti_diag[0] == self.board_size: 19 | return True 20 | else: 21 | return False 22 | 23 | 24 | if __name__ == "__main__": 25 | player1 = TicTacToe(3) 26 | player2 = TicTacToe(3) 27 | player1.has_won(1, 1) 28 | player2.has_won(1, 2) 29 | player1.has_won(0, 0) 30 | player2.has_won(2, 2) 31 | player1.has_won(0, 2) 32 | player2.has_won(0, 1) 33 | resp = player1.has_won(2, 0) 34 | if resp: 35 | print("WON") 36 | else: 37 | print("Not won") 38 | -------------------------------------------------------------------------------- /linked_list/kth_to_last.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def kth_from_last(llist, k): 5 | if llist is None: 6 | return 1, False 7 | 8 | ret, found = kth_from_last(llist.next, k) 9 | # print(ret) 10 | if ret == k and not found: 11 | return llist.data, True 12 | if not found: 13 | return ret + 1, False 14 | else: 15 | return ret, True 16 | 17 | 18 | def two_pointer(llist, k): 19 | ptr1 = llist 20 | ptr2 = llist 21 | for i in range(k-1): 22 | ptr2 = ptr2.next 23 | 24 | while ptr2.next != None: 25 | ptr1 = ptr1.next 26 | ptr2 = ptr2.next 27 | 28 | return ptr1.data 29 | 30 | 31 | if __name__ == "__main__": 32 | llist = LinkedList() 33 | llist.push(4) 34 | llist.push(7) 35 | llist.push(1) 36 | llist.push(2) 37 | llist.push(3) 38 | llist.push(5) 39 | llist.push(6) 40 | llist.push(8) 41 | llist.traverse() 42 | result = kth_from_last(llist.head, 3) 43 | print("\n") 44 | print("Kth FROM LAST By recursion:") 45 | print(result[0]) 46 | result = two_pointer(llist.head, 3) 47 | print("Kth from Last By Two pointer:") 48 | print(result) 49 | -------------------------------------------------------------------------------- /hard_problems/baby_names.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | names_dict = {'John': 15, 'Jon': 12, 'Chris': 13, 'Kris': 4, 'Christopher': 19} 4 | final_dict = defaultdict(lambda: 0) 5 | 6 | 7 | class Graph: 8 | def __init__(self): 9 | self.graph = defaultdict(list) 10 | self.visited = {} 11 | 12 | def add_edge(self, a, b): 13 | self.graph[a].append(b) 14 | self.graph[b].append(a) 15 | if a not in self.visited: 16 | self.visited[a] = False 17 | if b not in self.visited: 18 | self.visited[b] = False 19 | 20 | def dfs(self, name, main_name): 21 | self.visited[name] = True 22 | if name in names_dict: 23 | freq = names_dict[name] 24 | final_dict[main_name] += freq 25 | 26 | for item in self.graph[name]: 27 | if not self.visited[item]: 28 | self.dfs(item, main_name) 29 | 30 | 31 | g = Graph() 32 | g.add_edge('Jon', 'John') 33 | g.add_edge('John', 'Johnny') 34 | g.add_edge('Chris', 'Kris') 35 | g.add_edge('Chris', 'Christopher') 36 | for item in g.graph: 37 | if g.visited[item]: 38 | continue 39 | final_dict[item] = 0 40 | g.dfs(item, item) 41 | 42 | print(final_dict) 43 | -------------------------------------------------------------------------------- /moderate_problems/best_line.py: -------------------------------------------------------------------------------- 1 | class Points: 2 | def __init__(self, x, y): 3 | self.x = x 4 | self.y = y 5 | 6 | 7 | def find_best_line(points): 8 | slope_dict = {} 9 | for i in range(len(points)): 10 | for j in range(i+1, len(points)): 11 | x1 = points[i].x 12 | y1 = points[i].y 13 | x2 = points[j].x 14 | y2 = points[j].y 15 | if x2-x1 == 0: 16 | slope = "infinite" 17 | else: 18 | slope = (y2 - y1) / (x2 - x1) 19 | if slope in slope_dict: 20 | slope_dict[slope].extend([(x1, y1), (x2, y2)]) 21 | else: 22 | slope_dict[slope] = [(x1, y1), (x2, y2)] 23 | print(slope_dict) 24 | max_count = 0 25 | for item in slope_dict: 26 | count = len(set(slope_dict[item])) 27 | if count > max_count: 28 | max_count = count 29 | best_slope = item 30 | 31 | p1 = slope_dict[best_slope][0] 32 | y_intercept = p1[1] - best_slope * p1[0] 33 | best_line = str(best_slope) + 'x' + ' + ' + str(y_intercept) 34 | print(best_line) 35 | 36 | 37 | find_best_line([Points(1, 2), Points(1, 1), Points(2, 3), Points(3, 4), Points(5, 6), Points(3, 3)]) 38 | -------------------------------------------------------------------------------- /sorting/merge_sort.py: -------------------------------------------------------------------------------- 1 | # Time complexity: nlogn 2 | 3 | 4 | def merge_sort(arr): 5 | if len(arr) > 1: 6 | 7 | mid = len(arr) // 2 8 | left_array = arr[:mid] 9 | right_array = arr[mid:] 10 | print(mid) 11 | print("-------------") 12 | print(left_array) 13 | print("===================") 14 | print(right_array) 15 | merge_sort(left_array) 16 | merge_sort(right_array) 17 | 18 | i = j = k = 0 19 | print(f"length of left_array: {len(left_array)}") 20 | print(f"length of right_array: {len(right_array)}") 21 | while i < len(left_array) and j < len(right_array): 22 | print(i, j) 23 | if left_array[i] > right_array[j]: 24 | arr[k] = right_array[j] 25 | j += 1 26 | else: 27 | arr[k] = left_array[i] 28 | i += 1 29 | k += 1 30 | 31 | while i < len(left_array): 32 | arr[k] = left_array[i] 33 | i += 1 34 | k += 1 35 | 36 | while j < len(right_array): 37 | arr[k] = right_array[j] 38 | j += 1 39 | k += 1 40 | 41 | print(arr) 42 | 43 | 44 | merge_sort([12, 11, 13, 5, 6, 7]) 45 | -------------------------------------------------------------------------------- /hard_problems/smallest_k.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | 4 | def find_smallest(arr, k): 5 | heapq.heapify(arr) 6 | print(heapq.nsmallest(k, arr)) 7 | 8 | # create max heap of k elements by iterating over array. For each element if element is less than largest 9 | # element remove that element and insert the smaller element by the end of the loop the heap will 10 | # have smallest k elements 11 | 12 | # Approach 2 13 | 14 | 15 | def find_k_smallest(arr, k): 16 | quick_sort(arr, 0, len(arr) - 1, k) 17 | 18 | 19 | def partition(arr, low, high): 20 | pivot = arr[high] 21 | i = low - 1 22 | for j in range(low, high): 23 | if arr[j] < pivot: 24 | i += 1 25 | arr[i], arr[j] = arr[j], arr[i] 26 | 27 | arr[i + 1], arr[high] = arr[high], arr[i + 1] 28 | return i + 1 29 | 30 | 31 | def quick_sort(arr, low, high, k): 32 | if low < high: 33 | partition_index = partition(arr, low, high) 34 | left_size = partition_index - low + 1 35 | if left_size == k: 36 | return max(arr[low:partition_index+1]) 37 | elif k < left_size: 38 | return quick_sort(arr, low, partition_index - 1, k) 39 | else: 40 | return quick_sort(arr, partition_index + 1, high, k) 41 | -------------------------------------------------------------------------------- /hashing.py: -------------------------------------------------------------------------------- 1 | # global hash_table 2 | 3 | 4 | class HashTable: 5 | def __init__(self, size): 6 | global hash_table 7 | hash_table = [[] for i in range(size)] 8 | 9 | def hash_func(self, key): 10 | return len(key) % 100 11 | 12 | def insert(self, key): 13 | index = self.hash_func(key) 14 | hash_table[index].append(key) 15 | 16 | def search(self, key): 17 | index = self.hash_func(key) 18 | for i in hash_table[index]: 19 | if i == key: 20 | return True 21 | return False 22 | 23 | def delete(self, key): 24 | index = self.hash_func(key) 25 | for ind, value in enumerate(hash_table[index]): 26 | if value == key: 27 | hash_table[index].remove(value) 28 | return True 29 | return False 30 | 31 | 32 | if __name__ == "__main__": 33 | hash_table_obj = HashTable(100) 34 | hash_table_obj.insert("Testing") 35 | hash_table_obj.insert("testing") 36 | print(hash_table) 37 | search_status = hash_table_obj.search("Testing") 38 | print("Found: {}".format(search_status)) 39 | delete_status = hash_table_obj.delete("testing") 40 | print("Deleted: {}".format(delete_status)) 41 | print(hash_table) 42 | -------------------------------------------------------------------------------- /trees/first_common_ancestor.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | node1_present = False 4 | node2_present = False 5 | 6 | 7 | def first_common_ancestor(root, node1, node2): 8 | if not root: 9 | return None 10 | if root.data == node1: 11 | global node1_present 12 | node1_present = True 13 | return root 14 | if root.data == node2: 15 | global node2_present 16 | node2_present = True 17 | return root 18 | 19 | left_fca = first_common_ancestor(root.left_child, node1, node2) 20 | right_fca = first_common_ancestor(root.right_child, node1, node2) 21 | 22 | if left_fca and right_fca: 23 | return root 24 | 25 | return left_fca if left_fca else right_fca 26 | 27 | 28 | if __name__ == "__main__": 29 | bst_obj = BinarySearchTree() 30 | bst_obj.level_order_insert(20) 31 | bst_obj.level_order_insert(30) 32 | bst_obj.level_order_insert(10) 33 | bst_obj.level_order_insert(15) 34 | bst_obj.level_order_insert(12) 35 | bst_obj.level_order_insert(6) 36 | node1 = 30 37 | node2 = 7 38 | fca = first_common_ancestor(bst_obj.root, node1, node2) 39 | if node1_present and node2_present: 40 | print(f"FCA: {fca.data}") 41 | else: 42 | print("FCA: None") 43 | -------------------------------------------------------------------------------- /recursion_and_dp/boolean_parenthesization.py: -------------------------------------------------------------------------------- 1 | def get_number_of_ways(S, i, j, isTrue): 2 | if i > j: 3 | return 0 4 | if i == j: 5 | if isTrue: 6 | return 'T' == S[i] 7 | else: 8 | return 'F' == S[i] 9 | 10 | ans = 0 11 | for k in range(i+1, j): 12 | left_true = get_number_of_ways(S, i, k-1, True) 13 | left_false = get_number_of_ways(S, i, k-1, False) 14 | right_true = get_number_of_ways(S, k+1, j, True) 15 | right_false = get_number_of_ways(S, k+1, j, False) 16 | 17 | if S[k] == '^': 18 | if isTrue: 19 | ans += (left_true * right_false) + (left_false * right_true) 20 | else: 21 | ans += (left_true * right_true) + (left_false * right_false) 22 | elif S[k] == '|': 23 | if isTrue: 24 | ans += (left_true * right_false) + (left_false * right_true) + (right_true * left_true) 25 | else: 26 | ans += left_false * right_false 27 | elif S[k] == '&': 28 | if isTrue: 29 | ans += left_true * right_true 30 | else: 31 | ans += (left_true * right_false) + (left_false * right_false) + (left_false * right_true) 32 | return ans 33 | 34 | -------------------------------------------------------------------------------- /arrays_and_strings/is_unique.py: -------------------------------------------------------------------------------- 1 | def unique_char_with_ds(s): 2 | char_flag = dict() 3 | 4 | if len(s) > 128: 5 | return False 6 | 7 | for c in s: 8 | if c in char_flag: 9 | return False 10 | else: 11 | char_flag[c] = True 12 | return True 13 | 14 | 15 | def unique_char_without_ds(s): 16 | sorted_s = ''.join(sorted(s)) 17 | for i in range(len(sorted_s) - 1): 18 | if sorted_s[i] == sorted_s[i+1]: 19 | return False 20 | return True 21 | 22 | 23 | def unique_char_using_bits(s): 24 | unique = 0 25 | for chr in s: 26 | val = ord(chr) - ord('a') 27 | if (unique & (1 << val)) > 0: 28 | return False 29 | unique = unique or (1 << val) 30 | return True 31 | 32 | 33 | # TC1 assuming only alphabets 34 | s1 = "ajabckajcaoichakcjakc" 35 | 36 | # TC2 assuming we also have special chars 37 | s2 = "alcacah9823yr2fhcck.cmalckjalcj" 38 | 39 | s3 = "abcdefghijklmnopqrstuvwxyz" 40 | 41 | s4 = "abcdefghijklmnopqrstuvwxyz (" 42 | 43 | result = unique_char_with_ds(s3) 44 | result2 = unique_char_without_ds(s3) 45 | result3 = unique_char_using_bits(s3) 46 | print("result with ds: {}".format(str(result))) 47 | print("result without ds: {}".format(str(result2))) 48 | print("result using bits: {}".format(str(result3))) 49 | -------------------------------------------------------------------------------- /hard_problems/max_submatrix.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def find_max_submatrix(mat): 5 | max_sum = -sys.maxsize 6 | max_left = 0 7 | max_right = 0 8 | max_up = 0 9 | max_down = 0 10 | for i in range(len(mat)): 11 | partial_sum = [0 for n in range(len(mat[0]))] 12 | for j in range(i, len(mat)): 13 | for k in range(len(mat[0])): 14 | partial_sum[k] += mat[j][k] 15 | running_sum, start, end = max_sum_subarray(partial_sum) 16 | if max_sum < running_sum: 17 | max_sum = running_sum 18 | max_left = i 19 | max_right = j 20 | max_up = start 21 | max_down = end 22 | print(max_left, max_right, max_up, max_down) 23 | 24 | 25 | def max_sum_subarray(arr): 26 | max_so_far = 0 27 | max_ending_here = 0 28 | start_index = 0 29 | end_index = 0 30 | for i in range(len(arr)): 31 | max_ending_here += arr[i] 32 | if max_ending_here < 0: 33 | max_ending_here = 0 34 | start_index = i+1 35 | 36 | if max_so_far < max_ending_here: 37 | max_so_far = max_ending_here 38 | end_index = i 39 | return max_so_far, start_index, end_index 40 | 41 | 42 | find_max_submatrix([[1, 2, 3], [-1, 2, 4], [-5, 7, 8]]) 43 | -------------------------------------------------------------------------------- /linked_list/intersection.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def find_intersection(l1, l2, l1_len, l2_len): 5 | if l1_len > l2_len: 6 | ptr_jump = l1_len - l2_len 7 | for i in range(ptr_jump): 8 | l1.head = l1.head.next 9 | elif l2_len > l1_len: 10 | ptr_jump = l2_len - l1_len 11 | for i in range(ptr_jump): 12 | l2.head = l2.head.next 13 | else: 14 | ptr_jump = 0 15 | 16 | while l1.head and l2.head: 17 | if l1.head == l2.head: 18 | return True 19 | l1.head = l1.head.next 20 | l2.head = l2.head.next 21 | return False 22 | 23 | 24 | if __name__ == "__main__": 25 | llist = LinkedList() 26 | llist.insertatend(4) 27 | llist.insertatend(7) 28 | llist.insertatend(1) 29 | llist.insertatend(2) 30 | 31 | llist2 = LinkedList() 32 | llist2.insertatend(5) 33 | llist2.insertatend(2) 34 | llist2.insertatend(1) 35 | llist2.insertatend(7) 36 | llist2.tail.next = llist.head.next.next 37 | llist2.tail = llist.head.next.next 38 | llist2.tail.next = llist.head.next.next.next 39 | llist2.tail = llist.head.next.next.next 40 | 41 | out = find_intersection(llist, llist2, 4, 6) 42 | if out: 43 | print("Intersection exists") 44 | else: 45 | print("No intersection found") 46 | -------------------------------------------------------------------------------- /hard_problems/continuous_median.py: -------------------------------------------------------------------------------- 1 | from heapq import heappop, heappush, heapify 2 | 3 | 4 | class Median: 5 | def __init__(self): 6 | self.max_heap = [] 7 | self.min_heap = [] 8 | heapify(self.min_heap) 9 | heapify(self.max_heap) 10 | 11 | def get_median(self): 12 | if len(self.max_heap) == len(self.min_heap): 13 | max_ele = heappop(self.max_heap) 14 | min_ele = heappop(self.min_heap) 15 | return (max_ele + min_ele) // 2 16 | else: 17 | return -1 * heappop(self.max_heap) 18 | 19 | def add_number(self, random_number): 20 | if len(self.max_heap) == len(self.min_heap): 21 | if self.min_heap and random_number > self.min_heap[0]: 22 | heappush(self.max_heap, -1 * heappop(self.min_heap)) 23 | heappush(self.min_heap, random_number) 24 | else: 25 | heappush(self.max_heap, -1 * random_number) 26 | else: 27 | if random_number < -1 * self.max_heap[0]: 28 | heappush(self.min_heap, -1 * heappop(self.max_heap)) 29 | heappush(self.max_heap, -1 * random_number) 30 | else: 31 | heappush(self.min_heap, random_number) 32 | 33 | 34 | m = Median() 35 | m.add_number(4) 36 | m.add_number(3) 37 | m.add_number(6) 38 | print(m.get_median()) 39 | -------------------------------------------------------------------------------- /linked_list/palindrome.py: -------------------------------------------------------------------------------- 1 | # Approach 1: Reverse the linked list and compare the first half of original list with reversed list. 2 | # If it matches then it is a palindrome otherwise not 3 | 4 | # Approach 2: Push first half elements of linked list to a stack if we know the size. 5 | # If we do not know the size use fast and slow pointers to insert first half of linked list to stack 6 | # and then pop out elements from stack and compare with second half of linked list 7 | # to check whether the string is palindrome or not 8 | 9 | # Approach 3: 10 | from linked_list_operations import LinkedList 11 | 12 | 13 | def check_palindrome(head, list_length): 14 | if head is None or list_length <= 0: 15 | return head, True 16 | elif list_length == 1: 17 | return head.next, True 18 | 19 | node, palindrome_flag = check_palindrome(head.next, list_length - 2) 20 | 21 | if not node or not palindrome_flag: 22 | return node, False 23 | 24 | if head.data != node.data: 25 | return node.next, False 26 | else: 27 | return node.next, True 28 | 29 | 30 | if __name__ == '__main__': 31 | llist = LinkedList() 32 | llist.push(1) 33 | llist.push(2) 34 | llist.push(0) 35 | llist.push(2) 36 | llist.push(1) 37 | # llist.push(3) 38 | res, flag = check_palindrome(llist.head, 5) 39 | if flag: 40 | print("Is palindrome") 41 | else: 42 | print("Not palindrome") 43 | -------------------------------------------------------------------------------- /hard_problems/multi_search.py: -------------------------------------------------------------------------------- 1 | def multi_search(big_str, small_str_list): 2 | t = Trie() 3 | final_out = [] 4 | for word in small_str_list: 5 | t.insert(word) 6 | 7 | for i in range(len(big_str)): 8 | for j in range(i, len(big_str)): 9 | search_word = big_str[i:j+1] 10 | if t.search(search_word): 11 | final_out.append(search_word) 12 | 13 | print(final_out) 14 | 15 | 16 | class TrieNode: 17 | def __init__(self): 18 | self.children = [None] * 26 19 | self.is_end_of_word = False 20 | 21 | 22 | class Trie: 23 | def __init__(self): 24 | self.root = TrieNode() 25 | 26 | def char_to_index(self, ch): 27 | return ord(ch) - ord('a') 28 | 29 | def insert(self, key): 30 | node = self.root 31 | for i in range(0, len(key)): 32 | index = self.char_to_index(key[i]) 33 | if not node.children[index]: 34 | node.children[index] = TrieNode() 35 | node = node.children[index] 36 | node.is_end_of_word = True 37 | 38 | def search(self, key): 39 | node = self.root 40 | for i in range(0, len(key)): 41 | index = self.char_to_index(key[i]) 42 | if not node.children[index]: 43 | return False 44 | node = node.children[index] 45 | return node 46 | 47 | 48 | multi_search("nirjhari", ["jhari", "nir", "jha", "alcalk", "abc"]) 49 | -------------------------------------------------------------------------------- /moderate_problems/pattern_matching.py: -------------------------------------------------------------------------------- 1 | def match_pattern(pattern, value): 2 | size = len(value) 3 | main_char = pattern[0] 4 | alt_char = 'b' if main_char == 'a' else 'a' 5 | main_char_count = count_of(pattern, main_char) 6 | alt_char_count = len(pattern) - main_char_count 7 | max_size_main = size//main_char_count 8 | first_alt = pattern.find(alt_char) 9 | 10 | for i in range(0, max_size_main): 11 | remaining_length = size - i * main_char_count 12 | first = value[0:i] 13 | if alt_char_count == 0 or remaining_length % alt_char_count == 0: 14 | alt_index = first_alt * i 15 | alt_size = 0 if alt_char_count == 0 else remaining_length//alt_char_count 16 | second = value[alt_index: alt_size + alt_index] 17 | 18 | cand = get_string_from_pattern(pattern, first, second) 19 | if cand == value: 20 | return True 21 | 22 | 23 | def get_string_from_pattern(pattern, first, second): 24 | final_str = '' 25 | for pat in pattern: 26 | if pat == 'a': 27 | final_str += first 28 | else: 29 | final_str += second 30 | return final_str 31 | 32 | 33 | def count_of(pattern, main_chr): 34 | count = 0 35 | for pat in pattern: 36 | if pat == main_chr: 37 | count += 1 38 | return count 39 | 40 | 41 | if match_pattern("aabab", "catcatgocatgo"): 42 | print("found") 43 | else: 44 | print("not found") 45 | -------------------------------------------------------------------------------- /moderate_problems/sub_sort.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class Pointer: 5 | def __init__(self, start=None, end=None): 6 | self.start = start 7 | self.end = end 8 | 9 | def print_index(self): 10 | print(self.start, self.end) 11 | 12 | 13 | def find_sort_index(arr): 14 | L = Pointer() 15 | M = Pointer() 16 | R = Pointer() 17 | 18 | L.start = 0 19 | R.end = len(arr) - 1 20 | max_so_far = -sys.maxsize 21 | for i in range(len(arr)): 22 | if arr[i] > max_so_far: 23 | L.end = i 24 | max_so_far = arr[i] 25 | else: 26 | left_max_index = i 27 | break 28 | 29 | min_so_far = sys.maxsize 30 | for i in range(len(arr)-1, 0, -1): 31 | right_min_index = i 32 | if arr[i] < min_so_far: 33 | R.start = i 34 | min_so_far = arr[i] 35 | else: 36 | break 37 | 38 | M.start = left_max_index 39 | M.end = right_min_index 40 | 41 | min_val = min(min(arr[M.start:M.end+1]), min(arr[R.start:R.end+1])) 42 | max_val = max(max(arr[L.start:L.end+1]), max(arr[M.start:M.end+1])) 43 | 44 | # M.print_index() 45 | # L.print_index() 46 | # R.print_index() 47 | 48 | while arr[L.end] > min_val: 49 | L.end = L.end - 1 50 | 51 | while arr[R.start] < max_val: 52 | R.start = R.start + 1 53 | 54 | print(L.end+1, R.start) 55 | 56 | 57 | find_sort_index([1, 2, 4, 7, 10, 11, 8, 6, 5, 12, 16, 18]) 58 | -------------------------------------------------------------------------------- /moderate_problems/english_int.py: -------------------------------------------------------------------------------- 1 | smalls = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", 2 | "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"] 3 | 4 | tens = ["", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"] 5 | 6 | bigs = ["", "Thousand", "Million", "Billion"] 7 | hundred = "Hundred" 8 | negative = "Negative" 9 | 10 | 11 | def convert_to_english(num): 12 | if num == 0: 13 | return smalls[0] 14 | elif num < 0: 15 | return negative + " " + convert_to_english(-1 * num) 16 | 17 | chunk_count = 0 18 | num_data = [] 19 | while num > 0: 20 | if num % 1000 != 0: 21 | chunk = convert_chunk(num % 1000) + " " + bigs[chunk_count] 22 | num_data.insert(0, chunk) 23 | num //= 1000 24 | chunk_count += 1 25 | print(' '.join(num_data)) 26 | return ' '.join(num_data) 27 | 28 | 29 | def convert_chunk(num): 30 | num_data = [] 31 | if num >= 100: 32 | num_data.append(smalls[num//100]) 33 | num_data.append(hundred) 34 | num %= 100 35 | 36 | if 19 >= num >= 10: 37 | num_data.append(smalls[num]) 38 | elif num >= 20: 39 | num_data.append(tens[num//10]) 40 | num %= 10 41 | 42 | if 9 >= num >= 1: 43 | num_data.append(smalls[num]) 44 | return ''.join(num_data) 45 | 46 | 47 | if __name__ == "__main__": 48 | convert_to_english(-1234) 49 | -------------------------------------------------------------------------------- /trees/paths_with_sum.py: -------------------------------------------------------------------------------- 1 | # Approach 1 2 | def find_paths(root, target): 3 | if root is None: 4 | return 0 5 | 6 | root_paths = find_paths_with_sum(root, target, 0) 7 | left_paths = find_paths(root.left, target) 8 | right_paths = find_paths(root.right, target) 9 | 10 | return root_paths + left_paths + right_paths 11 | 12 | 13 | def find_paths_with_sum(root, target, current_sum): 14 | if root is None: 15 | return 0 16 | total_paths = 0 17 | current_sum += root.data 18 | if current_sum == target: 19 | total_paths += 1 20 | 21 | total_paths += find_paths_with_sum(root.left, target, current_sum) 22 | total_paths += find_paths_with_sum(root.right, target, current_sum) 23 | return total_paths 24 | 25 | 26 | # Approach 2 27 | def count_paths(root, target): 28 | return count_paths_with_sum(root, target, 0, {}) 29 | 30 | 31 | def count_paths_with_sum(root, target, running_sum, sum_dict): 32 | if root is None: 33 | return 0 34 | running_sum += root.data 35 | remaining = running_sum - target 36 | total_paths = sum_dict.get(remaining, 0) 37 | 38 | if running_sum in sum_dict: 39 | sum_dict[running_sum] += 1 40 | else: 41 | sum_dict[running_sum] = 1 42 | total_paths += count_paths_with_sum(root.left, target, running_sum, sum_dict) 43 | total_paths += count_paths_with_sum(root.right, target, running_sum, sum_dict) 44 | if running_sum in sum_dict: 45 | sum_dict[running_sum] -= 1 46 | return total_paths 47 | -------------------------------------------------------------------------------- /trees/check_subtree.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | 4 | def check_subtree(t1, t2): 5 | # import pdb; pdb.set_trace() 6 | if t1 is None and t2 is None: 7 | return True 8 | elif t1 is None or t2 is None: 9 | return False 10 | elif t1.data != t2.data: 11 | return False 12 | else: 13 | left_subtree = check_subtree(t1.left_child, t2.left_child) 14 | right_subtree = check_subtree(t1.right_child, t2.right_child) 15 | if left_subtree and right_subtree: 16 | return True 17 | else: 18 | return False 19 | 20 | 21 | def traverse(t1, t2): 22 | if t2 is None: 23 | return True 24 | 25 | if t1 is None: 26 | return 27 | 28 | if t1.data == t2.data: 29 | is_subtree = check_subtree(t1, t2) 30 | if is_subtree: 31 | return True 32 | left_subtree = traverse(t1.left_child, t2) 33 | right_subtree = traverse(t1.right_child, t2) 34 | if left_subtree or right_subtree: 35 | return True 36 | 37 | 38 | if __name__ == "__main__": 39 | tree1 = BinarySearchTree() 40 | tree1.insert(10) 41 | tree1.insert(8) 42 | tree1.insert(12) 43 | tree1.insert(11) 44 | tree1.insert(15) 45 | tree1.insert(4) 46 | 47 | tree2 = BinarySearchTree() 48 | tree2.insert(12) 49 | tree2.insert(11) 50 | tree2.insert(13) 51 | 52 | res = traverse(tree1.root, tree2.root) 53 | if res: 54 | print("Subtree present") 55 | else: 56 | print("Subtree not present") 57 | -------------------------------------------------------------------------------- /linked_list/merge_two_lists.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def sorted_merge(list1, list2): 5 | if list1 is None: 6 | return list2 7 | elif list2 is None: 8 | return list1 9 | 10 | final_list = LinkedList() 11 | while list1 or list2: 12 | if list1 and list2 is None: 13 | final_list.insertatend(list1.data) 14 | list1 = list1.next 15 | continue 16 | 17 | if list2 and list1 is None: 18 | final_list.insertatend(list2.data) 19 | list2 = list2.next 20 | continue 21 | 22 | if list1.data < list2.data: 23 | final_list.insertatend(list1.data) 24 | list1 = list1.next 25 | elif list1.data > list2.data: 26 | final_list.insertatend(list2.data) 27 | list2 = list2.next 28 | elif list1.data == list2.data: 29 | final_list.insertatend(list1.data) 30 | final_list.insertatend(list2.data) 31 | list1 = list1.next 32 | list2 = list2.next 33 | return final_list 34 | 35 | 36 | if __name__ == "__main__": 37 | llist1 = LinkedList() 38 | llist1.push(10) 39 | llist1.push(9) 40 | llist1.push(8) 41 | llist1.push(7) 42 | llist1.push(6) 43 | llist1.push(5) 44 | llist2 = LinkedList() 45 | llist2.push(4) 46 | llist2.push(3) 47 | llist2.push(2) 48 | llist2.push(2) 49 | llist2.push(1) 50 | llist2.push(0) 51 | final_list = sorted_merge(llist1.head, llist2.head) 52 | final_list.traverse() 53 | -------------------------------------------------------------------------------- /linked_list/partition.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | class Node: 5 | def __init__(self, data): 6 | self.data = data 7 | self.next = None 8 | 9 | 10 | class NewLinkedList: 11 | def __init__(self): 12 | self.head = None 13 | self.tail = None 14 | 15 | def insert_at_end(self, data): 16 | new_node = Node(data) 17 | if not self.head: 18 | self.head = new_node 19 | self.tail = new_node 20 | self.tail.next = new_node 21 | self.tail = new_node 22 | 23 | def insert_at_beg(self, data): 24 | new_node = Node(data) 25 | if not self.head: 26 | self.head = new_node 27 | self.tail = new_node 28 | new_node.next = self.head 29 | self.head = new_node 30 | 31 | def traverse(self): 32 | temp = self.head 33 | while temp: 34 | print(temp.data, end=" -> ") 35 | temp = temp.next 36 | 37 | 38 | def partition_list(llist, partition): 39 | new_list = NewLinkedList() 40 | while llist: 41 | if llist.data < partition: 42 | new_list.insert_at_beg(llist.data) 43 | else: 44 | new_list.insert_at_end(llist.data) 45 | llist = llist.next 46 | new_list.traverse() 47 | 48 | 49 | if __name__ == "__main__": 50 | llist = LinkedList() 51 | llist.push(1) 52 | llist.push(2) 53 | llist.push(10) 54 | llist.push(5) 55 | llist.push(8) 56 | llist.push(5) 57 | llist.push(3) 58 | partition_list(llist.head, 5) 59 | -------------------------------------------------------------------------------- /stacks_and_queues/sort_stack.py: -------------------------------------------------------------------------------- 1 | class StackNode: 2 | def __init__(self, data): 3 | self.next = None 4 | self.data = data 5 | 6 | 7 | class Stack: 8 | def __init__(self): 9 | self.top = None 10 | 11 | def push(self, data): 12 | new_node = StackNode(data) 13 | if self.top: 14 | new_node.next = self.top 15 | self.top = new_node 16 | 17 | def pop(self): 18 | if self.top: 19 | temp = self.top 20 | item = temp.data 21 | self.top = self.top.next 22 | return item 23 | else: 24 | print("Stack is empty") 25 | 26 | def peek(self): 27 | if self.top: 28 | return self.top.data 29 | 30 | def is_empty(self): 31 | if self.top: 32 | return False 33 | else: 34 | return True 35 | 36 | def sort_stack(self, ss_stack): 37 | # import pdb; pdb.set_trace() 38 | while not self.is_empty(): 39 | temp = self.pop() 40 | while not ss_stack.is_empty() and ss_stack.peek() > temp: 41 | self.push(ss_stack.pop()) 42 | ss_stack.push(temp) 43 | 44 | def print_stack(self): 45 | temp = self.top 46 | while temp: 47 | print(temp.data) 48 | temp = temp.next 49 | 50 | 51 | if __name__ == "__main__": 52 | s = Stack() 53 | s.push(10) 54 | s.push(20) 55 | s.push(8) 56 | s.push(5) 57 | s.push(11) 58 | ss_stack = Stack() 59 | s.sort_stack(ss_stack) 60 | ss_stack.print_stack() 61 | -------------------------------------------------------------------------------- /hard_problems/letters_numbers.py: -------------------------------------------------------------------------------- 1 | # brute for would be N^2 2 | # The other approach is to create a table with with digit and char count as we move forward in the array 3 | # a 1 1 a 1 1 a a a a 1 4 | # a 1 1 1 2 2 2 3 4 5 6 6 5 | # 1 0 1 2 2 3 4 4 4 4 4 5 6 | # d 1 0 -1 0 -1 -2 -1 0 1 2 1 7 | # where a denotes a character and 1 denotes a number 8 | # now one thing to notice is that where ever the diff is same means after the first index where diff was found same, 9 | # same number of characters and digits have been added 10 | 11 | 12 | def find_longest_subarray(arr): 13 | delats = find_delta(arr) 14 | # print(delats) 15 | match = find_longest_match(delats) 16 | print(match) 17 | return arr[match[0]+1:match[1]+1] 18 | 19 | 20 | def find_delta(arr): 21 | deltas = [] 22 | delta = 0 23 | for i in arr: 24 | if i.isdigit(): 25 | delta -= 1 26 | else: 27 | delta += 1 28 | deltas.append(delta) 29 | return deltas 30 | 31 | 32 | def find_longest_match(deltas): 33 | hash_table = dict() 34 | max_index = (0, 0) 35 | for i in range(len(deltas)): 36 | delta = deltas[i] 37 | if delta not in hash_table: 38 | hash_table[delta] = i 39 | else: 40 | match = hash_table.get(delta) 41 | current_distance = i - match 42 | max_distance = max_index[1] - max_index[0] 43 | if current_distance > max_distance: 44 | max_index = (match, i) 45 | return max_index 46 | 47 | 48 | print(find_longest_subarray(['a', '1', '1', 'a', '1', '1', 'a', 'a', 'a', 'a', '1'])) 49 | -------------------------------------------------------------------------------- /moderate_problems/line_segment.py: -------------------------------------------------------------------------------- 1 | class Line: 2 | def __init__(self, start, end): 3 | self.start = start 4 | self.end = end 5 | self.slope = (end.y - start.y) / (end.x - start.x) 6 | self.intercept = end.y - self.slope * end.x 7 | 8 | 9 | class Point: 10 | def __init__(self, x, y): 11 | self.x = x 12 | self.y = y 13 | 14 | 15 | class Intersection: 16 | def __init__(self, line1, line2): 17 | self.line1 = line1 18 | self.line2 = line2 19 | 20 | def find_intersection(self): 21 | if self.line1.slope == self.line2.slope: 22 | if self.line1.intercept == self.line2.intercept and \ 23 | self.is_between(self.line1.start, self.line2.start, self.line1.end): 24 | return self.line2.start2 25 | return None 26 | x = (self.line2.intercept - self.line1.intercept) / (self.line1.slope - self.line2.slope) 27 | y = x * self.line1.slope + self.line1.intercept 28 | intersection_point = Point(x, y) 29 | 30 | if self.is_between(self.line1.start, intersection_point, self.line1.end) and \ 31 | self.is_between(self.line2.start, intersection_point, self.line2.end): 32 | return intersection_point 33 | else: 34 | return None 35 | 36 | def is_between(self, start, middle, end): 37 | if start.x > end.x and start.y > end.y: 38 | return middle.x >= end.x and middle.y >= end.y and middle.x <= start.x and middle.y <= start.y 39 | else: 40 | return middle.x <= end.x and middle.y <= end.y and middle.x >= start.x and middle.y >= start.y 41 | 42 | -------------------------------------------------------------------------------- /trees/bst_sequence.py: -------------------------------------------------------------------------------- 1 | from binary_tree import BinarySearchTree 2 | 3 | 4 | def weave(prefix, subtree_left, subtree_right, results): 5 | # print(subtree_left, subtree_right) 6 | if not len(subtree_left) or not len(subtree_right): 7 | results.append(prefix + subtree_left + subtree_right) 8 | return results 9 | 10 | left_subtree_item = subtree_left.pop(0) 11 | prefix.append(left_subtree_item) 12 | # print("left subtree") 13 | # print(subtree_left) 14 | weave(prefix, subtree_left, subtree_right, results) 15 | left_item = prefix.pop() 16 | subtree_left.insert(0, left_item) 17 | 18 | right_subtree_item = subtree_right.pop(0) 19 | prefix.append(right_subtree_item) 20 | # print("subtree right") 21 | # print(subtree_right) 22 | weave(prefix, subtree_left, subtree_right, results) 23 | right_item = prefix.pop() 24 | subtree_right.insert(0, right_item) 25 | 26 | return results 27 | 28 | 29 | def traverse(root): 30 | results = [] 31 | if root is None: 32 | return results 33 | prefix = [root.data] 34 | 35 | left_seq = traverse(root.left_child) 36 | right_seq = traverse(root.right_child) 37 | 38 | results = weave(prefix, left_seq, right_seq, results) 39 | 40 | return results 41 | 42 | 43 | if __name__ == "__main__": 44 | bst_obj = BinarySearchTree() 45 | bst_obj.insert(20) 46 | bst_obj.insert(8) 47 | bst_obj.insert(22) 48 | bst_obj.insert(4) 49 | bst_obj.insert(12) 50 | bst_obj.insert(10) 51 | bst_obj.insert(14) 52 | # bst_obj.insert(2) 53 | # bst_obj.insert(1) 54 | # bst_obj.insert(3) 55 | res = traverse(bst_obj.root) 56 | print(res) 57 | -------------------------------------------------------------------------------- /hard_problems/max_black_square.py: -------------------------------------------------------------------------------- 1 | def find_max_black_sq(mat): 2 | processed = preprocess_mat(mat) 3 | for i in range(len(mat), 0, -1): 4 | size = find_square(processed, i) 5 | if size: 6 | return size 7 | return None 8 | 9 | 10 | def find_square(processed, size): 11 | count = len(processed) - size + 1 12 | for r in range(0, count): 13 | for c in range(0, count): 14 | if square_exists(processed, r, c, size): 15 | return size 16 | 17 | 18 | def square_exists(mat, row, col, size): 19 | top_left = mat[row][col] 20 | top_right = mat[row][col+size-1] 21 | bottom_left = mat[row+size-1][col] 22 | 23 | if top_left[0] < size or top_left[1] < size or top_right[1] < size or bottom_left[0] < size: 24 | return False 25 | return True 26 | 27 | 28 | def preprocess_mat(mat): 29 | processed = [[0 for i in range(len(mat))] for j in range(len(mat))] 30 | for row in range(len(mat)-1, -1, -1): 31 | for col in range(len(mat)-1, -1, -1): 32 | right_zeros = 0 33 | below_zeros = 0 34 | if mat[row][col] == 0: 35 | right_zeros += 1 36 | below_zeros += 1 37 | if col + 1 < len(mat): 38 | previous = processed[row][col+1] 39 | right_zeros += previous[0] 40 | if row + 1 < len(mat): 41 | below = processed[row+1][col] 42 | below_zeros += below[1] 43 | processed[row][col] = (right_zeros, below_zeros) 44 | return processed 45 | 46 | 47 | # print(find_max_black_sq([[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [0, 0, 0, 0]])) 48 | print(find_max_black_sq([[1, 0, 1], [0, 0, 1], [0, 0, 1]])) -------------------------------------------------------------------------------- /graphs/detect_cycle.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Solution: 5 | def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: 6 | self.temp_dict = {} 7 | self.visited = set() 8 | self.cycle_found = False 9 | unique_courses = set() 10 | 11 | if not prerequisites: 12 | return True 13 | for item in prerequisites: 14 | src = item[0] 15 | dest = item[1] 16 | unique_courses.add(src) 17 | unique_courses.add(dest) 18 | if src not in self.temp_dict: 19 | self.temp_dict[src] = [dest] 20 | else: 21 | self.temp_dict[src].append(dest) 22 | 23 | while len(self.visited) != len(unique_courses): 24 | n = random.randint(0, len(prerequisites) - 1) 25 | src = prerequisites[n][0] 26 | if src in self.visited: 27 | continue 28 | self.visited.add(src) 29 | self.dfs(src) 30 | if self.cycle_found: 31 | break 32 | 33 | if self.cycle_found: 34 | return False 35 | return True 36 | 37 | def dfs(self, node, src_list=[]): 38 | if node in self.temp_dict: 39 | src_list.append(node) 40 | for i in range(len(self.temp_dict[node])): 41 | current_node = self.temp_dict[node][i] 42 | if current_node in self.visited and current_node in src_list: 43 | self.cycle_found = True 44 | return 45 | elif current_node in self.visited: 46 | continue 47 | self.visited.add(current_node) 48 | self.dfs(current_node, src_list) 49 | -------------------------------------------------------------------------------- /linked_list/sum_lists.py: -------------------------------------------------------------------------------- 1 | from linked_list_operations import LinkedList 2 | 3 | 4 | def sum_reversed_num_list(l1, l2): 5 | final_list = LinkedList() 6 | if l1 is None and l2 is None: 7 | return 8 | if l1 and l2 is None: 9 | return l1 10 | if l2 and l1 is None: 11 | return l2 12 | carry = 0 13 | while l1 or l2: 14 | if l1 and l2: 15 | sum = l1.data + l2.data 16 | if carry: 17 | sum = sum + carry 18 | val = sum % 10 19 | final_list.push(val) 20 | if sum >= 10: 21 | carry = 1 22 | else: 23 | carry = 0 24 | l1 = l1.next 25 | l2 = l2.next 26 | elif l1 and not l2: 27 | sum = l1.data 28 | if carry: 29 | sum = carry + sum 30 | val = sum % 10 31 | final_list.push(val) 32 | if sum >= 10: 33 | carry = 1 34 | else: 35 | carry = 0 36 | l1 = l1.next 37 | elif l2 and not l1: 38 | sum = l2.data 39 | if carry: 40 | sum = carry + sum 41 | val = sum % 10 42 | final_list.push(val) 43 | if sum >= 10: 44 | carry = 1 45 | else: 46 | carry = 0 47 | l2 = l2.next 48 | if carry: 49 | final_list.push(carry) 50 | final_list.reverse() 51 | final_list.traverse() 52 | 53 | 54 | if __name__ == "__main__": 55 | llist1 = LinkedList() 56 | llist1.push(1) 57 | llist1.push(9) 58 | llist1.push(8) 59 | llist1.push(2) 60 | llist2 = LinkedList() 61 | llist2.push(4) 62 | llist2.push(3) 63 | llist2.push(2) 64 | sum_reversed_num_list(llist1.head, llist2.head) 65 | -------------------------------------------------------------------------------- /hard_problems/shortest_supersequence.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections import defaultdict 3 | 4 | 5 | class Node: 6 | def __init__(self, key, index): 7 | self.key = key 8 | self.index = index 9 | # self.val = val 10 | 11 | 12 | def find_shortest(s_arr, b_arr): 13 | dict = defaultdict(list) 14 | for i in range(len(b_arr)): 15 | if b_arr[i] in s_arr: 16 | dict[b_arr[i]].append(i) 17 | 18 | temp = [] 19 | for item in dict: 20 | temp.append(Node(item, dict[item][0])) 21 | 22 | min_range = sys.maxsize 23 | while len(temp) == len(dict): 24 | min_val, min_key, min_index = get_val(temp, val='min') 25 | max_val, max_key, max_index = get_val(temp, val='max') 26 | cur_range = max_val - min_val 27 | if cur_range < min_range: 28 | min_range = cur_range 29 | 30 | dict[min_key].pop(0) 31 | temp.pop(min_index) 32 | if dict[min_key]: 33 | temp.append(Node(min_key, dict[min_key][0])) 34 | 35 | print(min_range) 36 | 37 | 38 | def get_val(arr, val): 39 | if val == 'min': 40 | min_val = sys.maxsize 41 | for i in range(len(arr)): 42 | item = arr[i] 43 | if item.index < min_val: 44 | min_val = item.index 45 | min_key = item.key 46 | min_index = i 47 | return min_val, min_key, min_index 48 | elif val == 'max': 49 | max_val = -sys.maxsize 50 | for i in range(len(arr)): 51 | item = arr[i] 52 | if item.index > max_val: 53 | max_val = item.index 54 | max_key = item.key 55 | max_index = i 56 | return max_val, max_key, max_index 57 | 58 | 59 | find_shortest([1, 5, 9], [7, 5, 9, 0, 2, 1, 3, 5, 7, 9, 1, 1, 5, 8, 8, 9, 7]) 60 | -------------------------------------------------------------------------------- /stacks_and_queues/stack.py: -------------------------------------------------------------------------------- 1 | class StackNode: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | 7 | class Stack: 8 | def __init__(self): 9 | self.top = None 10 | self.min_stack_top = None 11 | 12 | def push(self, item): 13 | new_node = StackNode(item) 14 | new_node.next = self.top 15 | self.top = new_node 16 | 17 | min_node = StackNode(item) 18 | min_node.next = self.min_stack_top 19 | if self.min_stack_top is None: 20 | self.min_stack_top = min_node 21 | else: 22 | if item < self.min_stack_top.data: 23 | self.min_stack_top = min_node 24 | 25 | def pop(self): 26 | if self.top is None: 27 | print("Stack is empty") 28 | return 29 | item = self.top.data 30 | self.top = self.top.next 31 | if item == self.min_stack_top.data: 32 | self.min_stack_top = self.min_stack_top.next 33 | return item 34 | 35 | def peek(self): 36 | if self.top is None: 37 | print("Stack is empty") 38 | return 39 | return self.top.data 40 | 41 | def is_empty(self): 42 | return self.top is None 43 | 44 | def get_min(self): 45 | if self.min_stack_top: 46 | item = self.min_stack_top.data 47 | self.min_stack_top = self.min_stack_top.next 48 | return item 49 | else: 50 | return "" 51 | 52 | 53 | if __name__ == "__main__": 54 | stack = Stack() 55 | stack.push(10) 56 | stack.push(20) 57 | stack.push(12) 58 | stack.push(8) 59 | stack.push(40) 60 | min = stack.get_min() 61 | print(min) 62 | item = stack.pop() 63 | item2 = stack.pop() 64 | print(item, item2) 65 | stack_min = stack.get_min() 66 | print(stack_min) 67 | -------------------------------------------------------------------------------- /stacks_and_queues/three_in_one.py: -------------------------------------------------------------------------------- 1 | class StackNode: 2 | def __init__(self, start_index, next_index, max_index): 3 | self.start_index = start_index 4 | self.next_index = next_index 5 | self.max_index = max_index 6 | 7 | def is_full(self, arr): 8 | if arr[self.max_index] == None: 9 | return False 10 | else: 11 | return True 12 | 13 | def is_empty(self, arr): 14 | if arr[self.start_index] == None: 15 | return True 16 | else: 17 | return False 18 | 19 | 20 | class ThreeStacks: 21 | def __init__(self, array_size): 22 | self.arr = [None] * array_size 23 | 24 | division = array_size//3 25 | first_stack_range = (0, division-1) 26 | second_stack_range = (0+division, (2*division)-1) 27 | third_stack_range = (0+2*division, 3*division) 28 | 29 | self.stack1 = StackNode(start_index=first_stack_range[0], next_index=first_stack_range[0], max_index=first_stack_range[1]) 30 | self.stack2 = StackNode(start_index=second_stack_range[0], next_index=second_stack_range[0], max_index=second_stack_range[1]) 31 | self.stack3 = StackNode(start_index=third_stack_range[0], next_index=third_stack_range[0], max_index=third_stack_range[1]) 32 | 33 | def push(self, stack, data): 34 | if not stack.is_full(self.arr): 35 | next_index = stack.next_index 36 | self.arr[next_index] = data 37 | 38 | def pop(self, stack): 39 | if not stack.is_empty(self.arr): 40 | item = self.arr[stack.next_index] 41 | self.arr[stack.next_index] = None 42 | return item 43 | 44 | 45 | if __name__ == "__main__": 46 | ts = ThreeStacks(10) 47 | ts.push(ts.stack1, 13) 48 | ts.push(ts.stack2, 12) 49 | ts.push(ts.stack2, 11) 50 | item = ts.pop(ts.stack1) 51 | print(item) 52 | -------------------------------------------------------------------------------- /arrays_and_strings/palindrome_permutation.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | 4 | def check_palindrome_permutation(input_str): 5 | input_str = input_str.lower() 6 | input_str = list(filter(lambda i: i.isalpha(), input_str)) 7 | count_dict = Counter(input_str) 8 | even_count = 0 9 | odd_count = 0 10 | for item in count_dict.items(): 11 | if item[1] % 2 == 0: 12 | even_count += 1 13 | else: 14 | odd_count += 1 15 | 16 | if len(input_str) % 2 == 0: 17 | if odd_count == 0 and even_count == len(input_str)//2: 18 | return True 19 | else: 20 | return False 21 | else: 22 | if odd_count == 1 and even_count == len(input_str)//2: 23 | return True 24 | else: 25 | return False 26 | 27 | 28 | def check_permutation_palindrome_bits(input_str): 29 | input_str = input_str.lower() 30 | input_str = list(filter(lambda i: i.isalpha(), input_str)) 31 | number = 0 32 | for inp in input_str: 33 | n = ord(inp) - ord('a') 34 | number ^= 1 << n 35 | 36 | if len(input_str) % 2 == 0: 37 | if number == 0: 38 | return True 39 | else: 40 | return False 41 | else: 42 | count_bits = 0 43 | while number: 44 | n = number & 1 45 | if n == 1: 46 | count_bits += 1 47 | if count_bits > 1: 48 | return False 49 | number = number >> 1 50 | return True 51 | 52 | 53 | if __name__ == "__main__": 54 | # out = check_palindrome_permutation("Tact Coa") 55 | # if out: 56 | # print("It is a palindrome permutation") 57 | # else: 58 | # print("It is not a palindrome permutation") 59 | out = check_permutation_palindrome_bits("abab cbcb") 60 | if out: 61 | print("It is a palindrome permutation") 62 | else: 63 | print("It is not a palindrome permutation") 64 | -------------------------------------------------------------------------------- /linked_list/merge_k_sorted_lists.py: -------------------------------------------------------------------------------- 1 | # Definition for singly-linked list. 2 | from linked_list_operations import LinkedList 3 | 4 | 5 | class Solution: 6 | def mergeKLists(self, lists): 7 | result = None 8 | for i in range(len(lists)): 9 | result = self.sorted_merge(result, lists[i]) 10 | temp = result 11 | while temp: 12 | print(temp.data, end=" -> ") 13 | temp = temp.next 14 | 15 | def sorted_merge(self, list1, list2): 16 | if list1 is None: 17 | return list2 18 | elif list2 is None: 19 | return list1 20 | 21 | final_list = LinkedList() 22 | while list1 or list2: 23 | if list1 and list2 is None: 24 | final_list.insertatend(list1.data) 25 | list1 = list1.next 26 | continue 27 | 28 | if list2 and list1 is None: 29 | final_list.insertatend(list2.data) 30 | list2 = list2.next 31 | continue 32 | 33 | if list1.data < list2.data: 34 | final_list.insertatend(list1.data) 35 | list1 = list1.next 36 | elif list1.data > list2.data: 37 | final_list.insertatend(list2.data) 38 | list2 = list2.next 39 | elif list1.data == list2.data: 40 | final_list.insertatend(list1.data) 41 | final_list.insertatend(list2.data) 42 | list1 = list1.next 43 | list2 = list2.next 44 | return final_list.head 45 | 46 | 47 | if __name__ == "__main__": 48 | llist1 = LinkedList() 49 | llist1.push(5) 50 | llist1.push(4) 51 | llist1.push(1) 52 | llist2 = LinkedList() 53 | llist2.push(4) 54 | llist2.push(3) 55 | llist2.push(1) 56 | llist3 = LinkedList() 57 | llist3.push(6) 58 | llist3.push(2) 59 | s = Solution() 60 | resp = s.mergeKLists([llist1.head, llist2.head, llist3.head]) 61 | -------------------------------------------------------------------------------- /graphs/adjacency_list.py: -------------------------------------------------------------------------------- 1 | class AdjacentNode: 2 | def __init__(self, vertex): 3 | self.vertex = vertex 4 | self.next = None 5 | 6 | 7 | class Graph: 8 | def __init__(self, vertices): 9 | self.vertices = vertices 10 | self.graph = [None] * self.vertices 11 | 12 | def add_edge(self, src, dest): 13 | node = AdjacentNode(src) 14 | node.next = self.graph[dest] 15 | self.graph[dest] = node 16 | 17 | node = AdjacentNode(dest) 18 | node.next = self.graph[src] 19 | self.graph[src] = node 20 | 21 | def print_graph(self): 22 | for i in range(self.vertices): 23 | print("Adjacency list of vertex {}\n head".format(i), end="") 24 | temp = self.graph[i] 25 | while temp: 26 | print(" -> {}".format(temp.vertex), end="") 27 | temp = temp.next 28 | print(" \n") 29 | 30 | def dfs(self, v): 31 | visited = [False] * self.vertices 32 | self.dfs_util(v, visited) 33 | 34 | def dfs_util(self, v, visited): 35 | visited[v] = True 36 | print(v, end=' ') 37 | for i in self.graph[v]: 38 | if visited[i] == False: 39 | self.dfs_util(i, visited) 40 | 41 | def bfs(self, num): 42 | visited = [False] * self.vertices 43 | 44 | queue = list() 45 | queue.append(num) 46 | visited[num] = True 47 | 48 | while queue: 49 | item = queue.pop(0) 50 | print(item) 51 | for i in self.graph[item]: 52 | if visited[i] == False: 53 | queue.append(i) 54 | visited[i] = True 55 | 56 | 57 | if __name__ == "__main__": 58 | V = 5 59 | graph = Graph(V) 60 | graph.add_edge(0, 1) 61 | graph.add_edge(0, 4) 62 | graph.add_edge(1, 2) 63 | graph.add_edge(1, 3) 64 | graph.add_edge(1, 4) 65 | graph.add_edge(2, 3) 66 | graph.add_edge(3, 4) 67 | graph.print_graph() 68 | -------------------------------------------------------------------------------- /graphs/topological_sort.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, vertex): 3 | self.vertex = vertex 4 | self.child = None 5 | 6 | 7 | class Graph: 8 | def __init__(self, *projects): 9 | self.visited = dict() 10 | self.graph = dict() 11 | self.final_output = list() 12 | for project in projects: 13 | self.graph[project] = list() 14 | self.visited[project] = False 15 | 16 | def add_edge(self, src, dest): 17 | new_node = Node(src) 18 | self.graph[dest].append(new_node) 19 | 20 | def print_graph(self): 21 | # print(self.graph) 22 | print(self.final_output) 23 | 24 | def topological_sort(self): 25 | while not all(list(self.visited.values())): 26 | for i, j in self.graph.items(): 27 | if not self.visited[i]: 28 | node = self.graph[i] 29 | src = i 30 | self.visited[i] = True 31 | if not node: 32 | if i not in self.final_output: 33 | self.final_output.append(i) 34 | break 35 | self.dfs(node) 36 | if src not in self.final_output: 37 | self.final_output.append(src) 38 | 39 | def dfs(self, node): 40 | for n in node: 41 | if self.graph[n.vertex] and not self.visited[n.vertex]: 42 | self.visited[n.vertex] = True 43 | self.dfs(self.graph[n.vertex]) 44 | self.visited[n.vertex] = True 45 | if n.vertex not in self.final_output: 46 | self.final_output.append(n.vertex) 47 | 48 | 49 | if __name__ == "__main__": 50 | graph_obj = Graph("a", "b", "c", "d", "e", "f") 51 | dependencies = [("a", "d"), ("f", "b"), ("b", "d"), ("f", "a"), ("d", "c")] 52 | for dependency in dependencies: 53 | graph_obj.add_edge(dependency[0], dependency[1]) 54 | graph_obj.topological_sort() 55 | graph_obj.print_graph() 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /trie/trie_operations.py: -------------------------------------------------------------------------------- 1 | class TrieNode: 2 | def __init__(self): 3 | self.children = [None]*26 4 | self.is_end_of_word = False 5 | 6 | 7 | class Trie: 8 | def __init__(self): 9 | self.root = TrieNode() 10 | self.word_list = [] 11 | 12 | def char_to_index(self, ch): 13 | return ord(ch) - ord('a') 14 | 15 | def index_to_char(self, index): 16 | return chr(index + ord('a')) 17 | 18 | def insert(self, key): 19 | node = self.root 20 | for i in range(0, len(key)): 21 | index = self.char_to_index(key[i]) 22 | if not node.children[index]: 23 | node.children[index] = TrieNode() 24 | node = node.children[index] 25 | node.is_end_of_word = True 26 | 27 | def search(self, key): 28 | node = self.root 29 | for i in range(0, len(key)): 30 | index = self.char_to_index(key[i]) 31 | if not node.children[index]: 32 | return False 33 | node = node.children[index] 34 | return node 35 | 36 | # def traverse(self, node, word=['']*20, level=0, search_word=''): 37 | # if node.is_end_of_word: 38 | # if search_word: 39 | # print(search_word, end='') 40 | # for i in range(level): 41 | # print(word[i], end='') 42 | # print('') 43 | # 44 | # for i in range(0, 26): 45 | # if node.children[i]: 46 | # word[level] = chr(i + ord('a')) 47 | # self.traverse(node.children[i], word=word, level=level+1, search_word=search_word) 48 | # # word = '' 49 | 50 | def suggestions(self, node, word): 51 | if node.is_end_of_word: 52 | self.word_list.append(word) 53 | 54 | for i in range(0, 26): 55 | if node.children[i]: 56 | letter = self.index_to_char(i) 57 | self.suggestions(node.children[i], word+letter) 58 | 59 | 60 | if __name__ == '__main__': 61 | keys = ["mobile", "mouse", "moneypot", "monitor", "mousepad"] 62 | t = Trie() 63 | for key in keys: 64 | t.insert(key) 65 | output = ["Not present in trie", 66 | "Present in trie"] 67 | 68 | # print("{} ---- {}".format("m", output[t.search("m")])) 69 | # print("{} ---- {}".format("mo", output[t.search("mo")])) 70 | # print("{} ---- {}".format("mou", output[t.search("mou")])) 71 | # print("{} ---- {}".format("mouse", output[t.search("mouse")])) 72 | node = t.search("mon") 73 | if node: 74 | # t.traverse(node, search_word="mou") 75 | t.suggestions(node, "mon") 76 | print(t.word_list) 77 | # t.traverse(t.root) 78 | -------------------------------------------------------------------------------- /graphs/adjacency_matrix.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | def __init__(self, vertices): 3 | self.visited = [[False] * vertices for i in range(vertices)] 4 | self.adj_matrix = [[-1] * vertices for i in range(vertices)] 5 | self.vertices = vertices 6 | self.vertices_dict = {} 7 | self.vertices_list = [0] * vertices 8 | 9 | def set_vertex(self, vertex, id): 10 | if 0 <= vertex <= self.vertices: 11 | self.vertices_dict[id] = vertex 12 | self.vertices_list[vertex] = id 13 | 14 | def set_edge(self, src, dest, weight): 15 | src = self.vertices_dict[src] 16 | dest = self.vertices_dict[dest] 17 | self.adj_matrix[src][dest] = weight 18 | self.adj_matrix[dest][src] = weight 19 | 20 | def get_vertex(self): 21 | return self.vertices_list 22 | 23 | def get_edges(self): 24 | edges = [] 25 | for i in range(self.vertices): 26 | for j in range(self.vertices): 27 | if self.adj_matrix[i][j] != -1: 28 | edges.append((self.vertices_list[i], self.vertices_list[j], self.adj_matrix[i][j])) 29 | return edges 30 | 31 | def get_matrix(self): 32 | return self.adj_matrix 33 | 34 | def bfs(self): 35 | queue = list() 36 | queue.append(self.adj_matrix[0][2]) 37 | self.visited[0][2] = True 38 | row = 0 39 | col = 2 40 | while queue: 41 | print(queue.pop(0)) 42 | directions_row = [row, row + 1, row, row - 1] 43 | directions_col = [col + 1, col, col - 1, col] 44 | for item in zip(directions_row, directions_col): 45 | row = item[0] 46 | col = item[1] 47 | if (row >= 0 and row < self.vertices) and (col >= 0 and col < self.vertices): 48 | if not self.adj_matrix[row][col] == -1: 49 | if not self.visited[row][col]: 50 | queue.append(self.adj_matrix[row][col]) 51 | self.visited[row][col] = True 52 | 53 | 54 | if __name__ == '__main__': 55 | G = Graph(6) 56 | G.set_vertex(0, 'a') 57 | G.set_vertex(1, 'b') 58 | G.set_vertex(2, 'c') 59 | G.set_vertex(3, 'd') 60 | G.set_vertex(4, 'e') 61 | G.set_vertex(5, 'f') 62 | G.set_edge('a', 'e', 10) 63 | G.set_edge('a', 'c', 20) 64 | G.set_edge('c', 'b', 30) 65 | G.set_edge('b', 'e', 40) 66 | G.set_edge('e', 'd', 50) 67 | G.set_edge('f', 'e', 60) 68 | # print("Vertices of Graph") 69 | # print(G.get_vertex()) 70 | # print("Edges of Graph") 71 | print(G.get_edges()) 72 | # print("Adjacency Matrix of Graph") 73 | # print(G.get_matrix()) 74 | G.bfs() 75 | -------------------------------------------------------------------------------- /binary_heap/min_heap.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class MinHeap: 5 | def __init__(self, maxsize): 6 | self.maxsize = maxsize 7 | self.size = 0 8 | self.Heap = [0] * (self.maxsize+1) 9 | self.Heap[0] = -1 * sys.maxsize 10 | self.FRONT = 1 11 | 12 | def parent(self, pos): 13 | return pos//2 14 | 15 | def left_child(self, pos): 16 | return 2 * pos 17 | 18 | def right_child(self, pos): 19 | return (2 * pos) + 1 20 | 21 | def swap(self, c_pos, p_pos): 22 | self.Heap[c_pos], self.Heap[p_pos] = self.Heap[p_pos], self.Heap[c_pos] 23 | 24 | def insert(self, item): 25 | if self.size > self.maxsize: 26 | return 27 | self.size += 1 28 | self.Heap[self.size] = item 29 | current_pos = self.size 30 | while self.Heap[current_pos] < self.Heap[self.parent(current_pos)]: 31 | self.swap(current_pos, self.parent(current_pos)) 32 | current_pos = self.parent(current_pos) 33 | 34 | def min_heapify(self, pos): 35 | if not self.isLeaf(pos): 36 | if self.Heap[pos] > self.Heap[self.left_child(pos)] or self.Heap[pos] > self.Heap[self.right_child(pos)]: 37 | if self.Heap[self.left_child(pos)] < self.Heap[self.right_child(pos)]: 38 | self.swap(pos, self.left_child(pos)) 39 | self.min_heapify(self.left_child(pos)) 40 | else: 41 | self.swap(pos, self.right_child(pos)) 42 | self.min_heapify(self.right_child(pos)) 43 | 44 | def isLeaf(self, pos): 45 | if pos >= (self.size // 2) and pos <= self.size: 46 | return True 47 | return False 48 | 49 | def minHeap(self): 50 | for pos in range(self.size // 2, 0, -1): 51 | self.min_heapify(pos) 52 | 53 | def Print(self): 54 | for i in range(1, (self.size//2)+1): 55 | print("PARENT : " + str(self.Heap[i]) + " LEFT CHILD : " + str(self.Heap[(2*i)]) + " RIGHT CHILD : " + 56 | str(self.Heap[(2*i)+1])) 57 | 58 | def remove(self): 59 | item = self.Heap[self.FRONT] 60 | self.Heap[self.FRONT] = self.Heap[self.size] 61 | self.size -= 1 62 | self.min_heapify(self.FRONT) 63 | return item 64 | 65 | 66 | if __name__ == '__main__': 67 | min_heap = MinHeap(15) 68 | min_heap.insert(5) 69 | min_heap.insert(3) 70 | min_heap.insert(17) 71 | min_heap.insert(10) 72 | min_heap.insert(84) 73 | min_heap.insert(19) 74 | min_heap.insert(6) 75 | min_heap.insert(22) 76 | min_heap.insert(9) 77 | min_heap.insert(2) 78 | min_heap.minHeap() 79 | min_heap.Print() 80 | print("The Min val is " + str(min_heap.remove())) 81 | -------------------------------------------------------------------------------- /stacks_and_queues/stack_of_plates.py: -------------------------------------------------------------------------------- 1 | class StackPlates: 2 | def __init__(self, max_size): 3 | self.max_size = max_size 4 | self.stack = [[]] 5 | self.top = None 6 | self.stack_index = 0 7 | self.sub_stack_top = [None] 8 | 9 | def push(self, data): 10 | if len(self.stack[self.stack_index]) == self.max_size: 11 | self.stack_index = self.stack_index + 1 12 | self.stack.append([data]) 13 | self.top = self.stack[self.stack_index][0] 14 | self.sub_stack_top.append(None) 15 | self.sub_stack_top[self.stack_index] = self.top 16 | else: 17 | self.stack[self.stack_index].append(data) 18 | stack_size = len(self.stack[self.stack_index]) - 1 19 | self.top = self.stack[self.stack_index][stack_size] 20 | self.sub_stack_top[self.stack_index] = self.top 21 | 22 | def pop(self): 23 | if self.top: 24 | stack_size = len(self.stack[self.stack_index]) - 1 25 | item = self.stack[self.stack_index][stack_size] 26 | self.stack[self.stack_index].pop(stack_size) 27 | if stack_size > 0: 28 | self.top = self.stack[self.stack_index][stack_size-1] 29 | self.sub_stack_top[self.stack_index] = self.top 30 | elif stack_size == 0: 31 | self.stack.pop(self.stack_index) 32 | self.sub_stack_top[self.stack_index] = None 33 | self.stack_index = self.stack_index - 1 34 | self.top = self.stack[self.stack_index][self.max_size - 1] 35 | return item 36 | else: 37 | print("Stack is Empty") 38 | return None 39 | 40 | def pop_at_index(self, index): 41 | if index == self.stack_index: 42 | item = self.pop() 43 | return item 44 | else: 45 | item = self.stack[index][self.max_size - 1] 46 | for i in range(index+1, self.stack_index+1): 47 | if i == self.stack_index: 48 | item_popped = self.pop() 49 | else: 50 | item_popped = self.stack[i][self.max_size - 1] 51 | self.stack[i-1][self.max_size - 1] = item_popped 52 | return item 53 | 54 | 55 | if __name__ == "__main__": 56 | stacks = StackPlates(2) 57 | stacks.push(1) 58 | stacks.push(2) 59 | stacks.push(3) 60 | stacks.push(4) 61 | stacks.push(5) 62 | # print(stacks.stack) 63 | # item = stacks.pop() 64 | # # print(item) 65 | # item = stacks.pop() 66 | # # print(item) 67 | # stacks.push(item) 68 | stacks.push(6) 69 | print(stacks.stack) 70 | item = stacks.pop_at_index(0) 71 | print(item) 72 | item = stacks.pop_at_index(0) 73 | print(item) 74 | print(stacks.stack) 75 | -------------------------------------------------------------------------------- /stacks_and_queues/queue_via_stacks.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | 4 | class StackNode: 5 | def __init__(self, data): 6 | self.data = data 7 | self.next = None 8 | 9 | 10 | class MyQueue: 11 | def __init__(self): 12 | self.stack1 = None 13 | self.stack2 = None 14 | 15 | def is_empty(self, stack): 16 | if stack is None: 17 | return True 18 | else: 19 | return False 20 | 21 | def push(self, data): 22 | if self.is_empty(self.stack1) and self.is_empty(self.stack2): 23 | node = StackNode(data) 24 | self.stack1 = node 25 | elif not self.is_empty(self.stack1): 26 | node = StackNode(data) 27 | node.next = self.stack1 28 | self.stack1 = node 29 | elif not self.is_empty(self.stack2): 30 | while self.stack2: 31 | if self.is_empty(self.stack1): 32 | temp_node = copy.deepcopy(self.stack2) 33 | temp_node.next = None 34 | self.stack1 = temp_node 35 | else: 36 | popped_node = copy.deepcopy(self.stack2) 37 | popped_node.next = self.stack1 38 | self.stack1 = popped_node 39 | self.stack2 = self.stack2.next 40 | node = StackNode(data) 41 | node.next = self.stack1 42 | self.stack1 = node 43 | 44 | def pop(self): 45 | if self.is_empty(self.stack1) and self.is_empty(self.stack2): 46 | return None 47 | elif not self.is_empty(self.stack2): 48 | popped_item = self.stack2.data 49 | self.stack2 = self.stack2.next 50 | return popped_item 51 | elif not self.is_empty(self.stack1): 52 | while self.stack1: 53 | if self.is_empty(self.stack2): 54 | temp_node = copy.deepcopy(self.stack1) 55 | temp_node.next = None 56 | self.stack2 = temp_node 57 | else: 58 | temp_node = copy.deepcopy(self.stack1) 59 | temp_node.next = self.stack2 60 | self.stack2 = temp_node 61 | self.stack1 = self.stack1.next 62 | popped_item = self.stack2.data 63 | self.stack2 = self.stack2.next 64 | return popped_item 65 | 66 | 67 | if __name__ == "__main__": 68 | queue = MyQueue() 69 | queue.push(1) 70 | queue.push(2) 71 | queue.push(3) 72 | queue.push(4) 73 | queue.push(5) 74 | item = queue.pop() 75 | another = queue.pop() 76 | print(another) 77 | queue.push(6) 78 | new = queue.pop() 79 | new2 = queue.pop() 80 | print(new) 81 | print(new2) 82 | new3 = queue.pop() 83 | print(new3) 84 | new4 = queue.pop() 85 | print(new4) 86 | -------------------------------------------------------------------------------- /moderate_problems/calculator.py: -------------------------------------------------------------------------------- 1 | class StackNode: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | 7 | class Stack: 8 | def __init__(self): 9 | self.top = None 10 | self.size = 0 11 | 12 | def push(self, item): 13 | new_node = StackNode(item) 14 | new_node.next = self.top 15 | self.top = new_node 16 | self.size += 1 17 | 18 | def pop(self): 19 | if self.top is None: 20 | print("Stack is empty") 21 | return 22 | item = self.top.data 23 | self.top = self.top.next 24 | self.size -= 1 25 | return item 26 | 27 | def peek(self): 28 | if self.top is None: 29 | print("Stack is empty") 30 | return 31 | return self.top.data 32 | 33 | def is_empty(self): 34 | return self.top is None 35 | 36 | 37 | class Calculator: 38 | def __init__(self): 39 | self.num_stack = Stack() 40 | self.op_stack = Stack() 41 | self.priority = {'+': 1, '-': 1, '/': 2, '*': 2, '': 0} 42 | 43 | def compute(self, expr): 44 | i = 0 45 | while i < len(expr): 46 | num = self.get_num(expr, i) 47 | i += len(num) 48 | self.num_stack.push(int(''.join(num))) 49 | if i >= len(expr): 50 | break 51 | op = expr[i] 52 | self.collapse_top(op) 53 | self.op_stack.push(op) 54 | i += 1 55 | self.collapse_top('') 56 | if self.num_stack.size == 1 and self.op_stack.size == 0: 57 | return self.num_stack.pop() 58 | return 0 59 | 60 | def get_num(self, expr, i): 61 | num = [] 62 | while i < len(expr): 63 | if expr[i].isdigit(): 64 | num.append(expr[i]) 65 | else: 66 | break 67 | i += 1 68 | return num 69 | 70 | def get_operator(self, expr): 71 | pass 72 | 73 | def collapse_top(self, op): 74 | while self.num_stack.size >= 2 and self.op_stack.size >= 1: 75 | if self.priority[op] <= self.priority[self.op_stack.peek()]: 76 | second = self.num_stack.pop() 77 | first = self.num_stack.pop() 78 | operator = self.op_stack.pop() 79 | collapsed = self.apply_op(first, operator, second) 80 | self.num_stack.push(collapsed) 81 | else: 82 | break 83 | 84 | def apply_op(self, first, op, second): 85 | if op == '+': 86 | return first + second 87 | elif op == '-': 88 | return first - second 89 | elif op == '*': 90 | return first * second 91 | elif op == '/': 92 | return round(first / second, 1) 93 | else: 94 | return second 95 | 96 | 97 | c = Calculator() 98 | print(c.compute('2-6-7*8/2+5')) 99 | -------------------------------------------------------------------------------- /trees/list_of_depths.py: -------------------------------------------------------------------------------- 1 | # Given binary tree create a linked list of all nodes at each depth. If you have D depth tree then D linked lists 2 | 3 | 4 | import sys 5 | sys.path.append('..') 6 | from binary_tree import BinarySearchTree 7 | from linked_list.linked_list_operations import LinkedList 8 | 9 | 10 | class ListDepth: 11 | def __init__(self): 12 | self.list = [] 13 | 14 | def depth_first(self, node, level): 15 | if not node: 16 | return 17 | if len(self.list) == level: 18 | linked_list_obj = LinkedList() 19 | self.list.append(linked_list_obj) 20 | ll_obj = self.list[level] 21 | else: 22 | ll_obj = self.list[level] 23 | ll_obj.push(node.data) 24 | self.depth_first(node.left_child, level+1) 25 | self.depth_first(node.right_child, level+1) 26 | 27 | def breadth_first(self, node): 28 | # import pdb; pdb.set_trace() 29 | if node: 30 | linked_list_obj = LinkedList() 31 | linked_list_obj.push(node) 32 | self.list.append(linked_list_obj) 33 | else: 34 | return 35 | # while self.list: 36 | 37 | # while linked_list_obj.head: 38 | # self.list.append(linked_list_obj) 39 | # parent = linked_list_obj.pop() 40 | # linked_list_obj = LinkedList() 41 | # while parent and parent.data: 42 | # if parent.data.left_child: 43 | # print("Left child") 44 | # print(parent.data.left_child.data) 45 | # linked_list_obj.push(parent.data.left_child) 46 | # if parent.data.right_child: 47 | # print("Right child") 48 | # print(parent.data.right_child.data) 49 | # linked_list_obj.push(parent.data.right_child) 50 | # parent = parent.next 51 | 52 | def print_list(self): 53 | for i in range(len(self.list)): 54 | ll_obj = self.list[i] 55 | ll_obj.traverse() 56 | print("\n") 57 | 58 | # def print_list_bfs(self): 59 | # for i in range(len(self.list)): 60 | # ll_obj = self.list[i] 61 | # temp = ll_obj.head 62 | # while temp: 63 | # # print(temp.data, end=" -> ") 64 | # print(temp.data.left_child.data) 65 | # print(temp.data.right_child.data) 66 | # temp = temp.next 67 | # print("\n") 68 | 69 | 70 | if __name__ == '__main__': 71 | bst_obj = BinarySearchTree() 72 | bst_obj.insert(20) 73 | bst_obj.insert(30) 74 | bst_obj.insert(10) 75 | bst_obj.insert(15) 76 | bst_obj.insert(12) 77 | bst_obj.insert(6) 78 | bst_obj.insert(2) 79 | bst_obj.in_order(bst_obj.root) 80 | list_depth = ListDepth() 81 | # list_depth.depth_first(bst_obj.root, 0) 82 | list_depth.breadth_first(bst_obj.root) 83 | # list_depth.print_list_bfs() 84 | 85 | -------------------------------------------------------------------------------- /stacks_and_queues/animal_shelter.py: -------------------------------------------------------------------------------- 1 | import time 2 | from _datetime import datetime 3 | 4 | 5 | class Animal: 6 | def __init__(self, name): 7 | self.name = name 8 | self.time_added = datetime.now() 9 | 10 | 11 | class Dog(Animal): 12 | pass 13 | 14 | 15 | class Cat(Animal): 16 | pass 17 | 18 | 19 | class AnimalQueue: 20 | def __init__(self): 21 | self.dogs_queue = None 22 | self.cats_queue = None 23 | 24 | def dequeue_any(self): 25 | if not self.cats_queue and not self.dogs_queue: 26 | print("NO CATS AND DOGS AT THE MOMENT...") 27 | return None 28 | if not self.dogs_queue: 29 | return self.cats_queue.pop() 30 | elif not self.cats_queue: 31 | return self.dogs_queue.pop() 32 | else: 33 | if self.dogs_queue.head.data.time_added < self.cats_queue.head.data.time_added: 34 | return self.dogs_queue.pop() 35 | else: 36 | return self.cats_queue.pop() 37 | 38 | def enqueue(self, animal): 39 | # import pdb; pdb.set_trace() 40 | if isinstance(animal, Dog): 41 | if self.dogs_queue: 42 | self.dogs_queue.insertatend(animal) 43 | else: 44 | new_list = LinkedList() 45 | new_list.insertatend(animal) 46 | self.dogs_queue = new_list 47 | 48 | if isinstance(animal, Cat): 49 | if self.cats_queue: 50 | self.cats_queue.insertatend(animal) 51 | else: 52 | new_list = LinkedList() 53 | new_list.insertatend(animal) 54 | self.cats_queue = new_list 55 | 56 | 57 | class Node: 58 | def __init__(self, data): 59 | self.data = data 60 | self.next = None 61 | 62 | 63 | class LinkedList: 64 | def __init__(self): 65 | self.head = None 66 | 67 | def traverse(self): 68 | temp = self.head 69 | while temp: 70 | print(temp.data, end=" -> ") 71 | temp = temp.next 72 | 73 | def push(self, new_data): 74 | print("New data:{}".format(str(new_data))) 75 | new_node = Node(new_data) 76 | new_node.next = self.head 77 | self.head = new_node 78 | 79 | def pop(self): 80 | if self.head: 81 | temp = self.head 82 | self.head = self.head.next 83 | return temp 84 | else: 85 | return None 86 | 87 | def insertafter(self, prev_node, new_data): 88 | new_node = Node(new_data) 89 | new_node.next = prev_node.next 90 | prev_node.next = new_node 91 | 92 | def insertatend(self, new_data): 93 | new_node = Node(new_data) 94 | if self.head is None: 95 | self.head = new_node 96 | return 97 | last = self.head 98 | while (last.next): 99 | last = last.next 100 | 101 | last.next = new_node 102 | 103 | 104 | if __name__ == "__main__": 105 | aq = AnimalQueue() 106 | aq.enqueue(Dog("Meggie")) 107 | time.sleep(5) 108 | aq.enqueue(Cat("Milo")) 109 | time.sleep(5) 110 | aq.enqueue(Cat("Thor")) 111 | time.sleep(5) 112 | aq.enqueue(Dog("Cooper")) 113 | time.sleep(5) 114 | aq.enqueue(Cat("Leo")) 115 | animal = aq.dequeue_any() 116 | print(animal.data.name) 117 | -------------------------------------------------------------------------------- /moderate_problems/lru_cache.py: -------------------------------------------------------------------------------- 1 | class Cache: 2 | def __init__(self): 3 | self.cache_max_size = 5 4 | self.cache_dict = dict() 5 | self.dll = DLL() 6 | 7 | def insert(self, key, value): 8 | if key not in self.cache_dict: 9 | if not self.cache_is_full(): 10 | self.cache_dict[key] = self.dll.insert(key, value) 11 | else: 12 | least_recently_used_key = self.find_lru() 13 | self.remove_key(least_recently_used_key) 14 | self.cache_dict[key] = self.dll.insert(key, value) 15 | else: 16 | print("Key already present") 17 | 18 | def retrieve(self, key): 19 | if key in self.cache_dict: 20 | self.update_most_recently_used(key) 21 | return self.cache_dict[key].value 22 | else: 23 | print(f"No data for {key} found in cache") 24 | return None 25 | 26 | def print_cache(self, key): 27 | if key in self.cache_dict: 28 | print(self.cache_dict[key].value) 29 | else: 30 | print(f"Key {key} not found") 31 | 32 | def cache_is_full(self): 33 | if len(self.cache_dict) >= self.cache_max_size: 34 | return True 35 | else: 36 | return False 37 | 38 | def find_lru(self): 39 | lru_node = self.dll.tail 40 | self.dll.remove_last() 41 | return lru_node.key 42 | 43 | def update_most_recently_used(self, key): 44 | node = self.cache_dict[key] 45 | self.dll.move_to_front(node) 46 | 47 | def remove_key(self, key): 48 | self.dll.remove_node(self.cache_dict[key]) 49 | del self.cache_dict[key] 50 | 51 | 52 | class Node: 53 | def __init__(self, key, val): 54 | self.key = key 55 | self.value = val 56 | self.prev = None 57 | self.next = None 58 | 59 | 60 | class DLL: 61 | def __init__(self): 62 | self.head = None 63 | self.tail = None 64 | 65 | def insert(self, key, value): 66 | node = Node(key, value) 67 | if not self.head: 68 | self.head = node 69 | self.tail = self.head 70 | else: 71 | temp = self.head 72 | temp.prev = node 73 | node.next = temp 74 | self.head = node 75 | return node 76 | 77 | def remove_last(self): 78 | temp = self.tail 79 | self.tail = temp.prev 80 | temp.prev.next = None 81 | 82 | def remove_node(self, node): 83 | # if only one node present 84 | if self.head == self.tail == node: 85 | self.head = None 86 | self.tail = None 87 | else: 88 | prev_node = node.prev 89 | next_node = node.next 90 | if prev_node: 91 | prev_node.next = next_node 92 | else: 93 | self.head = next_node 94 | 95 | def move_to_front(self, node): 96 | # while node.prev: 97 | # prev_node = node.prev 98 | # next_node = node.next 99 | # node.prev = prev_node.prev 100 | # node.next = prev_node 101 | # prev_node.next = next_node 102 | # self.head = node 103 | self.remove_node(node) 104 | self.insert(node.key, node.value) 105 | 106 | 107 | c = Cache() 108 | c.insert(12, "Nirjhari") 109 | c.insert(13, "Pankhuri") 110 | c.insert(14, "Puneet") 111 | c.insert(15, "Nisheeth") 112 | c.insert(11, "Kashvi") 113 | print(c.retrieve(15)) 114 | print(c.retrieve(11)) 115 | print(c.retrieve(13)) 116 | print(c.retrieve(14)) 117 | c.insert(16, 'Ravi') 118 | c.print_cache(12) 119 | -------------------------------------------------------------------------------- /linked_list/linked_list_operations.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data): 3 | self.data = data 4 | self.next = None 5 | 6 | 7 | class LinkedList: 8 | def __init__(self): 9 | self.head = None 10 | self.tail = None 11 | 12 | def traverse(self): 13 | temp = self.head 14 | while temp: 15 | print(temp.data, end=" -> ") 16 | temp = temp.next 17 | 18 | def push(self, new_data): 19 | # print("New data:{}".format(str(new_data))) 20 | new_node = Node(new_data) 21 | new_node.next = self.head 22 | if self.head is None: 23 | self.tail = new_node 24 | self.head = new_node 25 | 26 | def pop(self): 27 | if self.head: 28 | temp = self.head 29 | self.head = self.head.next 30 | return temp 31 | else: 32 | return None 33 | 34 | def insertafter(self, prev_node, new_data): 35 | new_node = Node(new_data) 36 | new_node.next = prev_node.next 37 | prev_node.next = new_node 38 | 39 | def insertatend(self, new_data): 40 | new_node = Node(new_data) 41 | if self.head is None: 42 | self.head = new_node 43 | self.tail = new_node 44 | return 45 | last = self.head 46 | while (last.next): 47 | last = last.next 48 | 49 | last.next = new_node 50 | self.tail = new_node 51 | 52 | def delete(self, key): 53 | print("deleting data: {}".format(str(key))) 54 | temp = self.head 55 | if temp: 56 | if temp.data == key: 57 | self.head = temp.next 58 | temp = None 59 | return 60 | prev_node = temp 61 | while temp: 62 | if temp.data == key: 63 | break 64 | prev_node = temp 65 | temp = temp.next 66 | 67 | if temp is None: 68 | return 69 | 70 | prev_node.next = temp.next 71 | 72 | def reverse_print(self): 73 | print("reverse_print") 74 | if self.head is None: 75 | return 76 | temp = self.head 77 | self.head = self.head.next 78 | self.reverse_print() 79 | print(temp.data) 80 | 81 | def reverse(self): 82 | print("inside reverse") 83 | prev_node = None 84 | while self.head.next: 85 | next_node = self.head.next 86 | self.head.next = prev_node 87 | prev_node = self.head 88 | self.head = next_node 89 | 90 | self.head.next = prev_node 91 | 92 | def remove_duplicates(self): 93 | temp_dict = dict() 94 | temp = self.head 95 | prev_node = temp 96 | while temp: 97 | if temp.data in temp_dict: 98 | prev_node.next = temp.next 99 | temp = temp.next 100 | else: 101 | temp_dict[temp.data] = 1 102 | prev_node = temp 103 | temp = temp.next 104 | 105 | def remove_duplicates_without_buffer(self): 106 | p1_ptr = self.head 107 | p2_ptr = self.head.next 108 | while p1_ptr: 109 | data = p1_ptr.data 110 | prev_node = p1_ptr 111 | while p2_ptr: 112 | if p2_ptr.data == data: 113 | prev_node.next = p2_ptr.next 114 | else: 115 | prev_node = p2_ptr 116 | p2_ptr = p2_ptr.next 117 | p1_ptr = p1_ptr.next 118 | if p1_ptr: 119 | p2_ptr = p1_ptr.next 120 | else: 121 | break 122 | 123 | def reverse_list(self, node, prev_node): 124 | if node is None: 125 | self.head = prev_node 126 | return 127 | self.reverse_list(node.next, node) 128 | node.next = prev_node 129 | 130 | def add_one(self, node): 131 | if not node: 132 | return 1 133 | 134 | val = self.add_one(node.next) 135 | val += node.data 136 | 137 | carry = val // 10 138 | node.data = val % 10 139 | 140 | return carry 141 | 142 | 143 | if __name__ == '__main__': 144 | llist = LinkedList() 145 | llist.push(9) 146 | # llist.push(7) 147 | llist.push(9) 148 | llist.push(9) 149 | # llist.push(2) 150 | llist.push(9) 151 | # llist.reverse() 152 | # llist.remove_duplicates_without_buffer() 153 | # llist.traverse() 154 | # llist.reverse_list(llist.head, None) 155 | # llist.traverse() 156 | carry = llist.add_one(llist.head) 157 | if carry: 158 | llist.push(carry) 159 | llist.traverse() 160 | 161 | -------------------------------------------------------------------------------- /graphs/bidirectional_search.py: -------------------------------------------------------------------------------- 1 | # Python 3 Program for Bidirectional BFS Search to check path between two vertices 2 | 3 | 4 | # Class Definition for Node to be added to graph 5 | class AdjacentNode: 6 | def __init__(self, vertex): 7 | self.vertex = vertex 8 | self.next = None 9 | 10 | 11 | # BidirectionalSearch implementation 12 | class BidirectionalSearch: 13 | def __init__(self, vertices): 14 | # Initialize vertices and graph with vertices 15 | self.vertices = vertices 16 | self.graph = [None] * self.vertices 17 | # initializing queue for forward and backward search 18 | self.src_queue = list() 19 | self.dest_queue = list() 20 | # Initializing source and destination visited nodes as False 21 | self.src_visited = [False] * self.vertices 22 | self.dest_visited = [False] * self.vertices 23 | # initializing source and destination parent nodes 24 | self.src_parent = [None] * self.vertices 25 | self.dest_parent = [None] * self.vertices 26 | 27 | def add_edge(self, src, dest): 28 | # Add edges to graph 29 | 30 | # Add source to destination 31 | node = AdjacentNode(dest) 32 | node.next = self.graph[src] 33 | self.graph[src] = node 34 | 35 | # since graph is undirected add destination to source 36 | node = AdjacentNode(src) 37 | node.next = self.graph[dest] 38 | self.graph[dest] = node 39 | 40 | def bfs(self, direction='forward'): 41 | if direction == 'forward': 42 | # BFS in forward direction 43 | current = self.src_queue.pop(0) 44 | connected_node = self.graph[current] 45 | while connected_node: 46 | vertex = connected_node.vertex 47 | if not self.src_visited[vertex]: 48 | self.src_queue.append(vertex) 49 | self.src_visited[vertex] = True 50 | self.src_parent[vertex] = current 51 | connected_node = connected_node.next 52 | else: 53 | # BFS in backward direction 54 | current = self.dest_queue.pop(0) 55 | connected_node = self.graph[current] 56 | while connected_node: 57 | vertex = connected_node.vertex 58 | if not self.dest_visited[vertex]: 59 | self.dest_queue.append(vertex) 60 | self.dest_visited[vertex] = True 61 | self.dest_parent[vertex] = current 62 | connected_node = connected_node.next 63 | 64 | def is_intersecting(self): 65 | # Returns intersecting node if present else -1 66 | for i in range(self.vertices): 67 | if self.src_visited[i] and self.dest_visited[i]: 68 | return i 69 | return -1 70 | 71 | def print_path(self, intersecting_node, src, dest): 72 | # Print final path from source to destination 73 | path = list() 74 | path.append(intersecting_node) 75 | i = intersecting_node 76 | while i != src: 77 | path.append(self.src_parent[i]) 78 | i = self.src_parent[i] 79 | path = path[::-1] 80 | i = intersecting_node 81 | while i != dest: 82 | path.append(self.dest_parent[i]) 83 | i = self.dest_parent[i] 84 | print("******* Path *******") 85 | path = list(map(str, path)) 86 | print(' '.join(path)) 87 | 88 | def bidirectional_search(self, src, dest): 89 | # Add source to queue and mark visited as True and add its parent as -1 90 | self.src_queue.append(src) 91 | self.src_visited[src] = True 92 | self.src_parent[src] = -1 93 | # Add destination to queue and mark visited as True and add its parent as -1 94 | self.dest_queue.append(dest) 95 | self.dest_visited[dest] = True 96 | self.dest_parent[dest] = -1 97 | 98 | while self.src_queue and self.dest_queue: 99 | # BFS in forward direction from Source Vertex 100 | self.bfs(direction='forward') 101 | # BFS in reverse direction from Destination Vertex 102 | self.bfs(direction='backward') 103 | # Check for intersecting vertex 104 | intersecting_node = self.is_intersecting() 105 | # If intersecting vertex exists then path from source to destination exists 106 | if intersecting_node != -1: 107 | print(f"Path exists between {src} and {dest}") 108 | print(f"Intersecting node is: {intersecting_node}") 109 | self.print_path(intersecting_node, src, dest) 110 | exit(0) 111 | return -1 112 | 113 | 114 | if __name__ == '__main__': 115 | # Number of Vertices in graph 116 | n = 15 117 | # Source Vertex 118 | src = 0 119 | # Destination Vertex 120 | dest = 14 121 | # Create a graph 122 | graph = BidirectionalSearch(n) 123 | graph.add_edge(0, 4) 124 | graph.add_edge(1, 4) 125 | graph.add_edge(2, 5) 126 | graph.add_edge(3, 5) 127 | graph.add_edge(4, 6) 128 | graph.add_edge(5, 6) 129 | graph.add_edge(6, 7) 130 | graph.add_edge(7, 8) 131 | graph.add_edge(8, 9) 132 | graph.add_edge(8, 10) 133 | graph.add_edge(9, 11) 134 | graph.add_edge(9, 12) 135 | graph.add_edge(10, 13) 136 | graph.add_edge(10, 14) 137 | out = graph.bidirectional_search(src, dest) 138 | if out == -1: 139 | print(f"Path does not exist between {src} and {dest}") 140 | -------------------------------------------------------------------------------- /trees/binary_tree.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class Node: 5 | def __init__(self, data): 6 | self.data = data 7 | self.left_child = None 8 | self.right_child = None 9 | self.parent = None 10 | self.size = 1 11 | 12 | 13 | class BinarySearchTree: 14 | def __init__(self): 15 | self.root = None 16 | self.count = 0 17 | self.bottom_view_dict = defaultdict(list) 18 | 19 | def level_order_insert(self, data): 20 | queue = [] 21 | new_node = Node(data) 22 | if self.root: 23 | queue.append(self.root) 24 | while queue: 25 | temp = queue.pop(0) 26 | if not temp.left_child: 27 | temp.left_child = new_node 28 | break 29 | else: 30 | queue.append(temp.left_child) 31 | if not temp.right_child: 32 | temp.right_child = new_node 33 | break 34 | else: 35 | queue.append(temp.right_child) 36 | else: 37 | self.root = new_node 38 | 39 | def insert(self, data): 40 | new_node = Node(data) 41 | if not self.root: 42 | self.root = new_node 43 | else: 44 | temp = self.root 45 | prev_node = temp 46 | while temp is not None: 47 | prev_node = temp 48 | if new_node.data < temp.data: 49 | new_node.parent = prev_node 50 | temp = temp.left_child 51 | else: 52 | new_node.parent = prev_node 53 | temp = temp.right_child 54 | if data < prev_node.data: 55 | new_node.parent = prev_node 56 | prev_node.left_child = new_node 57 | elif data > prev_node.data: 58 | new_node.parent = prev_node 59 | prev_node.right_child = new_node 60 | 61 | def in_order(self, node): 62 | """ 63 | LEFT -- ROOT -- RIGHT 64 | :return: 65 | """ 66 | if node is not None: 67 | self.in_order(node.left_child) 68 | print(node.data) 69 | self.in_order(node.right_child) 70 | 71 | def pre_order(self, root): 72 | """ 73 | ROOT -- LEFT -- RIGHT 74 | :return: 75 | """ 76 | if root is None: 77 | return 78 | stack = [root] 79 | while stack: 80 | item = stack.pop() 81 | print(item.data) 82 | if item.right_child: 83 | stack.append(item.right_child) 84 | if item.left_child: 85 | stack.append(item.left_child) 86 | 87 | def post_order(self, root): 88 | stack = [] 89 | while True: 90 | while root: 91 | if root.right_child is not None: 92 | stack.append(root.right_child) 93 | stack.append(root) 94 | root = root.left_child 95 | 96 | 97 | root = stack.pop() 98 | 99 | if not stack: 100 | print(root.data) 101 | break 102 | 103 | if root.right_child is not None and stack[-1] == root.right_child: 104 | stack.pop() 105 | stack.append(root) 106 | root = root.right_child 107 | else: 108 | print(root.data) 109 | root = None 110 | 111 | def in_order_successor(self, node): 112 | if node.right_child: 113 | successor = self.find_min(node.right_child) 114 | return successor 115 | 116 | parent = node.parent 117 | while parent: 118 | if node != parent.right_child: 119 | break 120 | node = parent 121 | parent = node.parent 122 | return parent 123 | 124 | def find_min(self, node): 125 | min = node.data 126 | while node.left_child: 127 | min = node.left_child.data 128 | node = node.left_child 129 | return min 130 | 131 | def generate_random_node(self): 132 | pass 133 | 134 | def count_nodes(self, node): 135 | if node is None: 136 | return 137 | self.count_nodes(node.left_child) 138 | self.count += 1 139 | self.count_nodes(node.right_child) 140 | 141 | def mirror(self, root): 142 | if root is None: 143 | return 144 | 145 | self.mirror(root.left_child) 146 | self.mirror(root.right_child) 147 | 148 | root.right_child, root.left_child = root.left_child, root.right_child 149 | 150 | def inorder_iterative(self, root): 151 | stack = [] 152 | current = root 153 | 154 | while True: 155 | if current is not None: 156 | stack.append(current) 157 | current = current.left_child 158 | elif stack: 159 | current = stack.pop() 160 | print(current.data) 161 | current = current.right_child 162 | else: 163 | break 164 | 165 | def print_boundary(self, root): 166 | if root: 167 | print(root.data) 168 | self.print_left_boundary(root.left_child) 169 | 170 | self.print_leaves(root.left_child) 171 | self.print_leaves(root.right_child) 172 | 173 | self.print_right_boundary(root.right_child) 174 | 175 | def print_left_boundary(self, root): 176 | if root: 177 | if root.left_child: 178 | print(root.data) 179 | self.print_left_boundary(root.left_child) 180 | elif root.right_child: 181 | print(root.data) 182 | self.print_left_boundary(root.right_child) 183 | 184 | def print_right_boundary(self, root): 185 | if root: 186 | if root.right_child: 187 | self.print_right_boundary(root.right_child) 188 | print(root.data) 189 | elif root.left_child: 190 | self.print_right_boundary(root.left_child) 191 | print(root.data) 192 | 193 | def print_leaves(self, root): 194 | if root: 195 | self.print_leaves(root.left_child) 196 | 197 | if root.left_child is None and root.right_child is None: 198 | print(root.data) 199 | 200 | self.print_leaves(root.right_child) 201 | 202 | def left_view(self, root, level, max_level): 203 | if root is None: 204 | return 205 | if max_level < level: 206 | print(root.data) 207 | max_level = level 208 | 209 | self.left_view(root.left_child, level+1, max_level) 210 | self.left_view(root.right_child, level+1, max_level) 211 | 212 | def bottom_view(self, root, hd=0): 213 | if root is None: 214 | return 215 | self.bottom_view_dict[hd].append(root.data) 216 | self.bottom_view(root.left_child, hd - 1) 217 | self.bottom_view(root.right_child, hd + 1) 218 | 219 | def spiral_traversal(self, root, ltr=True): 220 | queue = [root] 221 | while queue: 222 | item = queue.pop(0) 223 | print(item.data) 224 | if not queue: 225 | ltr = not ltr 226 | if ltr: 227 | if item.left_child: 228 | queue.append(item.left_child) 229 | if item.right_child: 230 | queue.append(item.right_child) 231 | else: 232 | if item.right_child: 233 | queue.append(item.right_child) 234 | if item.left_child: 235 | queue.append(item.left_child) 236 | 237 | def level_order_traversal(self, root): 238 | queue = [root] 239 | while queue: 240 | item = queue.pop(0) 241 | print(item.data) 242 | if item.left_child: 243 | queue.append(item.left_child) 244 | if item.right_child: 245 | queue.append(item.right_child) 246 | 247 | def print_nodes_k_distance_from_root(self, root, k): 248 | if root is None: 249 | return 250 | if k == 0: 251 | print(root.data) 252 | else: 253 | self.print_nodes_k_distance_from_root(root.left_child, k - 1) 254 | self.print_nodes_k_distance_from_root(root.right_child, k - 1) 255 | 256 | def print_all_nodes_at_k_distance_of_node(self, node, target, k): 257 | if node is None: 258 | return -1 259 | if node.data == target: 260 | self.print_nodes_k_distance_from_root(node, k) 261 | return 0 262 | 263 | left_distance = self.print_all_nodes_at_k_distance_of_node(node.left_child, target, k) 264 | 265 | if left_distance != -1: 266 | if left_distance + 1 == k: 267 | print(node.data) 268 | else: 269 | self.print_nodes_k_distance_from_root(node.right_child, k-2-left_distance) 270 | return left_distance + 1 271 | 272 | right_distance = self.print_all_nodes_at_k_distance_of_node(node.right_child, target, k) 273 | 274 | if right_distance != -1: 275 | if right_distance + 1 == k: 276 | print(node.data) 277 | else: 278 | self.print_nodes_k_distance_from_root(node.left_child, k - 2 - right_distance) 279 | return right_distance + 1 280 | 281 | 282 | if __name__ == "__main__": 283 | bst_obj = BinarySearchTree() 284 | bst_obj.insert(20) 285 | bst_obj.insert(8) 286 | bst_obj.insert(22) 287 | bst_obj.insert(4) 288 | bst_obj.insert(12) 289 | bst_obj.insert(10) 290 | bst_obj.insert(14) 291 | # bst_obj.in_order(bst_obj.root) 292 | # successor = bst_obj.in_order_successor(bst_obj.root.left_child.right_child.left_child) 293 | # if successor: 294 | # print(successor.data) 295 | # bst_obj.count_nodes(bst_obj.root) 296 | # print(bst_obj.count) 297 | # bst_obj.in_order(bst_obj.root) 298 | # bst_obj.mirror(bst_obj.root) 299 | # print("***********") 300 | # bst_obj.in_order(bst_obj.root) 301 | # bst_obj.pre_order(bst_obj.root) 302 | # bst_obj.post_order(bst_obj.root) 303 | # bst_obj.bottom_view(bst_obj.root) 304 | # print(bst_obj.bottom_view_dict) 305 | # bst_obj.level_order_traversal(bst_obj.root) 306 | bst_obj.spiral_traversal(bst_obj.root) 307 | --------------------------------------------------------------------------------