├── CMakeLists.txt ├── LICENSE ├── README.md ├── TODO.md ├── c_tests ├── Makefile └── testlib.h ├── core_concepts ├── bracket_resolver.c ├── play.c ├── quine.c ├── variable_size.c └── variadic_function.c ├── dynamic_programming ├── find_fib.c └── longest_palindrome.py ├── graph ├── bfs_undirected_graph.py ├── demo_union_find_adt.py ├── demo_weighted_undirected_graph_adt.py ├── dfs_undirected_graph.py ├── mst_kruskal.py ├── mst_prims.py └── union_find_adt.py ├── hashing ├── test_three_sum.py ├── test_two_sum.py ├── three_sum.py └── two_sum.py ├── heap ├── continuous_median.py ├── k_largest_elements_array.c ├── k_largest_elements_array.py ├── k_largest_elements_immutable_max_heap.py ├── test_continuous_median.py ├── test_k_largest_elements_array.c ├── test_k_largest_elements_array.py └── test_k_largest_elements_immutable_max_heap.py ├── sorting └── trouble_sort.c └── stack ├── blance_bracket.py ├── reverse_stack.c └── test_blance_bracket.py /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # purpose: add .c files 2 | cmake_minimum_required(VERSION 3.14) 3 | project(ds_algorithm C) 4 | 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | # Include all library code 8 | # ========================================================================================== 9 | # include all source files 10 | # =========================================================================================== 11 | FILE(GLOB stack_singly_linklist_int libO2/src/stack_singly_linkedlist_int.c) 12 | FILE(GLOB queue_singly_linklist_int libO2/src/queue_singly_linkedlist_int.c) 13 | FILE(GLOB binary_heap_dynamic_array_int libO2/src/binary_heap_dynamic_array_int.c) 14 | FILE(GLOB sort libO2/src/sort_int.c) 15 | 16 | # ========================================================================================== 17 | # include the executables 18 | # =========================================================================================== 19 | 20 | # core_concepts 21 | add_executable(bracket_resolver core_concepts/bracket_resolver.c) 22 | add_executable(play core_concepts/play.c) 23 | add_executable(quine core_concepts/quine.c) 24 | add_executable(concept core_concepts/variable_size.c) 25 | add_executable(example_variadic_function core_concepts/variadic_function.c) 26 | 27 | # stack 28 | add_executable(example_stack_singly_linklist_int libO2/examples/example_stack_singly_linkedlist_int.c ${stack_singly_linklist_int}) 29 | add_executable(example_stack_singly_linklist_int_02 libO2/examples/example_stack_singly_linkedlist_int_02.c ${stack_singly_linklist_int}) 30 | add_executable(reverse_stack stack/reverse_stack.c ${stack_singly_linklist_int}) 31 | 32 | # queue 33 | add_executable(example_queue_singly_linklist_int 34 | libO2/examples/example_queue_singly_linkedlist_int.c 35 | ${queue_singly_linklist_int}) 36 | 37 | # heap 38 | add_executable(example_binary_heap_dynamic_array_int libO2/examples/example_binary_heap_dynamic_array_int.c 39 | ${binary_heap_dynamic_array_int}) 40 | add_executable(k_largest_elements heap/k_largest_elements_array.c 41 | ${binary_heap_dynamic_array_int}) 42 | 43 | # sorting 44 | add_executable(example_sort_int libO2/examples/example_sort_int.c ${sort}) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ABHIJIT SINHA (grey.shell@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Learning Data Structures and Algorithms using `C` and `Python`. 4 | 5 | ## `C` & Data Structures / Algorithms Reference Materials 6 | 7 | 1. `video`: Algorithm Part 1 and 2 offered by Princeton University 8 | - https://www.coursera.org/learn/algorithms-part1/home/welcome 9 | - https://www.coursera.org/learn/algorithms-part2#syllabus 10 | - https://www.coursera.org/learn/analysis-of-algorithms#syllabus 11 | - Reference `Book`: Algorithms in C by Robert Sedgewick, Part I, II 12 | - https://algs4.cs.princeton.edu/home/ 13 | 2. `video`: Dr. Naveen Garg - IITD 14 | - https://www.youtube.com/watch?v=zWg7U0OEAoE&list=PLBF3763AF2E1C572F 15 | - Reference `Book`: Coreman 16 | 3. `video`: Algorithms Specialization offered by Standford University 17 | - https://www.coursera.org/specializations/algorithms 18 | 4. `video`: Abdul Bari 19 | - https://www.udemy.com/course/datastructurescncpp/learn/lecture/13319372 20 | - https://www.youtube.com/watch?v=0IAPZzGSbME&list=PLDN4rrl48XKpZkf03iYFl-O29szjTrs_O 21 | - Reference `Book`: Data Structures and Algorithms made easy by Narasimha Karumanchi 22 | 5. `problems`: Leetcode Theory | Exercise 23 | - https://leetcode.com/explore/learn/ 24 | - https://leetcode.com/problemset/all/ 25 | 6. `Book`: Modern C by Jens Gustedt 26 | 7. `Book`: C, the complete reference by Herbert Schildt 27 | 8. `Book`: The C Programming Language by Brian Kernighan and Dennis Ritchie 28 | 9. `video`: Ravindrababu Ravula 29 | - https://ravindrababuravula.com/interviewpreperation.php 30 | 10. `video`: Chekuri Srikanth Varma 31 | - https://interviewprep.appliedcourse.com/course/2/Interview-Preparation-Course 32 | - https://gate.appliedcourse.com/course/6/data_arr-structures-and-algorithms 33 | 11. `video`: Clément Mihailescu | AlgoExpert 34 | - https://www.algoexpert.io/questions 35 | 12. `video`: Benyam Ephrem 36 | - https://coding-interview-class.teachable.com/ 37 | 13. `web resource`: `geeksforgeeks.org` 38 | - https://www.geeksforgeeks.org/fundamentals-of-algorithms/ 39 | - https://www.geeksforgeeks.org/heap-data_arr-structure/ 40 | 14. `video`: William Fiset: https://www.youtube.com/user/purpongie/playlists 41 | 42 | ## `python` Reference Materials 43 | 44 | 1. `Book`: Grokking Algorithms by Aditya Y Bhargava 45 | 2. Python 3: Project-based Python, Algorithms, Data Structures 46 | - https://subscription.packtpub.com/playlists/196d6fdd-26ca-4603-a786-59777b7b5687 47 | 3. Advanced Data Structures and Algorithms in Python 48 | - https://subscription.packtpub.com/video/Programming/9781838643157 49 | 4. Hands-On Data Structures and Algorithms with Python - Second Edition 50 | - https://subscription.packtpub.com/book/application_development/9781788995573 51 | 5. `Book`: Python Data Structures and Algorithms by Benjamin Baka (Author) 52 | 6. `Book`: Data Structures and Algorithms Using Python by Rance D 53 | 7. `Book` by Brad Miller: http://interactivepython.org/runestone/static/pythonds/index.html 54 | 8. http://nptel.ac.in/courses/106106145/ 55 | 56 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | # MST 4 | 5 | - implement prims eagar version, understand the benefit over lazy 6 | 7 | ## chapter: undirected graph 8 | 9 | - has_cycle 10 | - has_euler_cycle 11 | - has_hamiltonian_cycle 12 | - is_bipartite 13 | - is_planar 14 | - is_isomorphic 15 | 16 | ## chapter: directed graph 17 | - multiple source shortest path 18 | - precedence scheduling in a DAG 19 | - 6 is done after 3, represents 3 -> 6 20 | 21 | - connected component: 22 | - topological sort: 23 | - reverse post order dfs 24 | - directed cycle detection in a directed graph 25 | - strong component 26 | - find strong components in a digraph 27 | - find shorted path from src to dst in a undirected connected graph 28 | - return None if no path 29 | - if path exists then return the path 30 | 31 | 32 | -------------------------------------------------------------------------------- /c_tests/Makefile: -------------------------------------------------------------------------------- 1 | TEST_PROG=kth_largest_elements_array 2 | FLD=heap 3 | FUZZ_TIME=5s 4 | 5 | PATHU = ../libO2/lib/Unity/src/ 6 | PATHS = ../$(FLD)/ 7 | PATHT = ../$(FLD)/ 8 | DEBUG_ROOT = test_build/ 9 | 10 | PATH_DEBUG = $(DEBUG_ROOT)$(TEST_PROG)/ 11 | PATH_FUZZ = $(PATH_DEBUG)/fuzz/ 12 | PATHB = $(PATH_DEBUG)/unittest/ 13 | PATHD = $(PATHB)/depends/ 14 | PATHO = $(PATHB)/objs/ 15 | PATHR = $(PATHB)/results/ 16 | 17 | BUILD_PATHS = $(DEBUG_ROOT) $(PATH_DEBUG) $(PATHB) $(PATHD) $(PATHO) $(PATHR) $(PATH_FUZZ) 18 | 19 | SRCT = $(wildcard $(PATHT)/$(TEST_PROG).c) 20 | 21 | CLEANUP=rm -f 22 | CLEANUP_DIR=rm -rf 23 | MKDIR=mkdir -p 24 | TARGET_EXTENSION=out 25 | 26 | COMPILE=gcc -c 27 | LINK=gcc 28 | DEPEND=gcc -MM -MG -MF 29 | CFLAGS=-I. -I$(PATHU) -I$(PATHS) -Dtest 30 | COMPILE_AFL=AFL_USE_ASAN=1 afl-gcc 31 | 32 | RESULTS = $(PATHR)test_$(TEST_PROG).txt 33 | 34 | PASSED = `grep -s PASS $(PATHR)*.txt` 35 | FAIL = `grep -s FAIL $(PATHR)*.txt` 36 | IGNORE = `grep -s IGNORE $(PATHR)*.txt` 37 | 38 | .PHONY: clean 39 | .PHONY: test 40 | 41 | all: cppcheck test fuzz 42 | 43 | test: $(BUILD_PATHS) $(RESULTS) 44 | @echo "-----------------------\nIGNORES:\n-----------------------" 45 | @echo "$(IGNORE)" 46 | @echo "-----------------------\nFAILURES:\n-----------------------" 47 | @echo "$(FAIL)" 48 | @echo "-----------------------\nPASSED:\n-----------------------" 49 | @echo "$(PASSED)" 50 | @echo "\nDONE" 51 | 52 | fuzz-build: $(BUILD_PATHS) 53 | $(COMPILE_AFL) $(CFLAGS) $(PATHT)/fuzz_$(TEST_PROG).c $(PATHS)/$(TEST_PROG).c \ 54 | -o $(PATH_FUZZ)/fuzz_$(TEST_PROG) 55 | 56 | fuzz: fuzz-build 57 | timeout $(FUZZ_TIME) afl-fuzz -i $(PATH_FUZZ)/in -o $(PATH_FUZZ)/out \ 58 | -m none $(PATH_FUZZ)/fuzz_$(TEST_PROG) 59 | 60 | cppcheck: $(BUILD_PATHS) 61 | cppcheck $(PATHS)/$(TEST_PROG).c > $(PATH_DEBUG)/cppcheck_report_$(TEST_PROG).txt 62 | 63 | cppcheck-report: cppcheck 64 | @echo "-----------------------\nCPPCHECK REPORT:\n-----------------------" 65 | @cat $(PATH_DEBUG)/cppcheck_report_$(TEST_PROG).txt 66 | 67 | $(PATHR)%.txt: $(PATHB)%.$(TARGET_EXTENSION) 68 | -./$< > $@ 2>&1 69 | 70 | $(PATHB)test_%.$(TARGET_EXTENSION): $(PATHO)test_%.o $(PATHO)%.o $(PATHU)unity.o 71 | $(LINK) -o $@ $^ -lO2 72 | 73 | $(PATHO)%.o:: $(PATHT)%.c 74 | $(COMPILE) $(CFLAGS) $< -o $@ 75 | 76 | $(PATHO)%.o:: $(PATHS)%.c 77 | $(COMPILE) $(CFLAGS) $< -o $@ 78 | 79 | $(PATHO)%.o:: $(PATHU)%.c $(PATHU)%.h 80 | $(COMPILE) $(CFLAGS) $< -o $@ 81 | 82 | $(PATHD)%.d:: $(PATHT)%.c 83 | $(DEPEND) $@ $< 84 | 85 | $(DEBUG_ROOT): 86 | $(MKDIR) $(DEBUG_ROOT) 87 | 88 | $(PATH_DEBUG): 89 | $(MKDIR) $(PATH_DEBUG) 90 | 91 | $(PATHB): 92 | $(MKDIR) $(PATHB) 93 | 94 | $(PATHD): 95 | $(MKDIR) $(PATHD) 96 | 97 | $(PATHO): 98 | $(MKDIR) $(PATHO) 99 | 100 | $(PATHR): 101 | $(MKDIR) $(PATHR) 102 | 103 | $(PATH_FUZZ): 104 | @$(MKDIR) $(PATH_FUZZ) 105 | @$(MKDIR) $(PATH_FUZZ)/in/ 106 | @echo abc > $(PATH_FUZZ)/in/t1 107 | 108 | clean: 109 | $(CLEANUP_DIR) $(PATH_DEBUG) 110 | 111 | clean-all: 112 | $(CLEANUP_DIR) $(DEBUG_ROOT) 113 | 114 | .PRECIOUS: $(PATHB)test_%.$(TARGET_EXTENSION) 115 | .PRECIOUS: $(PATHD)%.d 116 | .PRECIOUS: $(PATHO)%.o 117 | .PRECIOUS: $(PATHR)%.txt 118 | -------------------------------------------------------------------------------- /c_tests/testlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * */ 4 | 5 | #ifndef UNIT_TEST_H__ 6 | #define UNIT_TEST_H__ 7 | 8 | // heap: kth_largest_elements_array.c 9 | void k_largest_elements(int *, int, int, int *); 10 | void k_largest_elements_02(int *, int, int, int *); 11 | 12 | #endif // UNIT_TEST_H__ 13 | 14 | -------------------------------------------------------------------------------- /core_concepts/bracket_resolver.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char *minRemoveToMakeValid(char *s) { 4 | int i = 0, n = 0, count = 0, length = 0; 5 | int flag = 0, is_seen = 0; 6 | char test_char; 7 | // iterate the string and identify the invalid ')' char 8 | // if found then replace that with '-' char 9 | while (s[i] != '\0') { 10 | // if ) is seen before the ( 11 | if (is_seen == 0 && s[i] == ')') { 12 | s[i] = '-'; 13 | } else if (s[i] == ')') { 14 | flag--; 15 | } else if (s[i] == '(') { 16 | is_seen = 1; 17 | flag++; 18 | } 19 | 20 | if (flag < 0) { 21 | s[i] = '-'; 22 | flag++; 23 | } 24 | i++; 25 | } 26 | length = i; 27 | 28 | if (flag < 0) { 29 | count = -flag; 30 | test_char = ')'; 31 | } else { 32 | count = flag; 33 | test_char = '('; 34 | } 35 | 36 | n = 1; 37 | for (int k = length - 1; k >= 0; k--) { 38 | if (s[k] == test_char && n <= count) { 39 | s[k] = '-'; 40 | n++; 41 | } 42 | } 43 | 44 | // remove the - and shift the valid characters 45 | n = i = 0; 46 | while (s[i] != '\0') { 47 | if (s[i] != '-') { 48 | s[n++] = s[i]; 49 | } 50 | i++; 51 | } 52 | s[n] = '\0'; 53 | 54 | return s; 55 | } 56 | 57 | int main(void) { 58 | char *r; 59 | char s[] = "())()((("; 60 | r = minRemoveToMakeValid(s); 61 | printf("\noutput: %s", r); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /core_concepts/play.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: random experiment 4 | * */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct { 11 | bool type; // non zero -> max heap 12 | size_t size; 13 | size_t initial_capacity; 14 | size_t current_capacity; 15 | int *data_arr; 16 | } heap; 17 | 18 | int main(void) { 19 | size_t b = 5; 20 | heap h; 21 | 22 | printf("floor = %d\n", h.type); 23 | 24 | printf("floor = %zu\n", (size_t) floor((double) b / 2)); 25 | 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /core_concepts/quine.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: quine programming: a program that display its own source code 4 | * */ 5 | 6 | #include 7 | #include 8 | 9 | int main(void) { 10 | FILE *fp; 11 | char c; 12 | // __FILE__ contains the location of this C programming file in a string 13 | fp = fopen(__FILE__, "r"); 14 | if (fp == NULL) { 15 | printf("cannot open file \n"); 16 | exit(0); 17 | } 18 | 19 | // read contents from file 20 | c = fgetc(fp); 21 | while (c != EOF) { 22 | printf("%c", c); 23 | c = fgetc(fp); 24 | } 25 | 26 | fclose(fp); 27 | return 0; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /core_concepts/variable_size.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: quick proof of concept 4 | * */ 5 | 6 | #include 7 | #include "stdbool.h" 8 | 9 | struct student { 10 | int i; 11 | char ch; 12 | float f; 13 | }; 14 | 15 | int main(void){ 16 | bool my_bool; 17 | int i; 18 | size_t j = 9; 19 | unsigned int k; 20 | long int l; 21 | long long int m; 22 | struct student s; 23 | char a; 24 | float f; 25 | 26 | printf("bool size = %lu \n", sizeof(my_bool)); // 1 byte 27 | printf("signed int size = %lu \n", sizeof(i)); // 4 bytes 28 | 29 | printf("unsigned int size = %lu \n", sizeof(k)); // 4 bytes 30 | printf("long int size = %lu \n", sizeof(l)); // 8 bytes 31 | printf("long long size = %lu \n", sizeof(m)); // 8 bytes 32 | printf("size_t size = %lu \n", sizeof(j)); // 8 bytes 33 | 34 | printf("singed char size = %lu \n", sizeof(a)); // 1 bytes 35 | printf("signed float size = %lu \n", sizeof(f)); // 4 bytes 36 | printf("student structure size = %lu \n", sizeof(s)); // 12 bytes, although total size is 9 bytes 37 | return 0; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /core_concepts/variadic_function.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: how to use and declare a variadic function 4 | * */ 5 | 6 | #include 7 | #include 8 | 9 | int add_em_up(int count, ...) { 10 | va_list arg; 11 | int i, sum; 12 | 13 | va_start (arg, count); /* Initialize the argument list. */ 14 | 15 | sum = 0; 16 | for (i = 0; i < count; i++) 17 | sum += va_arg (arg, int); /* Get the next argument value. */ 18 | 19 | va_end (arg); /* Clean up. */ 20 | return sum; 21 | } 22 | 23 | int main(void) { 24 | /* This call prints 16. */ 25 | printf("%d\n", add_em_up(3, 5, 5, 6)); 26 | 27 | /* This call prints 55. */ 28 | printf("%d\n", add_em_up(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 29 | 30 | return 0; 31 | } -------------------------------------------------------------------------------- /dynamic_programming/find_fib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: find the 5th fibonacci number 4 | * if n = 7 then output = 13 5 | * considering 0th fib = 0, 1st fib = 1 6 | * */ 7 | 8 | #include 9 | 10 | size_t fib(size_t n) { 11 | /* 12 | * property: 13 | * ========== 14 | * - recursive solution 15 | * - recurrent relation: F(n) = F(n-1) + F(n-2), seed values F(0) = 0 and F(1) = 1 16 | * 17 | * time complexity: 18 | * =============== 19 | * - actual program: time complexity = O(2^n) 20 | * - due to DP, time complexity reduced to O(n) for only having few fib(n-1) call 21 | * 22 | * space complexity: 23 | * ================= 24 | * - O(n) -> max size of the call stack 25 | * - O(n) -> dynamic_programming array size 26 | */ 27 | // TO DO: dynamically generates the array based on the input. 28 | static size_t dp[4] = {0}; // initialized the entire array with 0 29 | 30 | // pick the saved value -> O(1), greater than zero means it filled up earlier 31 | if (dp[n] > 0) { 32 | return dp[n]; // bug: when n > 9, the unallocated memory will be overwritten with fib number 33 | } 34 | // base case 1 35 | if (n == 0) { 36 | return 0; 37 | } 38 | // base case 1 39 | if (n == 1) { 40 | return 1; 41 | } 42 | 43 | // normal recursive call where value is not seen 44 | dp[n] = fib(n - 1) + fib(n - 2); 45 | return dp[n]; 46 | } 47 | 48 | int main(void) { 49 | // sample tests case 50 | size_t number = 5; 51 | // function call 52 | printf("\n%zu th fib number = %zu", number, fib(number)); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /dynamic_programming/longest_palindrome.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | """ 6 | description: 7 | Given a string, find the longest substring which is palindrome. 8 | For example, if the given string is "forgeeksskeegfor", 9 | the output should be "geeksskeeg". 10 | 11 | reference: 12 | https://www.geeksforgeeks.org/longest-palindrome-substring-set-1/ 13 | 14 | """ 15 | 16 | 17 | def calculate_palindrome(my_string: str, dp_matrix: list, i: int, j: int) -> int: 18 | """ 19 | 20 | :param my_string: 21 | :param dp_matrix: 22 | :param i: 23 | :param j: 24 | :return: 25 | """ 26 | # base case scenarios 27 | if i == j: # check for 1 char 28 | dp_matrix[i][j] = 1 # consider 1 char is palindrome 29 | return 1 30 | if (i + 1) == j: # check for 2 chars 31 | if my_string[i] == my_string[j]: 32 | dp_matrix[i][j] = 1 # palindrome when both chars are same 33 | else: 34 | dp_matrix[i][j] = 0 # not palindrome when both chars are not same 35 | return dp_matrix[i][j] 36 | 37 | # top down approach: already calculated cases 38 | if dp_matrix[i][j] != -1: 39 | return dp_matrix[i][j] 40 | 41 | # consider all cases where string length > = 3 and not evaluated 42 | is_palindrome = calculate_palindrome(my_string, dp_matrix, (i + 1), 43 | (j - 1)) # check if the sub_string is palindrome 44 | # check if the 1st and the last char is same and sub_string is also palindrome 45 | if (my_string[i] == my_string[j]) and is_palindrome: 46 | dp_matrix[i][j] = 1 47 | else: 48 | dp_matrix[i][j] = 0 49 | 50 | return dp_matrix[i][j] 51 | 52 | 53 | def longest_palindrome(my_string: str) -> str: 54 | """ 55 | time complexity: 56 | - O(n^2) -> for the nested loops 57 | - calculate_palindrome() -> will be n^2 times as it is inside the nested loops 58 | - but due to the memoization matrix after certain call it will give O(1) complexity 59 | 60 | space complexity: 61 | for dp_matrix => O(n^2) 62 | stack call => O(1) => constant 63 | """ 64 | str_len = len(my_string) 65 | 66 | # initialize a 2d array with -1 67 | w, h = str_len, str_len 68 | dp_matrix = [[-1 for y in range(w)] for x in range(h)] 69 | # objective is to set 1 if the substring(i, j) is palindrome, else set 0 70 | 71 | max_str = "" 72 | max_len = -1 73 | for i in range(str_len): 74 | for j in range(i, str_len): 75 | is_palin = calculate_palindrome(my_string, dp_matrix, i, j) # it returns 1 if 76 | # palindrome 77 | if is_palin and (j - i + 1) > max_len: # not consider the case 'a' 78 | max_len = (j - i) + 1 79 | max_str = my_string[i:j + 1] # returns a new string as python strings are immutable 80 | 81 | return max_str 82 | 83 | 84 | def main(): 85 | # sample test case 86 | my_string = "abcbde" 87 | my_string = "forgeeksskeegfor" 88 | result = longest_palindrome(my_string) 89 | print(f"[+] largest palindrome: {result}, length: {len(result)}") 90 | 91 | 92 | if __name__ == '__main__': 93 | main() 94 | -------------------------------------------------------------------------------- /graph/bfs_undirected_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: bfs traversals on a connected undirected graph 5 | 6 | from collections import deque 7 | from graph import UndirectedGraph 8 | 9 | 10 | def bfs(ug, start_vertex): 11 | """ 12 | breadth first search 13 | time complexity: O(V + E) 14 | space complexity: O(V) -> to maintain the visited set 15 | """ 16 | # check if the start vertex_name is present in the graph 17 | if not ug.get_vertex_obj(start_vertex): 18 | return False 19 | 20 | # track the visited vertices 21 | visited = set() 22 | # add the start vertex_name into the visited set 23 | visited.add(start_vertex) 24 | 25 | # add the start vertex_name into the queue 26 | queue = deque() 27 | queue.append(start_vertex) 28 | 29 | while queue: 30 | # pop the vertex_name from the queue and process 31 | vertex = queue.popleft() 32 | print(vertex, end=" ") 33 | 34 | vertex_obj = ug.vertices[vertex] 35 | # iterate all neighbors of that vertex_name 36 | for edge_obj in vertex_obj.neighbor_edges_obj: 37 | neighbor = edge_obj.dst_vertex 38 | # if that neighbor is not in the visited set then add to the queue and set 39 | if neighbor not in visited: 40 | visited.add(neighbor) 41 | queue.append(neighbor) 42 | 43 | 44 | def main(): 45 | # ref: Sedgewick Algorithms 4th edition, page 532 46 | ug = UndirectedGraph() 47 | # add vertices 48 | for vertex in range(0, 6): 49 | ug.add_vertex(str(vertex)) 50 | 51 | # add edges 52 | ug.add_edge("0", "1") 53 | ug.add_edge("0", "5") 54 | ug.add_edge("0", "2") 55 | ug.add_edge("1", "2") 56 | ug.add_edge("2", "3") 57 | ug.add_edge("2", "4") 58 | ug.add_edge("3", "4") 59 | ug.add_edge("3", "5") 60 | 61 | start_vertex = "0" 62 | 63 | print(f"bfs traversal: ") 64 | bfs(ug, start_vertex) 65 | 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /graph/demo_union_find_adt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | from union_find_adt import QuickFind, QuickUnion, WeightedQuickUnion 6 | 7 | 8 | def main(): 9 | # robert sedgewick page: 221 10 | vertices = [str(n) for n in range(0, 10)] 11 | edges = (("4", "3"), 12 | ("3", "8"), 13 | ("6", "5"), 14 | ("9", "4"), 15 | ("2", "1"), 16 | ("5", "0"), 17 | ("7", "2"), 18 | ("6", "1") 19 | ) 20 | 21 | print("\ndemo QuickFind") 22 | uf = QuickFind(vertices) 23 | for edge in edges: 24 | if uf.connected(edge[0], edge[1]): 25 | continue 26 | uf.union(edge[0], edge[1]) 27 | 28 | print(f"is 0 connected with 6: {uf.connected('0', '6')}") 29 | print(f"is 0 connected with 8: {uf.connected('0', '8')}") 30 | print(f"total components: {uf.get_components()}") 31 | # lookup dict final state 32 | # for k, v in uf._vertices_lookup.items(): 33 | # print(f"{k} => {v}") 34 | 35 | print("\ndemo QuickUnion") 36 | uf2 = QuickUnion(vertices) 37 | for edge in edges: 38 | if uf2.connected(edge[0], edge[1]): 39 | continue 40 | uf2.union(edge[0], edge[1]) 41 | 42 | print(f"is 0 connected with 6: {uf2.connected('0', '6')}") 43 | print(f"is 0 connected with 8: {uf2.connected('0', '8')}") 44 | print(f"total components: {uf2.get_components()}") 45 | 46 | print("\ndemo WeightedQuickUnion") 47 | uf3 = WeightedQuickUnion(vertices) 48 | for edge in edges: 49 | if uf3.connected(edge[0], edge[1]): 50 | continue 51 | uf3.union(edge[0], edge[1]) 52 | 53 | print(f"is 0 connected with 6: {uf3.connected('0', '6')}") 54 | print(f"is 0 connected with 8: {uf3.connected('0', '8')}") 55 | print(f"total components: {uf3.get_components()}") 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /graph/demo_weighted_undirected_graph_adt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: demo undirected graph adt 5 | 6 | from graph import UndirectedGraph 7 | 8 | 9 | def main(): 10 | # ref: Sedgewick Algorithms 4th edition, page 614 11 | wug = UndirectedGraph() 12 | # add vertices 13 | wug.add_vertex("0") 14 | wug.add_vertex("1") 15 | wug.add_vertex("2") 16 | wug.add_vertex("3") 17 | wug.add_vertex("4") 18 | wug.add_vertex("5") 19 | wug.add_vertex("6") 20 | wug.add_vertex("7") 21 | 22 | # add edges 23 | wug.add_edge("0", "7", 0.16) 24 | wug.add_edge("0", "4", 0.38) 25 | wug.add_edge("0", "2", 0.26) 26 | wug.add_edge("1", "5", 0.32) 27 | wug.add_edge("1", "7", 0.19) 28 | wug.add_edge("1", "2", 0.36) 29 | wug.add_edge("1", "3", 0.39) 30 | wug.add_edge("2", "3", 0.17) 31 | wug.add_edge("2", "7", 0.34) 32 | wug.add_edge("3", "6", 0.52) 33 | wug.add_edge("4", "5", 0.35) 34 | wug.add_edge("4", "7", 0.37) 35 | wug.add_edge("5", "7", 0.28) 36 | wug.add_edge("6", "2", 0.40) 37 | wug.add_edge("6", "0", 0.58) 38 | wug.add_edge("6", "4", 0.93) 39 | 40 | # print vertices 41 | vertices = wug.get_all_vertices() 42 | print(f"total vertices: {len(vertices)}") 43 | for v in vertices: 44 | print(f"{v}", end=" ") 45 | 46 | # print edges 47 | print("\n") 48 | edges = wug.get_all_edges() 49 | print(f"total edges: {len(edges)}") 50 | 51 | for e in edges: 52 | print(f"{e}", end=" ") 53 | 54 | # print a node 55 | print("\n") 56 | print(f"{wug.vertices['0']}") 57 | print(f"{wug.vertices['6']}") 58 | 59 | # print the graph 60 | print("\n") 61 | print(f"{wug}") 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /graph/dfs_undirected_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: dfs traversals on a connected undirected graph 5 | 6 | from collections import deque 7 | from graph import UndirectedGraph 8 | 9 | 10 | def dfs(ug, start_vertex): 11 | """ 12 | depth first search 13 | time complexity: O(V + E) 14 | space complexity: O(V) -> to maintain the visited set 15 | """ 16 | # check if the start vertex_name is present in the ug 17 | if not ug.get_vertex_obj(start_vertex): 18 | return False 19 | 20 | # track the visited vertices 21 | visited = set() 22 | 23 | # add the start vertex_name into the stack 24 | stack = deque() 25 | stack.append(start_vertex) 26 | 27 | while stack: 28 | # pop the vertex_name from the stack but don't process that immediately 29 | vertex = stack.pop() 30 | # check if the vertex_name is not in visited set then add into the visited set and process 31 | if vertex not in visited: 32 | visited.add(vertex) 33 | print(vertex, end=" ") 34 | # iterate all neighbors of that node 35 | vertex_obj = ug.vertices[vertex] 36 | # iterate all neighbors of that vertex_name 37 | for edge_obj in vertex_obj.neighbor_edges_obj: 38 | neighbor = edge_obj.dst_vertex 39 | # if that neighbor is not in the visited set then add that into the stack 40 | if neighbor not in visited: 41 | stack.append(neighbor) 42 | 43 | 44 | def main(): 45 | # ref: Sedgewick Algorithms 4th edition, page 532 46 | ug = UndirectedGraph() 47 | # add vertices 48 | for vertex in range(0, 6): 49 | ug.add_vertex(str(vertex)) 50 | 51 | # add edges 52 | ug.add_edge("0", "1") 53 | ug.add_edge("0", "5") 54 | ug.add_edge("0", "2") 55 | ug.add_edge("1", "2") 56 | ug.add_edge("2", "3") 57 | ug.add_edge("2", "4") 58 | ug.add_edge("3", "4") 59 | ug.add_edge("3", "5") 60 | 61 | start_vertex = "0" 62 | 63 | print(f"dfs traversal: ") 64 | dfs(ug, start_vertex) 65 | 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /graph/mst_kruskal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: kruskal algorithm to find the mst of an undirected weighted graph 5 | 6 | from typing import Tuple 7 | from heap import Heap 8 | from union_find_adt import WeightedQuickUnion 9 | from graph import UndirectedGraph, Edge 10 | 11 | 12 | def mst_kruskal(wug: UndirectedGraph) -> Tuple[list, int]: 13 | """ 14 | time complexity: E log E -> to push and pop each node 15 | space complexity: E -> to maintain a min heap 16 | """ 17 | mst = list() # returns this final list of tuple - (_src_vertex, _dst_vertex, _weight) that has all minimum edges 18 | 19 | # create a union find object to check the connectivity between two vertices in O(1) time 20 | vertices = wug.get_all_vertices() 21 | # uf = QuickFind(vertices) 22 | # uf = QuickUnion(vertices) 23 | uf = WeightedQuickUnion(vertices) 24 | 25 | # create a custom min heap where each element is a weighted edge obj 26 | min_heap = Heap([]) 27 | edges = wug.get_all_edges() 28 | for e in edges: 29 | src_vertex, dst_vertex, weight = e 30 | weighted_edge_obj = Edge(src_vertex, dst_vertex, weight) 31 | min_heap.insert(weighted_edge_obj) # O(log E) 32 | # heapq.heappush(min_heap, weighted_edge_obj) 33 | 34 | weight_sum = 0 35 | while min_heap and len(mst) < (len(vertices) - 1): 36 | edge_obj = min_heap.remove() 37 | 38 | # extract the attributes from the object 39 | src_vertex = edge_obj.src_vertex 40 | dst_vertex = edge_obj.dst_vertex 41 | weight = edge_obj.weight 42 | 43 | if uf.connected(src_vertex, dst_vertex): # O(log(n)) for WeightedQuickUnion 44 | continue 45 | 46 | uf.union(src_vertex, dst_vertex) # O(1) for WeightedQuickUnion 47 | mst.append(edge_obj.__str__()) 48 | weight_sum = weight_sum + weight 49 | 50 | return mst, weight_sum 51 | -------------------------------------------------------------------------------- /graph/mst_prims.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: prims algorithm (lazy approach) to find the mst of an undirected weighted graph (connected, no cycle) 5 | 6 | from typing import Tuple 7 | from heap import Heap 8 | from graph import UndirectedGraph 9 | 10 | 11 | def mark_node(vertex_obj, min_heap, visited): 12 | visited.add(vertex_obj.vertex_name) 13 | for edge_obj in vertex_obj.neighbor_edges_obj: 14 | dst_vertex = edge_obj.dst_vertex 15 | if dst_vertex not in visited: 16 | min_heap.insert(edge_obj) 17 | 18 | 19 | def mst_prims_lazy(wug: UndirectedGraph) -> Tuple[list, int]: 20 | """ 21 | lazy approach 22 | time complexity: E log E -> to push and pop each node 23 | space complexity: E -> to maintain a min heap 24 | """ 25 | mst = list() # returns minimum list of edges 26 | 27 | vertices = wug.get_all_vertices() 28 | start_vertex_obj = wug.get_vertex_obj(vertices[0]) 29 | 30 | # create a min heap where each element is a weighted edge obj 31 | min_heap = Heap(list()) 32 | visited = set() 33 | 34 | mark_node(start_vertex_obj, min_heap, visited) 35 | 36 | weight_sum = 0 37 | while min_heap: 38 | edge_obj = min_heap.remove() 39 | 40 | # extract the attributes from the object 41 | src_vertex = edge_obj.src_vertex 42 | dst_vertex = edge_obj.dst_vertex 43 | weight = edge_obj.weight 44 | 45 | if src_vertex in visited and dst_vertex in visited: 46 | continue 47 | 48 | mst.append(edge_obj.__str__()) 49 | weight_sum = weight_sum + weight 50 | 51 | if src_vertex not in visited: 52 | mark_node(wug.get_vertex_obj(src_vertex), min_heap, visited) 53 | 54 | if dst_vertex not in visited: 55 | mark_node(wug.get_vertex_obj(dst_vertex), min_heap, visited) 56 | 57 | return mst, weight_sum 58 | 59 | -------------------------------------------------------------------------------- /graph/union_find_adt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: UnionFind data structure is used to solve dynamic connectivity problems 5 | 6 | 7 | class QuickFind: 8 | """ 9 | find() operation is quick but union() operation is expensive 10 | """ 11 | 12 | def __init__(self, vertices): 13 | """ 14 | time complexity: O(n) 15 | """ 16 | # initially assume total component is equal to the length of the vertices 17 | self._components_count = len(vertices) 18 | self._vertices_lookup = dict() 19 | # initialize the dict with it's own value 20 | for vertex_name in vertices: 21 | self._vertices_lookup[vertex_name] = vertex_name 22 | 23 | def get_components(self): 24 | """ 25 | get the nos of components 26 | time complexity: O(1) 27 | """ 28 | return self._components_count 29 | 30 | def find(self, node): 31 | """ 32 | identify component for a node 33 | time complexity: O(1) 34 | """ 35 | return self._vertices_lookup.get(node, False) 36 | 37 | def connected(self, src_node, dst_node): 38 | """ 39 | returns true if src_node and dst_node ae in same component 40 | time complexity: O(1) 41 | """ 42 | if self.find(src_node) == self.find(dst_node): 43 | return True 44 | else: 45 | return False 46 | 47 | def union(self, src_node, dst_node): 48 | """ 49 | add connection between src_node and dst_node 50 | time complexity: O(v) 51 | """ 52 | src_node_id = self.find(src_node) 53 | dst_node_id = self.find(dst_node) 54 | 55 | # nothing to do if both src_node and dst_node are in the same component 56 | if src_node_id == dst_node_id: 57 | return 58 | 59 | # change values from 60 | for vertex_name in self._vertices_lookup: 61 | if self._vertices_lookup[vertex_name] == src_node_id: 62 | self._vertices_lookup[vertex_name] = dst_node_id 63 | 64 | # decrease the component count by 1 65 | self._components_count -= 1 66 | 67 | 68 | class QuickUnion: 69 | """ 70 | union() operation is quick but find() operation is expensive 71 | """ 72 | 73 | def __init__(self, vertices): 74 | """ 75 | time complexity: O(n) 76 | """ 77 | # initially assume total component is equal to the length of the vertices 78 | self._components_count = len(vertices) 79 | self._vertices_lookup = dict() 80 | # initialize the dict with it's own value 81 | for vertex_name in vertices: 82 | self._vertices_lookup[vertex_name] = vertex_name 83 | 84 | def get_components(self): 85 | """ 86 | get the nos of components 87 | time complexity: O(1) 88 | """ 89 | return self._components_count 90 | 91 | def find(self, node): 92 | """ 93 | find the root or parent of a node / vertex_name 94 | time complexity: tree height, worst case -> O(n) 95 | """ 96 | while node != self._vertices_lookup[node]: 97 | node = self._vertices_lookup[node] 98 | return node 99 | 100 | def connected(self, src_node, dst_node): 101 | """ 102 | returns true if src_node and dst_node ae in same component 103 | time complexity: O(1) 104 | """ 105 | if self.find(src_node) == self.find(dst_node): 106 | return True 107 | else: 108 | return False 109 | 110 | def union(self, src_node, dst_node): 111 | """ 112 | add connection between src_node and dst_node 113 | time complexity: tree height, worst case -> O(n) 114 | """ 115 | src_node_root = self.find(src_node) 116 | dst_node_root = self.find(dst_node) 117 | self._vertices_lookup[dst_node_root] = src_node_root 118 | 119 | self._components_count -= 1 120 | 121 | 122 | class WeightedQuickUnion: 123 | """ 124 | improved version QuickUnion with path compression 125 | - without path compression, find() takes Log(n) time without 126 | - whereas with path compression it takes liner time (amortized analysis) 127 | """ 128 | 129 | def __init__(self, vertices): 130 | """ 131 | time complexity: O(n) 132 | """ 133 | # initially assume total component is equal to the length of the vertices 134 | self._components_count = len(vertices) 135 | self._sz = dict() # holds size of components for roots 136 | self._vertices_lookup = dict() 137 | # initialize the dict with it's own value 138 | for vertex_name in vertices: 139 | self._vertices_lookup[vertex_name] = vertex_name 140 | 141 | # ste the initial size 142 | for key in self._vertices_lookup.keys(): 143 | self._sz[key] = 1 144 | 145 | def get_components(self): 146 | """ 147 | get the nos of components 148 | time complexity: O(1) 149 | """ 150 | return self._components_count 151 | 152 | def find(self, node): 153 | """ 154 | find the root of a node / vertex_name 155 | time complexity: with path compression it takes very very nearly O(1) -> amortized analysis 156 | - without path compression improvement it takes O(log(n)) 157 | """ 158 | while node != self._vertices_lookup[node]: 159 | # path compression improvement: flatten the tree 160 | grand_parent = self._vertices_lookup[self._vertices_lookup[node]] 161 | # make every other node in the path point to its grandparent thereby halving the path 162 | self._vertices_lookup[node] = grand_parent 163 | node = self._vertices_lookup[node] 164 | return node 165 | 166 | def connected(self, src_node, dst_node): 167 | """ 168 | returns true if src_node and dst_node ae in same component 169 | time complexity: depends on the find() method 170 | """ 171 | if self.find(src_node) == self.find(dst_node): 172 | return True 173 | else: 174 | return False 175 | 176 | def union(self, src_node, dst_node): 177 | """ 178 | add connection between src_node and dst_node 179 | time complexity: with path compression it takes very very nearly O(1) -> amortized analysis 180 | - without path compression improvement it takes O(log(n)) 181 | """ 182 | src_node_root = self.find(src_node) 183 | dst_node_root = self.find(dst_node) 184 | 185 | if src_node_root == dst_node_root: 186 | return 187 | 188 | # need to make sure that smaller tree comes below 189 | if self._sz[src_node_root] < self._sz[dst_node_root]: 190 | self._vertices_lookup[src_node_root] = dst_node_root 191 | self._sz[dst_node_root] += self._sz[src_node_root] 192 | else: 193 | self._vertices_lookup[dst_node_root] = src_node_root 194 | self._sz[src_node_root] += self._sz[dst_node_root] 195 | 196 | self._components_count -= 1 197 | -------------------------------------------------------------------------------- /hashing/test_three_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # command: python -m pytest test_three_sum.py -v 5 | 6 | import pytest 7 | from three_sum import three_sum, three_sum_alternate 8 | 9 | 10 | def test_case_1() -> None: 11 | arr = [1, 2, 3, 4, 5] 12 | target = 6 13 | assert three_sum(arr, target) is True 14 | 15 | 16 | def test_case_2() -> None: 17 | arr = [1, 5, -4, -2, -9, 5] 18 | target = 14 19 | assert three_sum(arr, target) is False 20 | 21 | 22 | def test_case_3() -> None: 23 | arr = [1, 5, -4, -2, -9, 4] 24 | target = 1 25 | assert three_sum(arr, target) is True 26 | 27 | 28 | def test_case_4() -> None: 29 | arr = [1, 5, 8] 30 | target = 11 31 | assert three_sum(arr, target) is False 32 | -------------------------------------------------------------------------------- /hashing/test_two_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # command: python -m pytest test_two_sum.py -v 5 | 6 | import pytest 7 | from two_sum import two_sum 8 | 9 | 10 | def test_case_1() -> None: 11 | arr = [12, 7, 11, 15, 35] 12 | target = 50 13 | expected_index = [3, 4] 14 | assert set(two_sum(arr, target)) == set(expected_index) 15 | 16 | 17 | def test_case_2() -> None: 18 | arr = [12, 7, 11, 15, 35] 19 | target = 19 20 | expected_index = [0, 1] 21 | assert set(two_sum(arr, target)) == set(expected_index) 22 | 23 | 24 | def test_case_3() -> None: 25 | arr = [12, 7, 11, 15, 35] 26 | target = 190 27 | expected_index = [None, None] 28 | assert set(two_sum(arr, target)) == set(expected_index) 29 | -------------------------------------------------------------------------------- /hashing/three_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | def three_sum(arr: list, target: int) -> bool: 6 | """ 7 | time: O(n^3) 8 | space: O(1) 9 | """ 10 | for i in range(0, len(arr)): 11 | for j in range(i + 1, len(arr)): 12 | for k in range(j + 1, len(arr)): 13 | if arr[i] + arr[j] + arr[k] == target: 14 | return True 15 | 16 | return False 17 | -------------------------------------------------------------------------------- /hashing/two_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | """ 6 | reference: https://leetcode.com/problems/two-sum/ 7 | 8 | description: 9 | Given an array of integers, return indices of the two numbers such that 10 | they add up to a specific target. 11 | 12 | You may assume that each input would have exactly one two_sum, and you may not 13 | use the same element twice. 14 | 15 | Example: 16 | 17 | Given arr = [2, 7, 11, 15], target = 9, 18 | 19 | Because arr[0] + arr[1] = 2 + 7 = 9, 20 | return [0, 1]. 21 | """ 22 | 23 | 24 | def two_sum(arr: list, target: int) -> list: 25 | """ 26 | time complexity: O(n) 27 | space complexity: O(n) 28 | """ 29 | lookup = dict() 30 | 31 | for i in range(0, len(arr)): 32 | num = arr[i] 33 | complement = target - num 34 | # if the complement is not present in lookup then 35 | # insert the number as key, index as value 36 | index = lookup.get(complement) # O(1), not found return None 37 | if index is not None: 38 | return [index, i] 39 | lookup[num] = i 40 | 41 | # scenario where target is not found 42 | return [None, None] # returns a list 43 | -------------------------------------------------------------------------------- /heap/continuous_median.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: find the median from data stream 5 | # reference: https://leetcode.com/problems/find-median-from-data-stream/ 6 | 7 | from snowowl import Heap, HeapType 8 | 9 | 10 | class MedianFinder: 11 | def __init__(self): 12 | # store lower arr in a max heap 13 | self._max_heap = Heap(list(), heap_type=HeapType.MAX) 14 | # store higher arr in a min heap 15 | self._min_heap = Heap(list()) 16 | 17 | def add_num(self, num: int) -> None: 18 | """ 19 | time complexity: O(log n) 20 | space complexity: O(1) 21 | """ 22 | # when the _max_heap is empty or the number is less than max number of max heap 23 | if len(self._max_heap) == 0 or num < self._max_heap.peek(): 24 | self._max_heap.insert(num) 25 | else: 26 | self._min_heap.insert(num) 27 | 28 | # when any heap of the two heaps has 2 extra elements then rebalance the heaps 29 | if len(self._max_heap) - len(self._min_heap) == 2: 30 | item = self._max_heap.remove() 31 | self._min_heap.insert(item) 32 | 33 | elif len(self._min_heap) - len(self._max_heap) == 2: 34 | item = self._min_heap.remove() 35 | self._max_heap.insert(item) 36 | 37 | def get_median(self) -> float: 38 | """ 39 | time complexity: O(1) 40 | space complexity: O(1) 41 | """ 42 | if len(self._max_heap) == len(self._min_heap): 43 | return (self._max_heap.peek() + self._min_heap.peek()) / 2 44 | 45 | # when any heap has 1 extra element 46 | if len(self._max_heap) > len(self._min_heap): 47 | return self._max_heap.peek() 48 | 49 | return self._min_heap.peek() 50 | -------------------------------------------------------------------------------- /heap/k_largest_elements_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: find k largest elements in an array without sorting 4 | * */ 5 | 6 | #include 7 | #include "stdbool.h" 8 | #include "../libO2/include/binary_heap_dynamic_array_int.h" 9 | 10 | void k_largest_elements_02(int *arr, int n, int k, int *out_arr) { 11 | /* 12 | * time complexity: O(n + k*log(n)) 13 | * space complexity: O(1) 14 | */ 15 | heap h; 16 | int data; 17 | 18 | // create a max heap of size n 19 | build_heap(&h, true, arr, n); // O(n) 20 | 21 | for (int i = k - 1; i >= 0; i--) { 22 | pop_heap(&h, &data); // O(k*log(n)) 23 | out_arr[i] = data; 24 | } 25 | 26 | delete_heap(&h); 27 | } 28 | 29 | void k_largest_elements(int *test_arr, int n, int k, int *out_arr) { 30 | /* 31 | * time complexity: O(n*log(k)) 32 | * space complexity: O(k) 33 | */ 34 | heap h; 35 | size_t heap_size; 36 | int out_data; 37 | 38 | // create a min heap, initial capacity = k 39 | // heap implementation is backed by dynamic array so we can insert any nos of elements 40 | initialize_heap(&h, k, false); 41 | 42 | for (int i = 0; i < n; i++) { 43 | push_heap(&h, test_arr[i]); 44 | heap_size = get_heap_size(&h); 45 | // if the heap size exceeds k, pop an item to maintain the size = k 46 | if (heap_size > k) { 47 | pop_heap(&h, &out_data); 48 | } 49 | } 50 | // copy the heap elements in the out_arr 51 | for (int i = 0; i < k; i++){ 52 | pop_heap(&h, &out_data); 53 | out_arr[i] = out_data; 54 | } 55 | delete_heap(&h); 56 | } 57 | -------------------------------------------------------------------------------- /heap/k_largest_elements_array.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | from snowowl import Heap, HeapType 6 | 7 | 8 | def get_k_largest_elements_array(array: list, k: int) -> list: 9 | """ 10 | time complexity: O(n*log(k)) 11 | space complexity: O(k) 12 | """ 13 | min_heap = Heap([]) 14 | 15 | for num in array: 16 | min_heap.insert(num) 17 | if len(min_heap) > k: 18 | _ = min_heap.remove() 19 | 20 | return [n for n in min_heap] 21 | 22 | 23 | def get_k_largest_elements_array_alternate(array: list, k: int) -> list: 24 | """ 25 | * time complexity: O(n + k*log(n)) 26 | * space complexity: O(1) 27 | """ 28 | max_heap = Heap(array, heap_type=HeapType.MAX) # O(n) 29 | return [max_heap.remove() for _ in range(0, k)] 30 | -------------------------------------------------------------------------------- /heap/k_largest_elements_immutable_max_heap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # description: find k largest elements from a immutable max heap 5 | 6 | from snowowl import Heap, HeapType 7 | 8 | 9 | class Node: 10 | def __init__(self, value, index): 11 | self.value = value 12 | self.index = index 13 | 14 | def __lt__(self, other): 15 | return self.value < other.value 16 | 17 | def __eq__(self, other): 18 | return self.value == other.value 19 | 20 | 21 | def get_k_largest_elements_immutable_max_heap(immutable_max_heap: Heap, k: int) -> list: 22 | """ 23 | time complexity: O(k * log k) 24 | space complexity: O(k) -> auxiliary max heap 25 | """ 26 | # create an auxiliary max heap 27 | auxiliary_max_heap = Heap([], heap_type=HeapType.MAX) 28 | 29 | # peek the min item from the immutable max heap 30 | # create a node obj with the value and index and push that object into auxiliary max heap 31 | node = Node(immutable_max_heap.peek(), 0) 32 | auxiliary_max_heap.insert(node) 33 | 34 | result = list() 35 | for i in range(0, k): 36 | node = auxiliary_max_heap.remove() 37 | result.append(node.value) 38 | 39 | index = node.index 40 | 41 | left_child_index = 2 * index + 1 42 | 43 | if left_child_index < len(immutable_max_heap): 44 | left_child = immutable_max_heap.__getitem__(left_child_index) 45 | left_node = Node(left_child, left_child_index) 46 | auxiliary_max_heap.insert(left_node) 47 | 48 | right_child_index = 2 * index + 2 49 | 50 | if right_child_index < len(immutable_max_heap): 51 | right_child = immutable_max_heap.__getitem__(right_child_index) 52 | right_node = Node(right_child, right_child_index) 53 | auxiliary_max_heap.insert(right_node) 54 | 55 | return result 56 | -------------------------------------------------------------------------------- /heap/test_continuous_median.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | # command: python -m pytest test_continuous_median.py -v 5 | 6 | import pytest 7 | from heap.continuous_median import MedianFinder 8 | 9 | 10 | def test_case_1() -> None: 11 | s = MedianFinder() 12 | s.add_num(1) 13 | assert s.get_median() == 1 14 | 15 | s.add_num(2) 16 | assert s.get_median() == 1.5 17 | s.add_num(3) 18 | assert s.get_median() == 2 19 | -------------------------------------------------------------------------------- /heap/test_k_largest_elements_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: c_tests case 4 | * 5 | * run the test: 6 | * make sure libO2 is installed in your system, no main() inside the test code. 7 | * export the test function - such as k_largest_elements() inside c_tests/testlib.h 8 | * navigate to the c_tests/ directory 9 | * make test FLD=heap TEST_PROG=k_largest_elements 10 | * */ 11 | 12 | #include 13 | #include "../libO2/lib/Unity/src/unity.h" 14 | #include "../c_tests/testlib.h" 15 | 16 | // create the data structure 17 | 18 | void setUp(void) { 19 | // initialize the data structure 20 | } 21 | 22 | void tearDown(void) { 23 | // delete the data structure 24 | } 25 | 26 | void test_k_largest_elements_case_1(void) { 27 | int test_arr[8] = {10, 20, 30, 50, 90, 70, 80, 40}; 28 | int exp_arr[3] = {70, 80, 90}; 29 | int out_arr[3] = {0}; 30 | 31 | k_largest_elements(test_arr, 8, 3, out_arr); 32 | TEST_ASSERT_EQUAL_INT_ARRAY(exp_arr, out_arr, 3); 33 | 34 | k_largest_elements_02(test_arr, 8, 3, out_arr); 35 | TEST_ASSERT_EQUAL_INT_ARRAY(exp_arr, out_arr, 3); 36 | } 37 | 38 | void test_k_largest_elements_case_2(void) { 39 | int test_arr[5] = {5, 2, 4, 1, 3}; 40 | int exp_arr[2] = {4, 5}; 41 | int out_arr[3] = {0}; 42 | 43 | k_largest_elements(test_arr, 5, 2, out_arr); 44 | TEST_ASSERT_EQUAL_INT_ARRAY(exp_arr, out_arr, 2); 45 | 46 | k_largest_elements_02(test_arr, 5, 2, out_arr); 47 | TEST_ASSERT_EQUAL_INT_ARRAY(exp_arr, out_arr, 2); 48 | } 49 | 50 | 51 | int main(void) { 52 | UNITY_BEGIN(); 53 | RUN_TEST(test_k_largest_elements_case_1); 54 | RUN_TEST(test_k_largest_elements_case_2); 55 | 56 | return UNITY_END(); 57 | } -------------------------------------------------------------------------------- /heap/test_k_largest_elements_array.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | import pytest 6 | from heap.k_largest_elements_array import * 7 | 8 | 9 | def test_case_1() -> None: 10 | array = [7, 17, 16, 2, 3, 15, 14] 11 | expected_result = [17, 16, 15, 14] 12 | result = get_k_largest_elements_array(array, k=4) 13 | # check if the two lists are identical: order does not matter 14 | assert set(result) == set(expected_result) 15 | 16 | 17 | def test_case_2() -> None: 18 | array = [10, 20, 30, 50, 90, 70, 80, 40] 19 | expected_result = [70, 80, 90] 20 | result = get_k_largest_elements_array(array, k=3) 21 | assert set(result) == set(expected_result) 22 | 23 | 24 | def test_case_3() -> None: 25 | """verify the alternate solution""" 26 | array = [10, 20, 30, 50, 90, 70, 80, 40] 27 | expected_result = [70, 80, 90] 28 | result = get_k_largest_elements_array_alternate(array, k=3) 29 | assert set(result) == set(expected_result) 30 | -------------------------------------------------------------------------------- /heap/test_k_largest_elements_immutable_max_heap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: greyshell 4 | 5 | import pytest 6 | from snowowl import Heap, HeapType 7 | from heap.k_largest_elements_immutable_max_heap import * 8 | 9 | 10 | def test_case_1() -> None: 11 | array = [7, 17, 16, 2, 3, 15, 14] 12 | immutable_max_heap = Heap(array, heap_type=HeapType.MAX) 13 | result = get_k_largest_elements_immutable_max_heap(immutable_max_heap, k=4) 14 | assert result == [17, 16, 15, 14] 15 | -------------------------------------------------------------------------------- /sorting/trouble_sort.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: https://codeforces.com/contest/1365/problem/B 4 | * */ 5 | 6 | #include 7 | #include "stdbool.h" 8 | 9 | bool is_sorted(int arr[], size_t length){ 10 | bool is_swapped = false; 11 | for (int j = length - 1; j > 0; j--) { 12 | // if the right element is smaller than the left element then perform _swap_heap_nodes 13 | if (arr[j] < arr[j - 1]) { 14 | is_swapped = true; 15 | } 16 | } 17 | if (is_swapped == false) { 18 | // no _swap_heap_nodes occurs inside the loop which means the array is sorted 19 | return true; 20 | } 21 | return false; 22 | } 23 | 24 | bool trouble_sort(int arr[], int arr_type[], size_t length){ 25 | /* 26 | * time complexity: O(n) 27 | * space complexity: O(1) 28 | */ 29 | bool return_type; 30 | bool type_0_is_present = false; 31 | bool type_1_is_present = false; 32 | 33 | // case 1: if entire array is already sorted 34 | return_type = is_sorted(arr, length); 35 | if (return_type == true){ 36 | return 0; 37 | } 38 | // check at least 1 '0' type is present 39 | for (int i = 0; i < length; i++){ 40 | if (arr_type[i] == 0){ 41 | type_0_is_present = true; 42 | break; 43 | } 44 | } 45 | 46 | // check at least 1 '1' type is present 47 | for (int i = 0; i < length; i++){ 48 | if (arr_type[i] == 1){ 49 | type_1_is_present = true; 50 | break; 51 | } 52 | } 53 | // case 2: sorting is possible when at least one both types of elements are present 54 | if (type_0_is_present == true && type_1_is_present == true){ 55 | return 1; 56 | } 57 | else { 58 | return 0; 59 | } 60 | } 61 | 62 | int main(void) { 63 | int arr[] = {1, 2, 3}; 64 | int arr_type[] = {1, 0, 1}; 65 | size_t length = 3; 66 | if (trouble_sort(arr, arr_type, length)){ 67 | printf("sorting possible \n"); 68 | } 69 | else{ 70 | printf("sorting not possible \n"); 71 | } 72 | 73 | return 0; 74 | } -------------------------------------------------------------------------------- /stack/blance_bracket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # author: greyshell 3 | 4 | 5 | def balance_bracket(arr): 6 | # create a stack 7 | s = list() 8 | 9 | for i in range(0, len(arr)): 10 | if arr[i] == '(': 11 | s.append(')') 12 | 13 | elif arr[i] == ')': 14 | if len(s) != 0: 15 | out = s[-1] 16 | if out == ')': 17 | s.pop() 18 | else: 19 | s.append('(') 20 | 21 | if len(s) == 0: 22 | return True 23 | 24 | return False 25 | 26 | 27 | def main(): 28 | # sample test case 29 | arr = ['(', ')'] 30 | result = balance_bracket(arr) 31 | print(f"{result}") 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /stack/reverse_stack.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: greyshell 3 | * description: reverse the stack without using any auxiliary stack 4 | * */ 5 | 6 | #include 7 | #include "../libO2/include/stack_singly_linkedlist_int.h" 8 | 9 | void _insert_at_bottom(stack *s, int data) { 10 | /* 11 | * time complexity: O(n) 12 | */ 13 | int out_data; 14 | if (is_empty_stack(s)) { 15 | push(s, data); 16 | return; 17 | } 18 | pop(s, &out_data); 19 | _insert_at_bottom(s, data); 20 | push(s, out_data); 21 | 22 | } 23 | 24 | void reverse_stack(stack *s) { 25 | /* 26 | * reverse all stack elements 27 | * time complexity: O(n^2), reverse_stack() depends on insert_at_bottom() 28 | * space complexity: O(n), at the max n elements can be present in the call stack 29 | */ 30 | int out_data; 31 | if (is_empty_stack(s)) { 32 | return; 33 | } 34 | pop(s, &out_data); 35 | reverse_stack(s); 36 | // start inserting element from bottom 37 | _insert_at_bottom(s, out_data); 38 | } 39 | 40 | int main(void) { 41 | // tests data_arr 42 | int data[] = {10, 20, 30, 40, 50}; 43 | stack s; 44 | 45 | // initialize the stack 46 | initialize_stack(&s); 47 | 48 | for (int i = 0; i < 5; i++) { 49 | push(&s, data[i]); 50 | } 51 | 52 | // display the stack 53 | printf("display(top -> last element): "); 54 | display_stack(&s); 55 | printf("\n"); 56 | 57 | // reverse the stack 58 | reverse_stack(&s); 59 | printf("display: "); 60 | display_stack(&s); 61 | printf("\n"); 62 | 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /stack/test_blance_bracket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # author: greyshell 3 | # how to run: python -m unittest test_balance_bracket.TestSolution 4 | 5 | import unittest 6 | from blance_bracket import balance_bracket 7 | 8 | 9 | class TestSolution(unittest.TestCase): 10 | 11 | def test_case_1(self): 12 | self.assertEqual(balance_bracket(['(', '(']), False) 13 | 14 | def test_case_2(self): 15 | self.assertEqual(balance_bracket(['(', ')']), True) 16 | 17 | def test_case_3(self): 18 | self.assertEqual(balance_bracket([')', '(']), False) 19 | 20 | def test_case_4(self): 21 | self.assertEqual(balance_bracket(['(', '(', ')', ')']), True) 22 | 23 | def test_case_5(self): 24 | self.assertEqual(balance_bracket([')', ')']), False) 25 | 26 | def test_case_6(self): 27 | self.assertEqual(balance_bracket(['(', '(', ')', '(', ')']), False) 28 | 29 | def test_case_7(self): 30 | self.assertEqual(balance_bracket(['(', ')', '(']), False) 31 | 32 | def test_case_8(self): 33 | self.assertEqual(balance_bracket(['(', ')', ')']), False) 34 | 35 | def test_case_9(self): 36 | self.assertEqual(balance_bracket(['(', '(', ')', '(', ')', ')']), True) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | --------------------------------------------------------------------------------