diff --git a/.gitignore b/.gitignore index c7126040..40202465 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ questions_to_do.txt +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f0c24e6..7fb881f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ This is a project that is for the community and its true essence is only possible when it is community driven. -Please feel free to contribute to this project and be a part of this. Anything from raising issues to adding new features or even a typo in documentations, all are welcome. Please report issues here [https://github.com/prabhupant/python-ds/issues](https://github.com/prabhupant/python-ds/issues). It is also recommended to go through the Contributions Best Practices below to help organize contributions :smiley: +Please feel free to contribute to this project and be a part of this. Anything from raising issues to adding new features or even a typo in documentations, all are welcome. Please report issues here [https://github.com/prabhupant/python-ds/issues](https://github.com/prabhupant/python-ds/issues). It is also recommended to go through the Contributions Best Practices below to help organize contributions ## Contributions Best Practices @@ -18,7 +18,7 @@ Please feel free to contribute to this project and be a part of this. Anything f ### Issues -Feel free to open up any issue in the repository, whether it is about code improvements or bug fixes or documentation. You can also use any label you want to associate with the issue. Please provide a clear description of the issue while filing it :smiley: +Feel free to open up any issue in the repository, whether it is about code improvements or bug fixes or documentation. You can also use any label you want to associate with the issue. Please provide a clear description of the issue while filing it. ### Code Styling Guide @@ -26,23 +26,23 @@ Python follows a Pep8 styling. Styling the code according to it makes it univers ### Questions -While there are infinite number of data structure and algorithm questions, it is impossible to collect all of them here. So please add only those questions that are either unique or tricky or have some mind blowing approach to solve them. Also, try not to file a PR for a question which is already present in the repo. Interesting questions and implementations are always welcomed :wink: +While there are infinite number of data structure and algorithm questions, it is impossible to collect all of them here. So please add only those questions that are either unique or tricky or have some mind blowing approach to solve them. Also, try not to file a PR for a question which is already present in the repo. Interesting questions and implementations are always welcomed. -For filing a PR for a new question, please open a issue first for the same and then reference it in the PR. For example, let's say you want to add a new question called "find max element in array". So first head over to the issues tab and create a new issue called "New question: find max element in array". Then if you want to work on it, please mention it in the description. After you are done writing the code and ready to file a PR, refer to the issue number in the PR description. Let's say the issue number was #23. So in the PR description, it should be "Fixes #23". That's it! :smiley: +For filing a PR for a new question, please open a issue first for the same and then reference it in the PR. For example, let's say you want to add a new question called "find max element in array". So first head over to the issues tab and create a new issue called "New question: find max element in array". Then if you want to work on it, please mention it in the description. After you are done writing the code and ready to file a PR, refer to the issue number in the PR description. Let's say the issue number was #23. So in the PR description, it should be "Fixes #23". That's it! ### Bookmarks -The Bookmarks directory is for storing awesome links to articles, videos, slides, talks, etc that are interesting and can help in increasing knowledge for a specific topic. There has been many PRs for adding new links in Bookmarks and most of them refered to either a whole book or DS Algo MOOCs, please refrain from adding them. Here are some points that you must consider before contributing to Bookmarks - +The Bookmarks directory is for storing awesome links to articles, videos, slides, talks, etc that are interesting and can help in increasing knowledge for a specific topic. There has been many PRs for adding new links in Bookmarks and most of them referred to either a whole book or DS Algo MOOCs, please refrain from adding them. Here are some points that you must consider before contributing to Bookmarks - * Don't add a link to a MOOC or course (like DS Algo MIT lectures) -* Ask yourself "Is the link interesting enough that someone will really love it?" or "Is it something that an interviewer might ask in the interview to test you?" or "Is it some common or amazing thing people don't have enough knowledge about?". If yes to any of them, then sure, quickly file the PR :wink: +* Ask yourself "Is the link interesting enough that someone will really love it?" or "Is it something that an interviewer might ask in the interview to test you?" or "Is it some common or amazing thing people don't have enough knowledge about?". If yes to any of them, then sure, quickly file the PR. * If an article has more than one part, be sure to refer to the first only in the PR. ## How To Contribute? -You can add anything to the repo as long as it is related to programming and increasing knowledge :smiley: +You can add anything to the repo as long as it is related to programming and increasing knowledge. 0. Firstly, fork the repo to your GitHub account. Just to get familiar with some terms, this will be the `origin`. To state `origin` and `upstream` explicitly, @@ -80,13 +80,13 @@ $ git push origin max-number-in-array 6. Write a good brief description and refer to the issue (if any) and submit the PR. -7. Now wait for the approval :smiley: +7. Now wait for the approval. ## Join The Development Before you join the development, please fork and clone the repository to your local machine and explore it. Don't worry nothing will happen, atmost some code might not work :wink: -Feel free to contribute and be a part of this endeavour :beers: +Feel free to contribute and be a part of this endeavour. diff --git a/README.md b/README.md index 6b18100c..7c05260f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -![logo](logo/logo.png) - # Python Data Structures and Algorithms -This repository contains data structures and algorithms concepts and questions in Python. +No non-sense solutions to common Data Structure and Algorithm interview questions in Python. Follows a consistent approach throughout problems. + +## Objective + +There are a plenty of resources when it comes to interview preparations on the internet. What prompted me to create this project was the dissimilarity across different approaches and the infused complexity of the code. -## :dart: Objective +Feel free to contribute but please follow the Contributing Guidelines as I want to maintain the uniformity of the implementation of data structures and algorithms. Last time around, people bombarded with me with Pull Requests, Issues and Emails insisting me to merge their changes -The open source community has helped me a lot during my interview preparations and studies while I was in my undergrad. I always wanted to give something back to the community. In my endeavour to contribute something back, I will be uploading data structures and algorithms questions in Python in this repo. Feel free to contribute and get in touch! :smiley: +The open source community has helped me a lot during my interview preparations and studies while I was in my undergrad. I always wanted to give something back to the community. In my endeavour to contribute something back, I will be uploading data structures and algorithms questions in Python in this repo. Feel free to contribute and get in touch! -## :file_folder: Structure of the repository +## Structure of the repository As of now, the repository contains 3 main directories: [**Bookmarks**](bookmarks), [**Data Structures**](data_structures) and [**Algorithms**](algorithms). @@ -27,8 +29,7 @@ Contains all data structure questions categorised into sub-directories like stac ### Algorithms -This directory contains various types of algorithm questions like Dynamic Programming, Sorting, Greedy, etc. The current structure of this directory is like - - +This directory contains various types of algorithm questions like Dynamic Programming, Sorting, Greedy, etc. The current structure of this directory is as follows: 1. [Dynamic Programming](algorithms/dynamic_programming) 2. [Graphs](algorithms/graph) @@ -36,7 +37,7 @@ This directory contains various types of algorithm questions like Dynamic Progra 4. [Math](algorithms/math) 5. [Misc](algorithms/miscellaneous) 6. [Sorting](algorithms/sorting) - +7. [Bit Manipulation](algorithms/bit_manipulation) ### Bookmarks @@ -51,24 +52,21 @@ You can find useful links in this repository in the different markdown files. Be | Videos | [Click Here](bookmarks/videos.md) | | Misc. | [Click Here](bookmarks/misc.md) | -## :clipboard: Things need to be done +## Things need to be done As you can see, the repo is still in its infancy. Here are some key things in the to-do. 1. Queue questions 2. Algorithms - 2.1. Dynamic Programming - 2.2. Greedy -3. More questions in data structures, especially for graph, circular linked list, tries, heaps and hash. +3. More questions in data structures, especially for graph, circular linked list, trees, heaps and hash. -## :raised_hand: Contributing +## Contributing -Contributions are always welcomed. :smiley: -Feel free to raise new issues, file new PRs and star and fork this repo! :wink: +Contributions are always welcomed. +Feel free to raise new issues, file new PRs. Consider giving it a star and fork this repo! To follow the guidelines, refer to [Contributing.md](CONTRIBUTING.md) +## License -## :page_facing_up: License - -MIT @ Prabhu Pant +[MIT License](LICENSE) diff --git a/algorithms/__init__.py b/algorithms/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/algorithms/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/algorithms/bit_manipulation/range_sum_set_bits.py b/algorithms/bit_manipulation/range_sum_set_bits.py new file mode 100644 index 00000000..6250acf9 --- /dev/null +++ b/algorithms/bit_manipulation/range_sum_set_bits.py @@ -0,0 +1,19 @@ +# Question: Find the sum of number of set bits in all the numbers in the range [1, n]. + +def countBits(n): + + """ Consider a number x and half of the number (x//2). + The binary representation of x has all the digits as + the binary representation of x//2 followed by an additional + digit at the last position. Therefore, we can find the number + of set bits in x by finding the number of set bits in x//2 + and determining whether the last digit in x is 0 or 1. """ + + res = [0] * (n+1) + for i in range(1, n+1): + res[i] = res[i//2] + (i & 1) + return sum(res) + + +# Extension: Find the sum of number of set bits in all the numbers in the range [m, n]. +# Answer: In countBits(m, n), return sum(res) - sum(res[:m]) \ No newline at end of file diff --git a/algorithms/bit_manipulation/sum_of_two_integers.py b/algorithms/bit_manipulation/sum_of_two_integers.py new file mode 100644 index 00000000..c8203c5a --- /dev/null +++ b/algorithms/bit_manipulation/sum_of_two_integers.py @@ -0,0 +1,22 @@ +# Question: Calculate the sum of two integers a and b but without the +# use of the operators + and -. + +#Solution +def getSum(a, b): + """ + :type a: int + :type b: int + :rtype: int + """ + + mask = 0xffffffff + diff = 0 + carry = 0 + while b & mask: + diff = a ^ b + carry = trunc(a & b) + carry = carry << 1 + a = diff + b = carry + if b > 0: return (a & mask) + else: return a \ No newline at end of file diff --git a/algorithms/dynamic_programming/01_knapsack.py b/algorithms/dynamic_programming/01_knapsack.py new file mode 100644 index 00000000..652c65e9 --- /dev/null +++ b/algorithms/dynamic_programming/01_knapsack.py @@ -0,0 +1,26 @@ +def knapsack(values, weights, total): + total_items = len(weights) + + rows = total_items + 1 + cols = total + 1 + + # rows are the number of items + # columns are the values of weights required + + t = [[0 for i in range(cols)] for i in range(rows)] + + for i in range(1, rows): + for j in range(1, cols): + if j < weights[i-1]: + t[i][j] = t[i-1][j] + else: + t[i][j] = max(t[i-1][j], values[i-1] + t[i-1][j-weights[i-1]]) + + return t[rows-1][cols-1] + + +weights = [1,3,4,5] +values = [1,4,5,7] + +ans = knapsack(values, weights, 7) +print(ans) diff --git a/algorithms/dynamic_programming/Coin_Change.py b/algorithms/dynamic_programming/Coin_Change.py deleted file mode 100644 index fcf8d87b..00000000 --- a/algorithms/dynamic_programming/Coin_Change.py +++ /dev/null @@ -1,26 +0,0 @@ -# Dynamic Programming Python implementation of Coin -# Change problem -def count(S, m, n): - # We need n+1 rows as the table is constructed - # in bottom up manner using the base case 0 value - # case (n = 0) - table = [[0 for x in range(m)] for x in range(n+1)] - - # Fill the entries for 0 value case (n = 0) - for i in range(m): - table[0][i] = 1 - - # Fill rest of the table entries in bottom up manner - for i in range(1, n+1): - for j in range(m): - - # Count of solutions including S[j] - x = table[i - S[j]][j] if i-S[j] >= 0 else 0 - - # Count of solutions excluding S[j] - y = table[i][j-1] if j >= 1 else 0 - - # total count - table[i][j] = x + y - - return table[n][m-1] diff --git a/algorithms/dynamic_programming/Z_Algorithm.py b/algorithms/dynamic_programming/Z_Algorithm.py new file mode 100644 index 00000000..6af4c894 --- /dev/null +++ b/algorithms/dynamic_programming/Z_Algorithm.py @@ -0,0 +1,30 @@ +class Z_Algorithm(): + """ + return all occurences of string s in text, returns its indexes starting from zero + delimeter should be charachter which will not occur neither is S nor in text + by default its '$' + """ + @staticmethod + def find_occurrences(s:str , text:str , delimeter = '$'): + return Z_Algorithm.z_function(s + delimeter + text , len(s)) + + @staticmethod + def z_function(text:str , size:int): + l = 0 + r = 0 + z = [0] * len(text) + for i in range(1 , len(text)): + if i <= r: + z[i] = min(r - i + 1 , z[i - l]) + while i + z[i] < len(text) and text[z[i]] == text[i + z[i]]: + z[i] += 1 + + if i + z[i] - 1 > r: + l = i + r = i + z[i] - 1 + + res = [] + for i in range(size , len(text)): + if z[i] == size: + res.append(i - size - 1) + return res \ No newline at end of file diff --git a/algorithms/dynamic_programming/climbing_stairs.py b/algorithms/dynamic_programming/climbing_stairs.py deleted file mode 100644 index b47ffa3a..00000000 --- a/algorithms/dynamic_programming/climbing_stairs.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -High Level Description: -You are climbing a stair case. It takes n steps to reach to the top. -Each time you can either climb 1 or 2 steps. In how many distinct ways can you -climb to the top? - -Time Complexity: -O(n) -""" - -def climb_stairs(n): - if n==0 or n==1: - return 1 - first= 1 - second= 1 - ans= 0 - - for i in range(2, n+1): - ans = first + second - second = first - first = ans - return ans \ No newline at end of file diff --git a/algorithms/dynamic_programming/coin_change.py b/algorithms/dynamic_programming/coin_change.py new file mode 100644 index 00000000..7b9cbe26 --- /dev/null +++ b/algorithms/dynamic_programming/coin_change.py @@ -0,0 +1,33 @@ + +def min_coins(coins, total): + cols = total + 1 + + min_coins = [float('inf')] * (total + 1) + coins_used = [-1] * (total + 1) + + min_coins[0] = 0 # to form 0, we need 0 coins + + for i in range(0, len(coins)): + for j in range(1, len(min_coins)): + if coins[i] > j: # if the coin value is more than j (curr total), ignore it + continue + + if (1 + min_coins[j - coins[i]]) < min_coins[j]: + min_coins[j] = 1 + min_coins[j - coins[i]] + coins_used[j] = i + + # finding which coins were used + picked_coins = [] + while total > 0: + index_of_coin_used = coins_used[total] + coin = coins[index_of_coin_used] + picked_coins.append(coin) + total -= coin + + print('Min coins needed - ', min_coins[-1]) + print('Coins used - ', picked_coins) + +total = 11 +coins = [9, 6, 5, 1] + +min_coins(coins, total) diff --git a/algorithms/dynamic_programming/edit_distance.py b/algorithms/dynamic_programming/edit_distance.py deleted file mode 100644 index dde466a9..00000000 --- a/algorithms/dynamic_programming/edit_distance.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -High Level Description: -Given two words word1 and word2, find the minimum number of operations -required to convert word1 to word2. You have the following 3 operations -permitted on a word: -Insert a character -Delete a character -Replace a character - -Time Complexity: -O(m*n) -""" - - -def edit_dist(str1, str2, m, n): - dp = [[0 for x in range(n+1)] for x in range(m+1)] - - for i in range(m+1): - for j in range(n+1): - if i == 0: - dp[i][j] = j - elif j == 0: - dp[i][j] = i - elif str1[i-1] == str2[j-1]: - dp[i][j] = dp[i-1][j-1] - else: - dp[i][j] = 1 + min(dp[i][j-1], # Insert - dp[i-1][j], # Remove - dp[i-1][j-1]) # Replace - - return dp[m][n] diff --git a/algorithms/dynamic_programming/hamilton_cycle.py b/algorithms/dynamic_programming/hamilton_cycle.py new file mode 100644 index 00000000..21e2b4ac --- /dev/null +++ b/algorithms/dynamic_programming/hamilton_cycle.py @@ -0,0 +1,27 @@ +import functools + +def hamilton_cycle(graph, n): + height = 1 << n + + dp = [[False for _ in range(n)] for _ in range(height)] + for i in range(n): + dp[1 << i][i] = True + + for i in range(height): + ones, zeros = [], [] + for pos in range(n): + if (1 << pos) & i: + ones.append(pos) + else: + zeros.append(pos) + + for o in ones: + if not dp[i][o]: + continue + + for z in zeros: + if graph[o][z]: + new_val = i + (1 << z) + dp[new_val][z] = True + + return functools.reduce(lambda a, b: a or b, dp[height - 1]) \ No newline at end of file diff --git a/algorithms/dynamic_programming/index.md b/algorithms/dynamic_programming/index.md deleted file mode 100644 index 7bfbcd4c..00000000 --- a/algorithms/dynamic_programming/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Index of dynamic_programming - -* [Longest Common Subsequence](longest_common_subsequence.py) -* [Rod Cutting](rod_cutting.py) -* [Max Subarray](max_subarray.py) -* [Unique Grid Paths](unique_grid_paths.py) -* [Edit Distance](edit_distance.py) - diff --git a/algorithms/dynamic_programming/longest_common_subsequence.py b/algorithms/dynamic_programming/longest_common_subsequence.py index d8a5e66e..2c60beda 100644 --- a/algorithms/dynamic_programming/longest_common_subsequence.py +++ b/algorithms/dynamic_programming/longest_common_subsequence.py @@ -1,23 +1,18 @@ -''' -We are given two sequences and we -need to find the length of the -longest common subsequence -''' +def lcs(s1, s2): + cols = len(s1) + 1 + rows = len(s2) + 1 + t = [[0 for i in range(cols)] for i in range(rows)] -def lcs(S1, S2): - m = len(S1) - n = len(S2) + max_length = 0 - LCS = [[-1]*(n + 1) for i in range(m + 1)] - - for i in range(m + 1): - for j in range(n + 1): - if i == 0 or j == 0: - LCS[i][j] = 0 - elif S1[i-1] == S2[j-1]: - LCS[i][j] = LCS[i-1][j-1]+1 + for i in range(1, rows): + for j in range(1, cols): + if s2[i-1] == s1[j-1]: + t[i][j] = 1 + t[i-1][j-1] else: - LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1]) + t[i][j] = max(t[i-1][j], t[i][j-1]) + + max_length = max(max_length, t[i][j]) - return LCS[m][n] + return max_length diff --git a/algorithms/dynamic_programming/longest_common_substring.py b/algorithms/dynamic_programming/longest_common_substring.py new file mode 100644 index 00000000..bf6995ad --- /dev/null +++ b/algorithms/dynamic_programming/longest_common_substring.py @@ -0,0 +1,15 @@ +def lcs(s1, s2): + cols = len(s1) + 1 + rows = len(s2) + 1 + + t = [[0 for i in range(cols)] for i in range(rows)] + + max_length = 0 + + for i in range(1, rows): + for j in range(1, cols): + if s2[i-1] == s1[j-1]: + t[i][j] = t[i-1][j-1] + 1 + max_length = max(max_length, t[i][j]) + + return max_length diff --git a/algorithms/dynamic_programming/longest_consecutive_subsequence.py b/algorithms/dynamic_programming/longest_consecutive_subsequence.py new file mode 100644 index 00000000..742ef4b7 --- /dev/null +++ b/algorithms/dynamic_programming/longest_consecutive_subsequence.py @@ -0,0 +1,45 @@ +""" +Given an array of integers, find the length of the longest sub-sequence +such that elements in the subsequence are consecutive integers, the +consecutive numbers can be in any order. + +The idea is to store all the elements in a set first. Then as we are iterating +over the array, we check two things - +1. a number x can be a starting number in a sequence if x-1 is not present in the +set. If this is the case, create a loop and check how many elements from x to x+j are +in the set +2. if x -1 is there in the set, do nothing as this number is not a starting element +and must have been considered in a different sequence +""" + +def find_seq(arr, n): + s = set() + + for num in arr: + s.add(num) + + ans = 0 + elements = [] + + for i in range(n): + temp = [] + + if arr[i] - 1 not in s: + j = arr[i] + + while j in s: + temp.append(j) + j += 1 + + if j - arr[i] > ans: + ans = j - arr[i] + elements = temp.copy() + + return ans, elements + + +arr = [36, 41, 56, 35, 44, 33, 34, 92, 43, 32, 42] + +ans, elements = find_seq(arr, len(arr)) +print('Length - ', ans) +print('Elements - ', elements) diff --git a/algorithms/dynamic_programming/longest_increasing_consecutive_subsequence.py b/algorithms/dynamic_programming/longest_increasing_consecutive_subsequence.py new file mode 100644 index 00000000..c272820c --- /dev/null +++ b/algorithms/dynamic_programming/longest_increasing_consecutive_subsequence.py @@ -0,0 +1,26 @@ +""" +Find the longest increasing consecutive subsequence in an array + +Idea - + +create a dictionary 'seq' and start iterating over the array + +1. if arr[i] - 1 exists in the array, length = length + seq[arr[i] - 1] +2. else, seq[i] = 1 +""" + +def find_seq(arr): + seq = {} + count = 0 + + for num in arr: + if num - 1 in seq: + seq[num] = seq[num - 1] + 1 + count = max(count, seq[num]) + else: + seq[num] = 1 + + return count + +arr = [6, 7, 8, 3, 4, 5, 9, 10] +print(find_seq(arr)) diff --git a/algorithms/dynamic_programming/longest_increasing_subsequence.py b/algorithms/dynamic_programming/longest_increasing_subsequence.py new file mode 100644 index 00000000..8a9fa822 --- /dev/null +++ b/algorithms/dynamic_programming/longest_increasing_subsequence.py @@ -0,0 +1,13 @@ +def LIS(arr): + n = len(arr) + if n == 0: return 0 + res = 1 + dp = [0] * n + dp[0] = 1 + for i in range(1, n): + dp[i] = 1; + for j in range(0 , i): + if arr[i] > arr[j]: + dp[i] = max(dp[i] , dp[j] + 1) + res = max(res , dp[i]) + return res \ No newline at end of file diff --git a/algorithms/dynamic_programming/longest_palindromic_subsequence.py b/algorithms/dynamic_programming/longest_palindromic_subsequence.py deleted file mode 100644 index e0d6f3bd..00000000 --- a/algorithms/dynamic_programming/longest_palindromic_subsequence.py +++ /dev/null @@ -1,40 +0,0 @@ -''' -Question : -Given a string consisting of English alphabets, the task is to count the alphabets in the longest palindromic subsequence. - -For example : -Input: str = "BBABCBCAB" -Output: 7 -"BABCBAB" is the longest palindromic subseuqnce in it. "BBBBB"" and "BBCBB" are also palindromic subsequences of the given sequence, but not the longest ones. - -Source: https://www.geeksforgeeks.org/python-program-for-longest-palindromic-subsequence-dp-12/ -''' - -def lps(str): - n = len(str) - - # Create a table to store results of subproblems - L = [[0 for x in range(n)] for x in range(n)] - - # Strings of length 1 are palindrome of length 1 - for i in range(n): - L[i][i] = 1 - - # Build the table. Note that the lower - # diagonal values of table are - # useless and not filled in the process. - for cl in range(2, n + 1): - for i in range(n-cl + 1): - j = i + cl-1 - if str[i] == str[j] and cl == 2: - L[i][j] = 2 - elif str[i] == str[j]: - L[i][j] = L[i + 1][j-1] + 2 - else: - L[i][j] = max(L[i][j-1], L[i + 1][j]); - - return L[0][n-1] - -# Test inputs -seq = "BBABCBCAB" -print("The length of the LPS is " + str(lps(seq))) diff --git a/algorithms/dynamic_programming/longest_palindromic_substring.py b/algorithms/dynamic_programming/longest_palindromic_substring.py new file mode 100644 index 00000000..251d2e7f --- /dev/null +++ b/algorithms/dynamic_programming/longest_palindromic_substring.py @@ -0,0 +1,57 @@ +def longest_palindromic_substring_DP(s): + + S = [[False for i in range(len(s))] for j in range(len(s))] + + max_palindrome = "" + + for i in range(len(s))[::-1]: + for j in range(i, len(s)): + # if j - 1 < 3, then there is one or two characters between these + # two positions, implying that if s[i] == s[j] + # then that small string is a palindrome + # We check if the above cases is valid or i + # they are larger, we use DP to check the substring + # in between j and i + S[i][j] = s[i] == s[j] and (j - i < 3 or S[i+1][j-1]) + if S[i][j] and j - i + 1 > len(max_palindrome): + max_palindrome = s[i:j+1] + + return max_palindrome + + +def longest_palindromic_substring_expansion(s): + max_palindrome = "" + + for i in range(len(s) * 2 - 1): + if i % 2 == 0: + # This is when you are "on" an actual character + # o = offset, ind = current character + o = 0 + ind = i // 2 + while ind + o < len(s) and ind - o >= 0: + if(s[ind + o] != s[ind - o]): + break + if ind + o - (ind - o) + 1 > len(max_palindrome): + max_palindrome = s[ind-o:ind+o + 1] + o += 1 + else: + # This is when you are "in the middle of" two characters + # o = offset, sind = start char, eind = end char + o = 0 + sind = i // 2 + eind = i // 2 + 1 + while sind - o >= 0 and eind + o < len(s): + if(s[sind - o] != s[eind + o]): + break + if eind + o - (sind - o) + 1 > len(max_palindrome): + max_palindrome = s[sind - o:eind + o + 1] + o += 1 + + return max_palindrome + + +input_string = "abbbacdcaacdca" + +ans_DP = longest_palindromic_substring_DP(input_string) +ans_expansion = longest_palindromic_substring_expansion(input_string) +print("DP Solution: {}, Expansion Solution: {}".format(ans_DP, ans_expansion)) diff --git a/algorithms/dynamic_programming/longest_subarray_sum_divisible_by_k.py b/algorithms/dynamic_programming/longest_subarray_sum_divisible_by_k.py new file mode 100644 index 00000000..4d691f59 --- /dev/null +++ b/algorithms/dynamic_programming/longest_subarray_sum_divisible_by_k.py @@ -0,0 +1,55 @@ +""" +Find the longest subarray in an array whose sum is ` +divisible by k + +source - https://www.geeksforgeeks.org/longest-subarray-sum-divisible-k/ + +The idea is that we create a new array mod_arr where we mod_arr[i] = +sum(arr[0]...arr[i]) % k. So basically this array tells us that upto this +point in the input array, if we take sum of numbers till index i, that sum will +be divisible by k + +We will be creating a hash table for this to store the mod results + +Now, lets say x = sum(arr[0]...arr[i]) % k = mod_arr[i]. If + +1. if we find x == 0, increment length by 1 +2. if x not in hash, create it and store (x, index of x) +3. if x in hash: + this tells us that upto this point, where the remainder of sum of numbers + till this point divided by k is x, that remainder we already saw before as it + exists in the hash. So if we ignore the first dont consider the first occurence of x + and remove that from the sum, then this sum will be divisible by k (because subtracting remainder + from a number makes it divisible). + Now find the max length of such case as + if length = max(length, (i - index(x)) +""" + +def find_length(arr, k): + hash_table = {} + mod_arr = [] + s = 0 + length = 0 + start, end = 0, 0 + + for i in range(0, len(arr)): + s += arr[i] + mod_arr.append(s % k) + + for i in range(0, len(mod_arr)): + if mod_arr[i] == 0: + length += 1 + else: + if mod_arr[i] not in hash_table: + hash_table[mod_arr[i]] = i + else: + if length < (i - mod_arr[i]): + length = i - mod_arr[i] + start = mod_arr[i] + end = i - 1 # i-1 because the current number is not to considered as it makes the sum not divisible by k + + return length, arr[start:end+1] + + +arr = [ 2, 7, 6, 1, 4, 5 ] +print(find_length(arr, 3)) diff --git a/algorithms/dynamic_programming/longest_subarray_with_no_pairsum_divisible_by_k.py b/algorithms/dynamic_programming/longest_subarray_with_no_pairsum_divisible_by_k.py new file mode 100644 index 00000000..844b5611 --- /dev/null +++ b/algorithms/dynamic_programming/longest_subarray_with_no_pairsum_divisible_by_k.py @@ -0,0 +1,54 @@ +""" +Find the longest subarray in the input array such that the pairwise sum of +the elements of this subarray is not divisible by K + +The idea is - +How can we tell that two numbers x and y will make a pairsum that will be +divisible by K just by looking at their remainders? There can be two conditions + +1. It will be only possible if the sum of the remainders when x and y are +divided by K is equal to K. As the sum of the remainders cannot exceed K +so if it reaches K then it means that the sum of those numbers will also be +divisible by K +0 < (X%K) + (Y%K) <= K + +2. If arr[i] % k == 0 and there is also an element j such that arr[j] % k == 0 +and 0 exists in the hash (i.e hash[j] = True) +""" + +def find_subarray(arr, k): + """ + True means divisible by k + """ + start, end = 0, 0 + max_start, max_end = 0, 0 + + n = len(arr) + mod_arr = [0] * n + + mod_arr[arr[0] % k] = mod_arr[arr[0] % k] + 1 + + for i in range(1, n): + mod = arr[i] % k + + while (mod_arr[k - mod] != 0) or (mod == 0 and mod_arr[mod] != 0): + mod_arr[arr[start] % k] = mod_arr[arr[start] % k] - 1 + start += 1 + + mod_arr[mod] = mod_arr[mod] + 1 + end += 1 + + if (end - start) > (max_end - max_start): + max_end = end + max_start = start + + print(f'Max size is {max_end - max_start}') + + for i in (max_start, max_end + 1): + print(arr[i], end=" ") + + +arr = [3, 7, 1, 9, 2] +k = 3 +find_subarray(arr, k) + diff --git a/algorithms/dynamic_programming/max_subarray.py b/algorithms/dynamic_programming/max_subarray.py deleted file mode 100644 index 5a1caa43..00000000 --- a/algorithms/dynamic_programming/max_subarray.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -High Level Description: -Given an integer array nums, find the contiguous subarray (containing at least -one number) which has the largest sum and return its sum. - -Time Complexity: -O(n) -""" - - -# Iterative Solution -def max_sub_array(self, arr): - if not arr: - return 0 - - cur_sum = max_sum = arr[0] - for num in arr[1:]: - cur_sum = max(num, cur_sum + num) - max_sum = max(max_sum, cur_sum) - - return max_sum diff --git a/algorithms/dynamic_programming/partition_sum.py b/algorithms/dynamic_programming/partition_sum.py new file mode 100644 index 00000000..c07b79f6 --- /dev/null +++ b/algorithms/dynamic_programming/partition_sum.py @@ -0,0 +1,46 @@ +# A Dynamic Programming based +# Python3 program to partition problem + +# Returns true if arr[] can be partitioned +# in two subsets of equal sum, otherwise false +def find_partiion(arr, n) : + sum = 0 + + # Calculate sum of all elements + for i in range(n) : + sum += arr[i] + if (sum % 2 != 0) : + return 0 + part = [0] * ((sum // 2) + 1) + + # Initialize the part array as 0 + for i in range((sum // 2) + 1) : + part[i] = 0 + + # Fill the partition table in bottom up manner + for i in range(n) : + + # the element to be included + # in the sum cannot be + # greater than the sum + for j in range(sum // 2, arr[i] - 1, -1) : + + # check if sum - arr[i] + # could be formed + # from a subset + # using elements + # before index i + if (part[j - arr[i]] == 1 or j == arr[i]) : + part[j] = 1 + + return part[sum // 2] + +# Drive code +arr = [ 1, 3, 3, 2, 3, 2 ] +n = len(arr) + +# Function call +if (find_partiion(arr, n) == 1) : + print("Can be divided into two subsets of equal sum") +else : + print("Can not be divided into two subsets of equal sum") diff --git a/algorithms/dynamic_programming/prefix_function.py b/algorithms/dynamic_programming/prefix_function.py new file mode 100644 index 00000000..c2cd4385 --- /dev/null +++ b/algorithms/dynamic_programming/prefix_function.py @@ -0,0 +1,19 @@ + +def prefix_function(s: str) -> [int]: + """ + The prefix function for string s is defined as an array pi of length n, + where pi[i] is the length of the longest proper prefix of the substring + s[0...i] which is also a suffix of this substring. A proper prefix of a + string is a prefix that is not equal to the string itself. + By definition, pi[0] = 0. + """ + n = len(s) + pi = [0] * n + for i in range(1, n): + j = pi[i - 1] + while (j > 0) and (s[i] != s[j]): + j = pi[j - 1] + if s[i] == s[j]: + j += 1 + pi[i] = j + return pi diff --git a/algorithms/dynamic_programming/prefix_sums.py b/algorithms/dynamic_programming/prefix_sums.py new file mode 100644 index 00000000..2a0b70db --- /dev/null +++ b/algorithms/dynamic_programming/prefix_sums.py @@ -0,0 +1,11 @@ +def prefix_sums(ls: [int]) -> [int]: + """ + Returns list of prefix sums for given list of integers. + """ + n = len(ls) + total = 0 + sums = [0] * n + for i in range(n): + total += ls[i] + sums[i] = total + return sums diff --git a/algorithms/dynamic_programming/rod_cutting.py b/algorithms/dynamic_programming/rod_cutting.py deleted file mode 100644 index 9e807bb0..00000000 --- a/algorithms/dynamic_programming/rod_cutting.py +++ /dev/null @@ -1,22 +0,0 @@ -''' -Given a rod of length n units and an -array of prices that contains prices -of all pieces of size less than n. We -need to find the maximum maximum price -obtainable by cutting the rod and -selling it. -''' -INT_MIN = -32767 - - -def cutRod(price, n): - values = [0 for i in range(n+1)] - values[0] = 0 - - for i in range(1, n+1): - max_value = INT_MIN - for j in range(i): - max_value = max(max_value, price[j] + values[i-j-1]) - values[i] = max_value - - return values[n] diff --git a/algorithms/dynamic_programming/unique_grid_paths.py b/algorithms/dynamic_programming/unique_grid_paths.py deleted file mode 100644 index a5518ba3..00000000 --- a/algorithms/dynamic_programming/unique_grid_paths.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -High Level Description: -A preson is located at the top-left corner of a m x n grid (marked 'Start' in -the diagram below). The preson can only move either down or right at any point -in time. The preson is trying to reach the bottom-right corner of the grid -(marked 'Finish' in the diagram below). -How many possible unique paths are there? - -Time Complexity: -O(m*n) -""" - - -def number_of_paths(m, n): - count = [[0 for x in range(m)] for y in range(n)] - - for i in range(m): - count[i][0] = 1 - - for j in range(n): - count[0][j] = 1 - - for i in range(1, m): - for j in range(n): - count[i][j] = count[i-1][j] + count[i][j-1] - return count[m-1][n-1] diff --git a/algorithms/graph/bfs.py b/algorithms/graph/bfs.py deleted file mode 100644 index 6c64f75f..00000000 --- a/algorithms/graph/bfs.py +++ /dev/null @@ -1,49 +0,0 @@ -# Python3 Program to print BFS traversal -# from a given source vertex. BFS(int s) -# traverses vertices reachable from s. -from collections import defaultdict - -# This class represents a directed graph -# using adjacency list representation -class Graph: - - # Constructor - def __init__(self): - - # default dictionary to store graph - self.graph = defaultdict(list) - - # function to add an edge to graph - def addEdge(self,u,v): - self.graph[u].append(v) - - # Function to print a BFS of graph - def BFS(self, s): - - # Mark all the vertices as not visited - visited = [False] * (len(self.graph)) - - # Create a queue for BFS - queue = [] - - # Mark the source node as - # visited and enqueue it - queue.append(s) - visited[s] = True - - while queue: - - # Dequeue a vertex from - # queue and print it - s = queue.pop(0) - print (s, end = " ") - - # Get all adjacent vertices of the - # dequeued vertex s. If a adjacent - # has not been visited, then mark it - # visited and enqueue it - for i in self.graph[s]: - if visited[i] == False: - queue.append(i) - visited[i] = True - \ No newline at end of file diff --git a/algorithms/graph/dfs.py b/algorithms/graph/dfs.py deleted file mode 100644 index fbd33795..00000000 --- a/algorithms/graph/dfs.py +++ /dev/null @@ -1,45 +0,0 @@ -# Python program to print DFS traversal for complete graph -from collections import defaultdict - -# This class represents a directed graph using adjacency -# list representation -class Graph: - - # Constructor - def __init__(self): - - # default dictionary to store graph - self.graph = defaultdict(list) - - # function to add an edge to graph - def addEdge(self,u,v): - self.graph[u].append(v) - - # A function used by DFS - def DFSUtil(self, v, visited): - - # Mark the current node as visited and print it - visited[v]= True - print v, - - # Recur for all the vertices adjacent to - # this vertex - for i in self.graph[v]: - if visited[i] == False: - self.DFSUtil(i, visited) - - - # The function to do DFS traversal. It uses - # recursive DFSUtil() - def DFS(self): - V = len(self.graph) #total vertices - - # Mark all the vertices as not visited - visited =[False]*(V) - - # Call the recursive helper function to print - # DFS traversal starting from all vertices one - # by one - for i in range(V): - if visited[i] == False: - self.DFSUtil(i, visited) \ No newline at end of file diff --git a/algorithms/graph/find_all_paths.py b/algorithms/graph/find_all_paths.py deleted file mode 100644 index 9152bf2a..00000000 --- a/algorithms/graph/find_all_paths.py +++ /dev/null @@ -1,32 +0,0 @@ -''' -find all the possible paths in a directed cyclic graph from -a start point to a end point. -''' - - - - -def find_all_paths(graph, start, end, path=[]): - path = path + [start] - if start == end: - return [path] - if start not in graph.keys(): - return [] - paths = [] - for node in graph[start]: - if node not in path: #to prevent cyclic rotations - newpaths = find_all_paths(graph, node, end, path) - #print(newpaths) - for newpath in newpaths: - paths.append(newpath) - return paths - - -graph={1:[2,4], - 2:[3], - 4:[5], - 3:[5] - } - -for i in graph.keys(): - print(i,'to 5',find_all_paths(graph,i,5)) diff --git a/algorithms/graph/index.md b/algorithms/graph/index.md deleted file mode 100644 index e5197adc..00000000 --- a/algorithms/graph/index.md +++ /dev/null @@ -1,4 +0,0 @@ -# Index of graph - -* dfs.py -* bfs.py diff --git a/algorithms/greedy/__init__.py b/algorithms/greedy/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/algorithms/greedy/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/algorithms/greedy/activity_selection.py b/algorithms/greedy/activity_selection.py new file mode 100644 index 00000000..374ad563 --- /dev/null +++ b/algorithms/greedy/activity_selection.py @@ -0,0 +1,33 @@ +#Prints a maximum set of activities that can be done by a +#single person, one at a time +#n --> Total number of activities +#s[]--> An array that contains start time of all activities +#f[] --> An array that contains finish time of all activities + + +def find_activities(arr): + n = len(arr) + selected = [] + + arr.sort(key = lambda x: x[1]) + + i = 0 + + # since it is a greedy algorithm, the first acitivity is always + # selected because it is the most optimal choice at that point + selected.append(arr[i]) + + for j in range(1, n): + start_time_next_activity = arr[j][0] + end_time_prev_activity = arr[i][1] + + if start_time_next_activity >= end_time_prev_activity: + selected.append(arr[j]) + i = j + + return selected + + +arr = [[5, 9], [1, 2], [3, 4], [0, 6],[5, 7], [8, 9]] +print(find_activities(arr)) + diff --git a/algorithms/greedy/cost_of_tiles.py b/algorithms/greedy/cost_of_tiles.py new file mode 100644 index 00000000..05fde8da --- /dev/null +++ b/algorithms/greedy/cost_of_tiles.py @@ -0,0 +1,45 @@ +""" +Find the min cost of tiles to cover a floor. +Floor is represented by 2D array where - +* = tile already placed +. = no tile + +tiles available are 1*1 and 1*2 and their costs +are A and B + +Source - https://www.geeksforgeeks.org/minimize-cost-to-cover-floor-using-tiles-of-dimensions-11-and-12/ +""" + +def cost(arr, A, B): + n = len(arr) + m = len(arr[0]) + + ans = 0 + + for i in range(n): + j = 0 + + while j < m: + if arr[i][j] == '*': # tile is already there + j += 1 + continue + + if j == m - 1: # if j is pointing to last tile, you can use only 1*1 tile + ans += A + else: + if arr[i][j+1] == '.': + ans += min(2 * A, B) + j += 1 + else: + ans += A + + j += 1 + + print('Cost of tiling is - ', ans) + +arr = [ [ '.', '.', '*' ], + [ '.', '*', '*' ] ] + +A, B = 2, 10 + +cost(arr, A, B) diff --git a/algorithms/greedy/egyptian_fraction.py b/algorithms/greedy/egyptian_fraction.py new file mode 100644 index 00000000..3d3ea79d --- /dev/null +++ b/algorithms/greedy/egyptian_fraction.py @@ -0,0 +1,13 @@ +# Reference - https://www.geeksforgeeks.org/greedy-algorithm-egyptian-fraction/ + +import math + +def egyptian_fraction(nr, dr): + ef = [] + + while nr != 0: + x = math.ceil(dr / nr) + ef.append(x) + + nr = x * nr - dr + dr = dr * x diff --git a/algorithms/greedy/index.md b/algorithms/greedy/index.md deleted file mode 100644 index d9b79484..00000000 --- a/algorithms/greedy/index.md +++ /dev/null @@ -1 +0,0 @@ -# Index of Greedy \ No newline at end of file diff --git a/algorithms/greedy/min_platforms.py b/algorithms/greedy/min_platforms.py new file mode 100644 index 00000000..fd372f0a --- /dev/null +++ b/algorithms/greedy/min_platforms.py @@ -0,0 +1,37 @@ +""" +Given the arrival and departure times of buses at a station +find the min number of platforms that must be there +""" + + +def find_platforms(arrival, departure): + n = len(arrival) + + arrival.sort() + departure.sort() + + i = 1 + j = 0 + + ans = 1 # atleast one platform is required + plat = 1 + + while i < n and j < n: + if arrival[i] <= departure[j]: + plat += 1 + i += 1 + + elif arrival[i] > departure[j]: + plat -= 1 + j += 1 + + ans = max(ans, plat) + + + return ans + + +arr = [900, 940, 950, 1100, 1500, 1800] +dep = [910, 1200, 1120, 1130, 1900, 2000] + +print(find_platforms(arr, dep)) diff --git a/algorithms/math/divisors.py b/algorithms/math/divisors.py new file mode 100644 index 00000000..ea923837 --- /dev/null +++ b/algorithms/math/divisors.py @@ -0,0 +1,63 @@ +import math + +class divisors: + + def findAllDivisors(self, n): + result = list() + + for i in range(1, n+1): + if (n%i == 0): + result.append(i) + + return result + + def divisorsCount(self, n): + divs = self.findAllDivisors(n) + return len(divs) + + def oddFactors(self, n): + result = list() + + for i in range(1, n+1): + if (n%i == 0 and i%2==1): + result.append(i) + + return result + + def oddFactorsSum(self, n): + divs = self.evenFactors(n) + return sum(divs) + + def evenFactors(self, n): + result = list() + + for i in range(1, n+1): + if (n%i == 0 and i%2==0): + result.append(i) + + return result + + def evenFactorsSum(self, n): + divs = self.evenFactors(n) + return sum(divs) + + def primeFactors(self, n): + result = list() + + while n % 2 == 0: + result.append(2) + n = n / 2 + + for i in range(3,int(math.sqrt(n))+1,2): + while n % i== 0: + result.append(i) + n = n / i + + if n > 2: + result.append(n) + + return result + + def primeFactorsSum(self, n): + divs = self.primeFactors(n) + return sum(divs) \ No newline at end of file diff --git a/algorithms/math/factorial_iterative.py b/algorithms/math/factorial_iterative.py new file mode 100644 index 00000000..d722ec92 --- /dev/null +++ b/algorithms/math/factorial_iterative.py @@ -0,0 +1,18 @@ +#Calculate factorial of a given number using iterative method. + +def factorial(number): + + answer = 1 + + if number == 0: + return 1 + else: + for num in range(1, number+1): + answer = answer * num + return answer + +if __name__ == '__main__': + + enter_number = int(input("Enter a number whose factorial is required : ")) + result = factorial(enter_number) + print(result) \ No newline at end of file diff --git a/algorithms/math/factorial_recursive.py b/algorithms/math/factorial_recursive.py new file mode 100644 index 00000000..ed5f3b38 --- /dev/null +++ b/algorithms/math/factorial_recursive.py @@ -0,0 +1,16 @@ +#Calculate factorial of a given number using recursive method. + +def factorial(number): + + if number == 0 or number == 1: + return 1 + + answer = number * factorial(number - 1) + + return answer + +if __name__ == '__main__': + + enter_number = int(input("Enter a number whose factorial is required : ")) + result = factorial(enter_number) + print(result) \ No newline at end of file diff --git a/algorithms/math/fibonacci_number.py b/algorithms/math/fibonacci_number.py index ad58a349..a355f32f 100644 --- a/algorithms/math/fibonacci_number.py +++ b/algorithms/math/fibonacci_number.py @@ -1,26 +1,15 @@ # Program that prints out the fibonacci sequence until a given number -def calc_fib(num): - while len(fib) <= num: - n = len(fib) - fib.append((fib[n-1] + fib[n-2])) - + def main(): - print("Enter the Position of the Number in the Sequence or \'0\' to Quit: ") - num = 0 - fib = list() - fib.append(0) - fib.append(1) + n = int(input("insert number")) + fib = [1, 1] + for i in range(2,n,1): + print(i) + fib.append(fib[i-1]+fib[i-2]) - while True: - num = int(input()) - if(num <= 0): - break + print(fib) - if len(fib) <= num: - calc_fib(num) - print('Fibonacci Number at Position ' + str(num) + ' is: ' + str(fib[num])) - if __name__ == '__main__': main() diff --git a/algorithms/math/greatest_common_divisor.py b/algorithms/math/greatest_common_divisor.py index d00efe53..53e70f1d 100644 --- a/algorithms/math/greatest_common_divisor.py +++ b/algorithms/math/greatest_common_divisor.py @@ -26,4 +26,10 @@ def gcd(x, y): if y == 0: return x - return gcd(y, x % y) \ No newline at end of file + return gcd(y, x % y) + +# Iterative optimized +def gcd(x, y): + while y != 0: + x, y = y, x % y + return x diff --git a/algorithms/math/index.md b/algorithms/math/index.md index a02f7ae0..d6248d88 100644 --- a/algorithms/math/index.md +++ b/algorithms/math/index.md @@ -4,4 +4,7 @@ * [Fibonacci Number](fibonacci_number.py) * [Nth Prime Number](prime.py) * [Sieve of Erastothenes](sieve_of_eratosthenes.py) - +* [Perfect Square](perfect_square.py) +* [Number Convertion](number_convertion.py) +* [Iterative Factorial](factorial_iterative.py) +* [Recursive Factorial](factorial_recursive.py) diff --git a/algorithms/math/number_convertion.py b/algorithms/math/number_convertion.py new file mode 100644 index 00000000..e119eb9c --- /dev/null +++ b/algorithms/math/number_convertion.py @@ -0,0 +1,137 @@ +bases = { + "binary": 2, + "octal": 8, + "decimal": 10, + "hex": 16, + "hexadecimal": 16 +} + +default = 10 + + +def verify_base(x): + """ + Verify if already is an integer + If is not verify if is in the bases + If is not return the default base + """ + try: + return int(x) + except: + if x in bases: + return bases[x] + else: + return default + + +def decimal_value(x): + """ + ord(x) is a function that return the number in asc2 table + we use ord to get the number of an caracter + """ + # TODO verify if in the base exist the character like: + # 'z' doesnt exisist in decimal values + if(x >= '0' and x <= '9'): + return int(x) + elif(x >= 'a' and x <= 'z'): + return int(ord(x) - ord('a') + 10) + elif(x >= 'A' and x <= 'Z'): + return int(ord(x) - ord('A') + 10) + else: + # Error + raise Exception('Number not valid for: ', x) + + +def to_special_caracter(x): + if(x >= 0 and x <= 9): + return str(x) + elif(x > 9): + return chr(ord('a') + x - 10) + else: + raise Exception('Not valid negative number in converter: ', x) + + +def convert(n, from_base, to_base): + """ + This algorithm convert numbers between any base to any base like: + + convert("10",2,10) + ~> "2" + + This function recive 3 parameters + n -> the number + from_base -> base of n | + to_base -> base of the result | + you can pass the bases as number like base 2 or as string like "binary" + + Why the n and the return is an string? + Because bases greater the 10 use leathers to represents the numbers + """ + n = str(n) + from_base = verify_base(from_base) + to_base = verify_base(to_base) + + # Corner case 0 + if(n == "0"): + return n + + if(n[0] == '-'): + n = n[1:] + negative = True + else: + negative = False + + # We convert to decimal because is the easy way to convert to all + multi = 1 + decimal_number = 0 + if(from_base == 10): + decimal_number = int(n) + else: + for i in range(len(n) - 1, -1, -1): + decimal_number += (multi * decimal_value(n[i])) + multi *= from_base + + if(to_base == 10): + decimal_number = str(decimal_number) + if(negative): + decimal_number = '-' + decimal_number + + return decimal_number + + result = "" + + while(decimal_number > 0): + value = decimal_number % to_base + result = to_special_caracter(value) + result + decimal_number = int((decimal_number - value)/to_base) + + if(negative): + result = '-' + result + + return result + + +def test_convert(): + print(convert("1111000111", 2, 8) == "1707") + print(convert("1111000111", 2, 10) == "967") + print(convert("1111000111", 2, 16) == "3c7") + print(convert("1234567", 8, 2) == "1010011100101110111") + print(convert("1234567", 8, 10) == "342391") + print(convert("1234567", 8, 16) == "53977") + print(convert("987123", 10, 2) == "11110000111111110011") + print(convert("987123", 10, 8) == "3607763") + print(convert("987123", 10, 16) == "f0ff3") + print(convert("abcdef", 16, 2) == "101010111100110111101111") + print(convert("abcdef", 16, 8) == "52746757") + print(convert("abcdef", 16, 10) == "11259375") + + print(convert("179", 10, 16) == "b3") + print(convert("b3", 16, 10) == "179") + + # Negative Tests + print("Negative Tests") + print(convert("-179", 10, 16) == "-b3") + print(convert("-b3", 16, 10) == "-179") + print(convert("-1111000111", 2, 10) == "-967") + +test_convert() diff --git a/algorithms/math/perfect_square.py b/algorithms/math/perfect_square.py new file mode 100644 index 00000000..40e817bb --- /dev/null +++ b/algorithms/math/perfect_square.py @@ -0,0 +1,21 @@ +def is_perfect_square(num): + """ + Given a positive integer num, write a function which returns True if num is a perfect square else False. + + Note: Do not use any built-in library function such as `sqrt`. + + :type num: int + :rtype: bool + """ + i = 0 + while i * i < num: + i += 1 + if i * i == num: + return True + else: + return False + + +# Test +print(is_perfect_square(16)) +print(is_perfect_square(14)) diff --git a/algorithms/math/power_of_two.py b/algorithms/math/power_of_two.py new file mode 100644 index 00000000..6fcfdb22 --- /dev/null +++ b/algorithms/math/power_of_two.py @@ -0,0 +1,34 @@ +""" + This simple code is to check if a given number is a poer of two or not. + Method: + if a number is a power of two, then its binary representation is (2^k). + ex: 4 --> 2^2 --> 100 --> k = 2 + 8 --> 2^3 --> 1000 --> k = 3 + 16--> 2^4 --> 10000 --> k = 4 + + assume that n is a power of two, then (n-1)&(n) will be zero; + ex: + n = 8 --> (1000) , n-1 = 7 ---> (0111) + performing bit (and) operation between both, then n&(n-1) = 0000 + + n = 12 --> (1100) , n-1 = 11 ---> (1011) + performing bit (and) operation between both, then n&(n-1) = 1000 + + Conclusion: + The result of the above bit "and" operation will be zero, ONLY if the given number is a pwoer of two. + + NOTE: + - Since Python considers 0 as "false", then we are gonna return the inversion of the result; i.e. return not(n&(n-1). + - BUT If n = 0, the result will be zero indicating that 0 is a power of 2, wich is not true. + So to fix that, we are going to perform an extra logical "and" operation with the oreginal number. +""" + + +def pow_of_two(n): + return(n and (not(n&(n-1)))) + +for i in range(20): + if pow_of_two(i): + print(f"{i} is a power of 2.") + else: + print(f"{i} is NOT a power of 2.") diff --git a/algorithms/math/recursive_fibonacci.py b/algorithms/math/recursive_fibonacci.py index 4094e6b9..eb319cd9 100644 --- a/algorithms/math/recursive_fibonacci.py +++ b/algorithms/math/recursive_fibonacci.py @@ -1,5 +1,6 @@ """ - Recursivly compute the Fibonacci sequence + Recursivly compute the Fibonacci sequence using two different methods + rec_fib(n) requires O(Fibo(n)) operations, whereas binary_rec_fib(n) requires less than O(n) """ def rec_fib(n): @@ -10,10 +11,23 @@ def rec_fib(n): else: return rec_fib(n-1)+rec_fib(n-2) +def binary_rec_fib(n): + if n == 2 or n == 1: + return 1 + elif n == 0: + return 0 + else: + # This recursive step takes advantage of the following two properties of the fibonacci numbers: + # Fibo(2n) = Fibo(n+1)^2 + Fibo(n)^2 + # Fibo(2n+1) = Fibo(n+1)^2 - Fibo(n-1)^2 + sgn = n % 2 + return binary_rec_fib((n-sgn)/2 + 1)**2 - ((-1)**sgn) * binary_rec_fib((n+sgn)/2 - 1)**2 + def main(): + times = [] n : int = int(input("n := ")) for i in range(0, n): - print(rec_fib(i)) + print(binary_rec_fib(i)) if __name__ == "__main__": main() diff --git a/algorithms/math/sumofdigits.py b/algorithms/math/sumofdigits.py index 62ba2177..8dd9040c 100644 --- a/algorithms/math/sumofdigits.py +++ b/algorithms/math/sumofdigits.py @@ -1,8 +1,11 @@ -#This is to find the sum of digits of a number untill it is a single digit -n=int(input()) #here n is the number -if n%9!=0: - print(n%9) -else: - print("9") -#this method reduces time complexity by a factor of n and also without using any loop +# This is to find the sum of digits of a number until it is a single digit + +def sum_of_digits(n): + n = int(input()) # here n is the number + if n % 9 != 0: + print(n % 9) + else: + print("9") + +# This method reduces time complexity by a factor of n and also without using any loop diff --git a/algorithms/miscellaneous/front_and_back_search.py b/algorithms/miscellaneous/front_and_back_search.py new file mode 100644 index 00000000..04e129ac --- /dev/null +++ b/algorithms/miscellaneous/front_and_back_search.py @@ -0,0 +1,26 @@ +def front_and_back_search(lst, item): + ''' + args: + lst: an unsorted array of integers + item: data to be found + + return: + item which is found else False + ''' + rear=0 + front=len(lst)-1 + u=None + if rear>front: + return False + else: + while rear<=front: + if item==lst[rear] or item==lst[front]: + u='' + return True ##item found + elif item!=lst[rear] and item!=lst[front]: + if item > lst[rear]: + rear=rear+1 + elif item < lst[front]: + front=front-1 + if u==None: + return False diff --git a/algorithms/miscellaneous/markov.py b/algorithms/miscellaneous/markov.py new file mode 100755 index 00000000..bffe7675 --- /dev/null +++ b/algorithms/miscellaneous/markov.py @@ -0,0 +1,48 @@ +import codecs +import random + +MAX_LETTERS = 2000 + +def readFile(f, mp, k): + seed = '' + mostFreqSeed = seed + mostFreq = 1 + for line in f: + for ch in line: + seed, mostFreq, mostFreqSeed = processSeed(seed, mostFreqSeed, mostFreq, ch, k, mp) + return mostFreqSeed + +def processSeed(seed, mostFreqSeed, mostFreq, ch, k, mp): + seed += ch + if len(seed) == k+1: + oldSeed = seed[:-1] + mp.setdefault(oldSeed, []).append(ch) + if mostFreq < len(mp[oldSeed]): + mostFreq, mostFreqSeed = len(mp[oldSeed]), oldSeed + seed = seed[1:] + return seed, mostFreq, mostFreqSeed + +def generateText(mp, mostFreqSeed): + text, curSeed = mostFreqSeed, mostFreqSeed + while (len(text) < MAX_LETTERS): + ch = random.choice(mp[curSeed]) + text, curSeed = text+ch, curSeed+ch + curSeed = curSeed[1:] + return text + +def main(): + fileName = input("Enter the file name: ") + ".txt" + f = codecs.open(fileName, encoding='utf-8') + k = int(input("Enter the Markov order [1-10]: ")) + assert (k >= 1 and k <= 10) + mp = {} + mostFreqSeed = readFile(f, mp, k) + f.close() + text = generateText(mp, mostFreqSeed) + print(text) + result = codecs.open("result.txt", "w", encoding='utf-8') + result.write(text) + result.close() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/algorithms/sorting/bubble_sort.py b/algorithms/sorting/bubble_sort.py index 781b1f5a..1c4a24cc 100644 --- a/algorithms/sorting/bubble_sort.py +++ b/algorithms/sorting/bubble_sort.py @@ -1,12 +1,30 @@ -''' +""" Bubble Sort worst time complexity occurs when array is reverse sorted - O(n^2) Best time scenario is when array is already sorted - O(n) -''' +""" -def bubbleSort(array): +def bubble_sort(array): n = len(array) - for i in range(n): for j in range(0, n-i-1): if array[j] > array[j+1]: - array[j], array[j+1] = array[j+1], array[j] \ No newline at end of file + array[j], array[j+1] = array[j+1], array[j] + return array + + +def bubble_sort_optimized(array): + """ + Optimizes on bubble sort by taking care of already swapped cases + Reference - https://github.com/prabhupant/python-ds/pull/346 + """ + has_swapped = True + + num_of_iterations = 0 + + while has_swapped: + has_swapped = False + for i in range(len(array) - num_of_iterations - 1): + if array[i] > array[i + 1]: + array[i], array[i + 1] = array[i + 1], array[i] + has_swapped = True + num_of_iterations += 1 diff --git a/algorithms/sorting/counting_sort.py b/algorithms/sorting/counting_sort.py new file mode 100644 index 00000000..d9f359c8 --- /dev/null +++ b/algorithms/sorting/counting_sort.py @@ -0,0 +1,35 @@ +""" +High level description: +Counting sort is a sorting technique based on keys between a specific range, +with efective performance on the predetermined range size of the values. +It works by counting the number of objects having distinct key values (kind of hashing). +Then doing some arithmetic to calculate the position of each object in the output sequence. + +Time complexity: +O(n+k) where n is the number of elements in input array and k is the range of input. + +Auxiliary Space: O(n+k) +""" + +def counting_sort(arr): + # Find min and max values + min_value = min(arr) + max_value = max(arr) + + # Count number appearances in the array + counting_arr = [0]*(max_value-min_value+1) + for num in arr: + counting_arr[num-min_value] += 1 + + # Rearrange sequence in the array + index = 0 + for i, count in enumerate(counting_arr): + for _ in range(count): + arr[index] = min_value + i + index += 1 + +test_array = [3, 3, 2, 6, 4, 7, 9, 7, 8] + +counting_sort(test_array) + +print(test_array) diff --git a/algorithms/sorting/gnome_sort.py b/algorithms/sorting/gnome_sort.py new file mode 100644 index 00000000..444b127d --- /dev/null +++ b/algorithms/sorting/gnome_sort.py @@ -0,0 +1,38 @@ +''' +Gnome sort is not best sorting algorithms but sure it takes its pride. +It has time O(n^2) +''' + + +def gnome_sort(arr): + """ + Examples: + >>> gnome_sort([0, 5, 2, 3, 2]) + [0, 2, 2, 3, 5] + + >>> gnome_sort([]) + [] + >>> gnome_sort([-2, -45, -5]) + [-45, -5, -2] + """ + + # first case + size = len(arr) + + if size <= 1: + return arr + ind = 0 + # while loop + while ind < size: + if ind == 0: + ind += 1 + elif arr[ind] >= arr[ind - 1]: + ind += 1 + else: + # swap + temp = arr[ind - 1] + arr[ind - 1] = arr[ind] + arr[ind] = temp + ind -= 1 + + return arr diff --git a/algorithms/sorting/insertion_sort.py b/algorithms/sorting/insertion_sort.py index b19d9ddd..5d0ec752 100644 --- a/algorithms/sorting/insertion_sort.py +++ b/algorithms/sorting/insertion_sort.py @@ -15,12 +15,11 @@ def insertion_sort(lst): return lst test_data = [5,9,4,27,3,6] -print insertion_sort(test_data) +print(insertion_sort(test_data)) test_data = ['f','b','z','a','x'] -print insertion_sort(test_data) +print(insertion_sort(test_data)) # Resulting output: # [3, 4, 5, 6, 9, 27] # ['a', 'b', 'f', 'x', 'z'] - diff --git a/algorithms/sorting/merge_sort.py b/algorithms/sorting/merge_sort.py index f9eb554c..8dbf1d37 100644 --- a/algorithms/sorting/merge_sort.py +++ b/algorithms/sorting/merge_sort.py @@ -1,49 +1,49 @@ """ High level explanation: -mergeSort is a Divide and conquer algorithm that splits in halves the array and +merge_sort is a Divide and conquer algorithm that splits in halves the array and then builds it back up by merging and sorting at the same time its elements. Time complexity: -mergeSort has a time complexity of O(n log n). +merge_sort has a time complexity of O(n log n). """ -def mergeSort(arr): - if len(arr) >1: - mid = len(arr)//2 #Finding the mid of the array - L = arr[:mid] # Dividing the array elements - R = arr[mid:] # into 2 halves - - mergeSort(L) # Sorting the first half - mergeSort(R) # Sorting the second half - +def merge_sort(arr): + if len(arr) >1: + mid = len(arr)//2 #Finding the mid of the array + L = arr[:mid] # Dividing the array elements + R = arr[mid:] # into 2 halves + + merge_sort(L) # Sorting the first half + merge_sort(R) # Sorting the second half + i = j = k = 0 - - # Copy data to temp arrays L[] and R[] - while i < len(L) and j < len(R): - if L[i] < R[j]: - arr[k] = L[i] + + # Copy data to temp arrays L[] and R[] + while i < len(L) and j < len(R): + if L[i] < R[j]: + arr[k] = L[i] i+=1 - else: - arr[k] = R[j] + else: + arr[k] = R[j] j+=1 k+=1 - - # Checking if any element was left - while i < len(L): - arr[k] = L[i] + + # Checking if any element was left + while i < len(L): + arr[k] = L[i] i+=1 k+=1 - - while j < len(R): - arr[k] = R[j] + + while j < len(R): + arr[k] = R[j] j+=1 k+=1 test_array = [10,30,20,100,40,80,90,210,34] -mergeSort(test_array) +merge_sort(test_array) print(test_array) diff --git a/algorithms/sorting/select_sort.py b/algorithms/sorting/select_sort.py index 51322430..8ba2cad7 100644 --- a/algorithms/sorting/select_sort.py +++ b/algorithms/sorting/select_sort.py @@ -1,5 +1,5 @@ -def findSmallest(arr): +def find_smallest(arr): smallest = arr[0] smallest_index = 0 @@ -12,15 +12,15 @@ def findSmallest(arr): -def selectionSort(arr): - newArr = [] +def selection_sort(arr): + new_arr = [] for i in range(len(arr)): - smallest = findSmallest(arr) - newArr.append(arr.pop(smallest)) + smallest = find_smallest(arr) + new_arr.append(arr.pop(smallest)) - return newArr + return new_arr array = [100, 5, 72, 41, 80, 1, 99, 36, 27, 78] -print(selectionSort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] \ No newline at end of file +print(selection_sort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] diff --git a/bookmarks/articles.md b/bookmarks/articles.md index 16fc7323..ccdd64e2 100644 --- a/bookmarks/articles.md +++ b/bookmarks/articles.md @@ -51,3 +51,11 @@ This is a list of articles that may be useful for algorithms and data structures - https://martinfowler.com/articles/serverless.html - https://samnewman.io/patterns/architectural/bff/ + +- https://www.ultravioletsoftware.com/single-post/2017/03/23/An-introduction-into-the-WSGI-ecosystem + +- https://jwt.io/introduction/ + +- https://khashtamov.com/en/how-to-become-a-data-engineer/ + +- https://blog.mirrorfly.com/xmpp-vs-websockets-instant-messaging-protocol-comparison/ diff --git a/bookmarks/database.md b/bookmarks/database.md new file mode 100644 index 00000000..5a4e2fd3 --- /dev/null +++ b/bookmarks/database.md @@ -0,0 +1 @@ +https://stackoverflow.com/questions/23435361/does-mysql-update-the-index-on-all-inserts-can-i-make-it-update-after-every-x-i diff --git a/bookmarks/misc.md b/bookmarks/misc.md index de317d49..2afa94e6 100644 --- a/bookmarks/misc.md +++ b/bookmarks/misc.md @@ -13,3 +13,9 @@ This is a list of misc links that may be useful when learning or researching dat - https://wsvincent.com/ - https://machinelearningmastery.com + +- https://stackoverflow.com/questions/10631326/difference-between-select-into-and-insert-into-from-old-table + +- Why SSL are not issued for IP address - https://stackoverflow.com/a/33419662/6111200 + +- PATCH vs PUT https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios/39338329#39338329 diff --git a/bookmarks/topics.md b/bookmarks/topics.md index bf8c7be2..d1d54010 100644 --- a/bookmarks/topics.md +++ b/bookmarks/topics.md @@ -14,4 +14,6 @@ This is a list of links to topics that may be helpful in learning or researching - https://stackoverflow.com/questions/40200413/sessions-vs-token-based-authentication -- https://stackoverflow.com/questions/15678406/when-to-use-myisam-and-innodb \ No newline at end of file +- https://stackoverflow.com/questions/15678406/when-to-use-myisam-and-innodb + +- https://www.bigocheatsheet.com/ diff --git a/bookmarks/videos.md b/bookmarks/videos.md deleted file mode 100644 index 0ac8813e..00000000 --- a/bookmarks/videos.md +++ /dev/null @@ -1,9 +0,0 @@ -# Videos - -This is a list of video links that may be useful for algorithms and data structures learning. - -## Links: - -- https://www.youtube.com/watch?v=mSzUb7f47qk - -- https://www.youtube.com/user/mycodeschool \ No newline at end of file diff --git a/data_structures/__init__.py b/data_structures/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/data_structures/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/data_structures/array/binary_search_infinite_array.py b/data_structures/array/binary_search_infinite_array.py index 6eff9207..1db83e07 100644 --- a/data_structures/array/binary_search_infinite_array.py +++ b/data_structures/array/binary_search_infinite_array.py @@ -23,7 +23,7 @@ def search(arr, val): high = 1 while temp < val: - low = 0 + low = high high = 2 * high temp = arr[high] diff --git a/data_structures/array/duplicate.py b/data_structures/array/duplicate.py index ad4a3fa5..4838b1aa 100644 --- a/data_structures/array/duplicate.py +++ b/data_structures/array/duplicate.py @@ -1,6 +1,12 @@ # Find duplicate in an array of integers given that the integers are in random order and # not necessarily each integer i is 0 <= i <= N where N = length of array +# Solution - use tortoise and hare algorithm. The tortoise pointer moves slower while the hare pointer +# moves faster + +# Note: this array will always contain a duplicate number due to the pigeonhole principle. You are trying to fit +# N different numbers in an array of size N - 1 so one number will be repeated + def duplicate(arr): tortoise = arr[0] hare = arr[0] @@ -11,10 +17,14 @@ def duplicate(arr): if tortoise == hare: break - ptr1 = nums[0] - ptr2 = tortoise - while ptr1 != ptr2: - ptr1 = nums[ptr1] - ptr2 = nums[ptr2] + tortoise = arr[0] + + while tortoise != hare: + tortoise = arr[tortoise] + hare = arr[hare] + + return hare + - return ptr1 +arr = [3,5,1,2,4,5] +print(duplicate(arr)) \ No newline at end of file diff --git a/data_structures/array/duplicates.py b/data_structures/array/duplicates.py index 04940c0e..a146efba 100644 --- a/data_structures/array/duplicates.py +++ b/data_structures/array/duplicates.py @@ -4,5 +4,5 @@ def duplicate(arr): if arr[abs(x) - 1] < 0: res.append(abs(x)) else: - nums[abs(x) - 1] *= -1 + arr[abs(x) - 1] *= -1 return res diff --git a/data_structures/array/equilibrium_index.py b/data_structures/array/equilibrium_index.py new file mode 100644 index 00000000..2b1d0601 --- /dev/null +++ b/data_structures/array/equilibrium_index.py @@ -0,0 +1,23 @@ +# Find the equilibrium index of an array. An equilibrium index is such that +# the sum of elements to the left of it is equal to sum of elements to the right +# of it + +def find_equi(arr): + total_sum = sum(arr) + + left_sum = 0 + + for i, num in enumerate(arr): + + total_sum -= num + + if left_sum == total_sum: + return i + + left_sum += num + + return -1 + + +arr = [-7, 1, 5, 2, -4, 3, 0] +print(find_equi(arr)) diff --git a/data_structures/array/even_more_than_odd.py b/data_structures/array/even_more_than_odd.py new file mode 100644 index 00000000..62735d9c --- /dev/null +++ b/data_structures/array/even_more_than_odd.py @@ -0,0 +1,16 @@ +# Rearrange an array such that numbers at even indexes are greater than numbers +# at odd indexes + +def rearrange(arr): + for i in range(1, len(arr)): + if i % 2 == 0: + if arr[i] > arr[i-1]: + arr[i-1], arr[i] = arr[i], arr[i-1] + else: + if arr[i] < arr[i-1]: + arr[i-1], arr[i] = arr[i], arr[i-1] + print(arr) + + +arr = [ 1, 3, 2, 2, 5 ] +rearrange(arr) \ No newline at end of file diff --git a/data_structures/array/find_common_in_three_arrays.py b/data_structures/array/find_common_in_three_arrays.py new file mode 100644 index 00000000..7c254ef8 --- /dev/null +++ b/data_structures/array/find_common_in_three_arrays.py @@ -0,0 +1,26 @@ +# Find the common elements in three arrays + +def find_common(arr1, arr2, arr3): + i, j, k = 0, 0, 0 + + while i < len(arr1) and j < len(arr2) and k < len(arr3): + if arr1[i] == arr2[j] == arr3[k]: + print(arr1[i]) + i += 1 + j += 1 + k += 1 + + elif arr1[i] < arr2[j]: + i += 1 + + elif arr2[j] < arr3[k]: + j += 1 + + else: + k += 1 + +arr1 = [1,2,3,4,5,6] +arr2 = [5,6,7,8,9] +arr3 = [4,5,6,9,10] + +find_common(arr1, arr2, arr3) diff --git a/data_structures/array/find_given_sum_in_array.py b/data_structures/array/find_given_sum_in_array.py new file mode 100644 index 00000000..e3adcfcb --- /dev/null +++ b/data_structures/array/find_given_sum_in_array.py @@ -0,0 +1,27 @@ +# Find if a given sum exists in an array +# Reference - https://www.geeksforgeeks.org/find-subarray-with-given-sum/ + +def find_sum(arr, s): + curr_sum = arr[0] + start = 0 + n = len(arr) - 1 + + i = 1 + + while i <= n: + + while curr_sum > s and start < i: + curr_sum = curr_sum - arr[start] + start += 1 + + if curr_sum == s: + return "Found between {} and {}".format(start, i - 1) + + curr_sum = curr_sum + arr[i] + i += 1 + + return "Sum not found" + +arr = [15, 2, 4, 8, 9, 5, 10, 23] + +print(find_sum(arr, 6)) \ No newline at end of file diff --git a/data_structures/array/find_odd_number.py b/data_structures/array/find_odd_number.py new file mode 100644 index 00000000..ea97a2a4 --- /dev/null +++ b/data_structures/array/find_odd_number.py @@ -0,0 +1,12 @@ +def find_odd_number(nums): + """ Find one number in array which is not duplicated, + or exsits odd times. + """ + s = 0 + for n in nums: + s ^= n + return s + + +a = [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10] +print(find_odd_number(a)) diff --git a/data_structures/array/first_repeating_char.py b/data_structures/array/first_repeating_char.py index 2ba272eb..89477411 100644 --- a/data_structures/array/first_repeating_char.py +++ b/data_structures/array/first_repeating_char.py @@ -1,4 +1,4 @@ -# Find the first character in a string without using extra space +# Find the first repeated character in a string without using extra space # With extra space its simple. Just check for the element in a hash map # If present, then it is the recurrent char diff --git a/data_structures/array/index.md b/data_structures/array/index.md index 8b964508..c549554c 100644 --- a/data_structures/array/index.md +++ b/data_structures/array/index.md @@ -1,34 +1,122 @@ # Index of array * [Permumations of Word](permutations_of_word.py) + +Find Permuatation of a given word. * [Common Elements of three sorted arrays](sorted_array_three_common_elements.py) + +Find Common elements in three array given that all three arrays are sorted * [Dutch Flag Problem](dutch_flag_problem.py) + + Given an array containing only 0s, 1s and 2s in a random order, arrange the array such that all 0s come + before 1s which in turn come before all 2s + +Input - [1,2,0,1,2,1,0,1,2,1,0] + +Output - [0,0,0,1,1,1,1,1,2,2,2] * [Partition an array into three parts with equal sum](partition_three_parts_equal_sum.py) -* [Binary search on an Infinite array](binary_search_infinite_array.py) + +Divide an array into three parts and sum all three parst must be same. +* [Binary search on a finite array](binary_search_infinite_array.py) + +Performing binary search on a finite array + * [Largest Element](largest_element.py) -* [Right Place](right_place.py) + +Find out the largest element in an array + * [Rotation](rotation.py) + +Rotate an array for given number such that the number goes to the end of array + +input=[1,2,3,4,5] rotate around + +output= [4,5,1,2,3] * [Find missing number](find_missing_numbers.py) + +find missing number between smallest and largest number in array which are not present in array + +input=[1,1,1,4] +output=[2,3] + * [Three Largest Elements](three_largest_elements.py) + +Find three largest numbers from an array * [Triplet Sum](triplet_sum.py) -* [Max Product](max_product.py) + +Find three elements of an array whose sum is eqaul to a given value + * [Moves to Zero](moves_zeros_to_end.py) + +Given an array move all zeros in arrays to the end. * [Pivot Index](pivot_index.py) + +Find the index of element whose right element when added give the same value. * [Intersection of Two Sorted Arrays](intersection_sorted_array.py) + + find out the common elements from two arrays * [Max Triplet Sum](max_triplet_sum.py) -* [Number of 1's in Sorted Array](number_of_1_in_sorted_array.py) + +Find out maximium sum of any three elements for a given array + * [Square of Sorted Array](square_of_sorted_array.py) -* [Max Consecutive 1's](max_consecutive_ones.py) -* [Max Product Three Elements](max_product_three_elements.py) -* [Kadane Algorithm](kadane_algorithm.py) + +For a given array return an array that contains square of elements. + * [All Numbers Divisible](all_numbers_divisible.py) -* [Duplicates](duplicates.py) + + Find a number in the array such that all the elements in the array are divisible by it + + + * [Majority Element](majority_element.py) + +Find an element in an array which has most occurences * [Find Sum](find_sum.py) + +Find if sum of any elements in array is eqaul to a given number * [Quick Sort](quick_sort.py) -* [Peak Element](peak_element.py) + +### Implement Quick Sort algorithm + +Quicksort Running Time: +Quick sort average case is O(n log n) + each level takes O(n) but splitting the data is O(log n) + O(n) * O(log n) = O(n log n) +Worse case is O(log n2) + if pivot is smallest value, each level is O(n) and splitting the data is O(n) + O(n) * O(n) = O(n2) + * [Union Sorted Array](union_sorted_array.py) -* [Min Product](min_product.py) + +Perform Union operations on two arrays + * [Sort By Parity](sort_by_parity.py) + +Given an array A of non-negative integers, return an array consisting of all the even elements of A, followed by all the odd elements of A. * [Duplicate](duplicate.py) -* [Product of Array Except Self](product_of_array_except_self.py) \ No newline at end of file + + Find duplicate in an array of integers given that the integers are in random order and +not necessarily each integer i is 0 <= i <= N where N = length of array +* [Product of Array Except Self](product_of_array_except_self.py) + +find the product of all elements in an array + +* [Right Place](right_place.py) + +Placing elements at right position in an array +* [Max Product Three Elements](max_product_three_elements.py) + +Find out highest sum of three numbers in an array +* [Max Consecutive 1's](max_consecutive_ones.py) + +Counts max number of one's in an array + +* [Kadane Algorithm](kadane_algorithm.py) + +Implementation of Kadane Algorithm +* [Max Product](max_product.py) +* [Number of 1's in Sorted Array](number_of_1_in_sorted_array.py) +* [Duplicates](duplicates.py) +* [Peak Element](peak_element.py) +* [Min Product](min_product.py) \ No newline at end of file diff --git a/data_structures/array/kadane_algorithm.py b/data_structures/array/kadane_algorithm.py index f2fee2d3..19824af5 100644 --- a/data_structures/array/kadane_algorithm.py +++ b/data_structures/array/kadane_algorithm.py @@ -1,3 +1,12 @@ +""" +Kadane's algorithm is used to find the maximum contiguous sum in an array. +The logic is simple. Take the first element in the sum and then find current max num. +Curr max = max(arr[i], curr_max + arr[i]) - we add this number if it increases the sum, +otherwise we take the number if it is more than the sum + +Then keep track of max of this value +""" + def max_sum(arr): max_so_far = arr[0] curr_max = arr[0] diff --git a/data_structures/array/majority_element.py b/data_structures/array/majority_element.py index 511902dd..457686ad 100644 --- a/data_structures/array/majority_element.py +++ b/data_structures/array/majority_element.py @@ -1,3 +1,7 @@ +# The only prerequisite condition of this algorithm is that the array +# definitely contains a majority element, otherwise it will just return +# the last element + def majority(arr): maj_index = 0 count = 1 @@ -11,3 +15,8 @@ def majority(arr): count = 1 return arr[maj_index] + + +arr = [3, 3, 1,5,6,8,3,0,7] + +print(majority(arr)) \ No newline at end of file diff --git a/data_structures/array/matrix_layer_rotation.py b/data_structures/array/matrix_layer_rotation.py deleted file mode 100644 index ef5d36c7..00000000 --- a/data_structures/array/matrix_layer_rotation.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Description: - -Matrix Layer Rotation - -You are given a 2D matrix of dimension mxn and a positive integer r. You have to rotate the -matrix r times and print the resultant matrix. Rotation should be in anti-clockwise direction. -Rotation of a 4x5 matrix is represented by the following figure. Note that in one rotation, -you have to shift elements by one step only. This is shifting the elemment. -Note:- Minimum of m and n will be even. - -Start First Second - 1 2 3 4 2 3 4 5 3 4 5 6 -12 1 2 5 -> 1 2 3 6 -> 2 3 4 7 -11 4 3 6 12 1 4 7 1 2 1 8 -10 9 8 7 11 10 9 8 12 11 10 9 -""" - - -# Function to rotate the sides of the matrix in place. -def matrix_rotate(matrix, m, n, i=0, j=0): - if (m, n) == (1, 1): - return matrix - if (m, n) == (2, 2): - temp = matrix[i][j] - matrix[i][j] = matrix[i + 1][j] - matrix[i + 1][j] = matrix[i + 1][j + 1] - matrix[i + 1][j + 1] = matrix[i + 1][j] - matrix[i + 1][j] = temp - return matrix - else: - temp = matrix[i][j] - # top - for a in range(j, n - j - 1): - matrix[i][a] = matrix[i][a + 1] - matrix[i][n - j - 1] = matrix[i + 1][n - j - 1] - # right - for a in range(i + 1, m - i - 2): - matrix[a][n - 1 - j] = matrix[a + 1][n - 1 - j] - matrix[m - j - 2][n - j - 1] = matrix[m - j - 1][n - j - 1] - # bottom - for a in range(n - j - 1, j, -1): - matrix[m - j - 1][a] = matrix[m - j - 1][a - 1] - matrix[m - j - 1][j] = matrix[m - j - 2][j] - # left - for a in range(m - i - 2, i, -1): - matrix[a][i] = matrix[a - 1][i] - matrix[i + 1][j] = temp - return matrix - - -def driver_code(matrix, r): - m = len(matrix) - n = len(matrix[0]) - i, j = 0, 0 - s = 0 - while (i, j) < (m // 2, n // 2): # Checking for the maximum values of i, j. - for _ in range(r): - s = matrix_rotate(matrix, m, n, i, j) - i += 1 - j += 1 - print_matrix(s) - - -def print_matrix(matrix): - m = len(matrix) - n = len(matrix[0]) - print('\n'.join([' '.join(['{:n}'.format(item) for item in row]) - for row in matrix])) - - -if __name__ == '__main__': - mnr = input().rstrip().split() - - m = int(mnr[0]) - - n = int(mnr[1]) - - r = int(mnr[2]) - - matrix2d = [] - - for _ in range(m): - matrix2d.append(list(map(int, input().rstrip().split()))) - - driver_code(matrix2d, r) - - diff --git a/data_structures/array/max_product.py b/data_structures/array/max_product.py index e69de29b..81ee1f70 100644 --- a/data_structures/array/max_product.py +++ b/data_structures/array/max_product.py @@ -0,0 +1,16 @@ +def max_product(arr): + n = len(arr) + if n == 0: + return 0 + if n == 1: + return arr[0] + a = arr[0] + b = arr[1] + maxprod = a * b + for i in range(n): + for j in range(i + 1, n): + if (arr[i] * arr[j]) > maxprod: + a = arr[i] + b = arr[j] + maxprod = a * b + return maxprod diff --git a/data_structures/array/max_product_three_elements.py b/data_structures/array/max_product_three_elements.py index 0d31fd53..5883172e 100644 --- a/data_structures/array/max_product_three_elements.py +++ b/data_structures/array/max_product_three_elements.py @@ -1,3 +1,6 @@ +# Q - Find the max product of three elements of an array +# A - Find the max 3 numbers and 2 min numbers. Then find the max of (min1*min2*max1, max1*max2*max3) + import sys def product(arr): diff --git a/data_structures/array/min_product.py b/data_structures/array/min_product.py index bdf7cbf3..01bbfcb9 100644 --- a/data_structures/array/min_product.py +++ b/data_structures/array/min_product.py @@ -7,45 +7,44 @@ # If there are odd number of negative numbers and no zeros, # the result is simply the product of all -# If there is a zero, then the result is zero +# If there is a zero and all other are positive, then the result is zero # If there are only positive numbers, the the result is # the smallest positive number + def find(arr): if len(arr) == 1: return arr[0] - + count_negative = 0 - count_positive = 0 count_zero = 0 max_neg = float('-inf') min_pos = float('inf') - + prod = 1 - + for num in arr: if num == 0: count_zero += 1 continue if num < 0: - count_neg += 1 + count_negative += 1 max_neg = max(max_neg, num) if num > 0: min_pos = min(min_pos, num) - prod = prod * num + prod *= num - if count_zero == len(arr) or (count_neg == 0 and count_zero > 0): + if count_zero == len(arr) or (count_negative == 0 and count_zero > 0): return 0 - - if count_neg == 0: + + if count_negative == 0: return min_pos - if count_neg & 1 == 0 and count_neg != 0: + if count_negative & 1 == 0 and count_negative != 0: prod = int(prod / max_neg) - - return prod + return prod diff --git a/data_structures/array/min_swaps.py b/data_structures/array/min_swaps.py new file mode 100644 index 00000000..3fdd13a7 --- /dev/null +++ b/data_structures/array/min_swaps.py @@ -0,0 +1,39 @@ +# Minimum swaps required to bring all elements less than or equal to k together + +def min_swaps(arr, k): + # First find out how many elements are there which are less than or + # equal to k + count = 0 + for i in arr: + if i <= k: + count += 1 + + # This count defines a window - inside this window all our elements should + # be placed + # Find the count of bad elements - elements which are more than k and that will be + # our starting answer as we will have to swap them out + bad = 0 + for i in range(0, count): + if arr[i] > k: + bad += 1 + + ans = bad + j = count + + for i in range(0, len(arr)): + if j == len(arr): + break + + if arr[i] > k: + bad -= 1 # because we have moved the bad element out of the window + + if arr[j] > k: + bad += 1 + + ans = min(bad, ans) + j += 1 + + print('answer - ', ans) + +arr = [2,7,9,5,8,7,4] +min_swaps(arr, 5) \ No newline at end of file diff --git a/data_structures/array/moves_zeros_to_end.py b/data_structures/array/moves_zeros_to_end.py index 5c9509bb..c5589961 100644 --- a/data_structures/array/moves_zeros_to_end.py +++ b/data_structures/array/moves_zeros_to_end.py @@ -1,3 +1,6 @@ +# Move all zeros in an array to the end + + def move(arr): count = 0 for a in arr: @@ -5,5 +8,5 @@ def move(arr): arr[count] = a count += 1 while count < len(nums): - nums[count] = 0 + arr[count] = 0 count += 1 diff --git a/data_structures/array/number_of_1_in_sorted_array.py b/data_structures/array/number_of_1_in_sorted_array.py index 79689872..bfcfda50 100644 --- a/data_structures/array/number_of_1_in_sorted_array.py +++ b/data_structures/array/number_of_1_in_sorted_array.py @@ -1,4 +1,8 @@ -# The array is sorted in decreasing order +""" +Count the number of 1s in a sorted array +Instead of linearly searching the array to find the first occurence, +do a binary search to find the first 0 +""" def count(arr): start = 0 diff --git a/data_structures/array/number_of_elements_that_can_searched_using_binary_search.py b/data_structures/array/number_of_elements_that_can_searched_using_binary_search.py new file mode 100644 index 00000000..9137db3c --- /dev/null +++ b/data_structures/array/number_of_elements_that_can_searched_using_binary_search.py @@ -0,0 +1,52 @@ +""" +In an input of unsorted integer array, find the number of elements +that can be searched using binary search + +The idea is the an element is binary searchable if the elements to the +left of it are smaller than it and the elements to the right of it +are bigger than it + +So maintain two arrays - left_max and right_min such that in i'th index - + +* left_max[i] contains the max element between 0 and i-1 (left to right movement) +* right_min[i] contains the min element between i+1 and n-1 (right to left movement) + +Now for every element in the array, if its index its i, then it is binary searchable +if left_max[i] < arr[i] < right_min[i] +""" +import sys + +def get_searchable_numbers(arr, n): + left_max = [None] * n + right_min = [None] * n + + left_max[0] = float('-inf') + right_min[n-1] = float('inf') + + for i in range(1, n): + left_max[i] = max(left_max[i-1], arr[i-1]) + + for i in range(len(arr) - 2, -1, -1): + right_min[i] = min(right_min[i+1], arr[i+1]) + + res = [] + count = 0 + + for i in range(0, n): + num = arr[i] + left = left_max[i] + right = right_min[i] + + if left < num < right: + res.append(num) + count += 1 + + return count, res + + +if __name__ == '__main__': + #arr = [5,1,4,3,6,8,10,7,9] + arr = [4,1,3,9,8,10,11] + count, res = get_searchable_numbers(arr, len(arr)) + + print(count, res) diff --git a/data_structures/array/peak_element.py b/data_structures/array/peak_element.py index e69de29b..ded661b0 100644 --- a/data_structures/array/peak_element.py +++ b/data_structures/array/peak_element.py @@ -0,0 +1,21 @@ +# A peak element is an element such that both of its neighbours are smaller than it +# In case of corner elements, consider only one neighbour + + +def peak(arr, low, high): + n = len(arr) + + while low <= high: + mid = (high - low) // 2 + + if (mid == 0 or arr[mid-1] <= arr[mid]) and (mid == n-1 or arr[mid+1] <= arr[mid]): + return(arr[mid]) + + elif mid > 0 and arr[mid-1] > arr[mid]: + high = mid - 1 + + else: + low = mid + 1 + +arr = [1, 3, 20, 4, 1, 0] +print(peak(arr, 0, len(arr) - 1)) \ No newline at end of file diff --git a/data_structures/array/permutations_of_word.py b/data_structures/array/permutations_of_word.py index c0085b54..e5eb8998 100644 --- a/data_structures/array/permutations_of_word.py +++ b/data_structures/array/permutations_of_word.py @@ -6,7 +6,7 @@ def permutation(lst): l = [] for i in range(len(lst)): m = lst[i] - rem_lst = lst[:i] + lst[i+i:] + rem_lst = lst[:i] + lst[i+1:] for p in permutation(rem_lst): l.append([m] + p) return l diff --git a/data_structures/array/pivot_index.py b/data_structures/array/pivot_index.py index dd2a9e21..d15ce1cd 100644 --- a/data_structures/array/pivot_index.py +++ b/data_structures/array/pivot_index.py @@ -2,9 +2,7 @@ def pivot(arr): s = sum(arr) left_sum = 0 for i, x in enumerate(arr): - if left_sum = (s - x - left_sum): + if left_sum == (s - x - left_sum): return i left_sum += x return -1 - - diff --git a/data_structures/array/random_matrix.py b/data_structures/array/random_matrix.py new file mode 100644 index 00000000..33017c51 --- /dev/null +++ b/data_structures/array/random_matrix.py @@ -0,0 +1,24 @@ +def random_matrix(n): + """ Generate random n x n matrix without repeated entries in rows or columns. + The entries are integers between 1 and n. + """ + from random import shuffle + + a = list(range(n + 1)) + shuffle(a) + + # Use slicing to left rotate + m = [a[i:] + a[:i] for i in range(n + 1)] + + # Shuffle rows in matrix + shuffle(m) + + # Shuffle cols in matrix (optional) + m = list(map(list, zip(*m))) # Transpose the matrix + shuffle(m) + + return m + + +m = random_matrix(9) +print('\n'.join(map(str, m))) diff --git a/data_structures/array/rearrange_positive_negative.py b/data_structures/array/rearrange_positive_negative.py new file mode 100644 index 00000000..ab1b971c --- /dev/null +++ b/data_structures/array/rearrange_positive_negative.py @@ -0,0 +1,23 @@ +# Rearragne positive and negative numbers in an array such that they appear +# alternately. If there are more numbers of any one kind, put them at the end + +def rearrange(arr): + i = -1 + + for j in range(len(arr)): + if arr[j] < 0: + i += 1 # maintaining index of the last negative number + arr[j], arr[i] = arr[i], arr[j] + + pos = i + 1 # index of first positive number + neg = 0 # index of first negative number + + while pos < len(arr) and neg < pos and arr[neg] < 0: + arr[pos], arr[neg] = arr[neg], arr[pos] + pos += 1 + neg += 2 + + print(arr) + +arr = [-1, 2, -3, 4, 5, 6, -7, 8, 9] +rearrange(arr) \ No newline at end of file diff --git a/data_structures/array/rotation.py b/data_structures/array/rotation.py index 36b9db0b..c89ab715 100644 --- a/data_structures/array/rotation.py +++ b/data_structures/array/rotation.py @@ -14,14 +14,14 @@ def rotate(arr, d): while 1: k = j + d if k >= n: - k = k -n + k -= n if k == i: break arr[j] = arr[k] j = k arr[j] = temp - + arr = [1,2,3,4,5] -rotate(arr, 1) +rotate(arr, 3) print(arr) diff --git a/data_structures/array/square_of_sorted_array.py b/data_structures/array/square_of_sorted_array.py index 88ef6c87..68f196b9 100644 --- a/data_structures/array/square_of_sorted_array.py +++ b/data_structures/array/square_of_sorted_array.py @@ -1,3 +1,8 @@ +""" +Find the square of all the numbers of a sorted array such that after finding the square of the sorted array, the +resultant array containing the squared numbers remains sorted +""" + def square(arr): n = len(arr) j = 0 @@ -26,8 +31,3 @@ def square(arr): j += 1 return ans - - - - - diff --git a/data_structures/array/transpose_matrix.py b/data_structures/array/transpose_matrix.py new file mode 100644 index 00000000..c7abb7f4 --- /dev/null +++ b/data_structures/array/transpose_matrix.py @@ -0,0 +1,14 @@ +class Solution: + def transpose(self, A: List[List[int]]) -> List[List[int]]: + l=[] + i=0 + while(i!=len(A[0])): + x=[] + j=0 + while(jleft->left = root->right +# 2. root->left->right = root +# 3. root->left = NULL +# 4. root->right = NULL + + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def flip_tree(root): + if root is None: + return root + + if root.left is None and root.right is None: + return root + + flipped_root = flip_tree(root.left) + + root.left.left = root.right + root.left.right = root + root.left = None + root.right = None + + return flipped_root diff --git a/data_structures/binary_trees/foldable_or_symmetric_tree.py b/data_structures/binary_trees/foldable_or_symmetric_tree.py new file mode 100644 index 00000000..0943d62c --- /dev/null +++ b/data_structures/binary_trees/foldable_or_symmetric_tree.py @@ -0,0 +1,31 @@ +# A foldable tree is one such that the mirror of left subtree is equal +# to the right subtree + +class Node: + + def __init__(self, val): + self.val = val + self.right = None + self.left = None + + +def foldable(left, right): + if left is None and right is None: + return True + + if left is None or right is None: + return False + + return left.val == right.val and foldable(left.left, right.right) and foldable(left.right, right.left) + + +root = Node(4) + +root.left = Node(2) +root.left.left = Node(1) + +root.right = Node(2) +root.right.right = Node(10) + +print(foldable(root.left, root.right)) + diff --git a/data_structures/binary_trees/height_of_tree.py b/data_structures/binary_trees/height_of_tree.py new file mode 100644 index 00000000..cd082f1f --- /dev/null +++ b/data_structures/binary_trees/height_of_tree.py @@ -0,0 +1,35 @@ +""" +Find the height of a binary tree + +Height - (max of left height and right height) + 1 +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def height(root): + if not root: + return 0 + + else: + lheight = height(root.left) + rheight = height(root.right) + + return 1 + max(lheight, rheight) + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(7) +root.left.right = Node(6) +root.right.left = Node(5) +root.right.right = Node(4) +root.right.right.right = Node(40) + +print(height(root)) \ No newline at end of file diff --git a/data_structures/binary_trees/identical_trees.py b/data_structures/binary_trees/identical_trees.py new file mode 100644 index 00000000..bacc0614 --- /dev/null +++ b/data_structures/binary_trees/identical_trees.py @@ -0,0 +1,18 @@ +# Check if two trees are identical or not + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def identical(root1, root2): + if root1 is None and root2 is None: + return True + + if root1 is not None and root2 is not None: + return root1.val == root2.val and identical(root1.left, root2.left) and identical(root1.right, root2.right) + + return False diff --git a/data_structures/binary_trees/index.md b/data_structures/binary_trees/index.md deleted file mode 100644 index 4f749eae..00000000 --- a/data_structures/binary_trees/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index of binary_trees - -* [Symmetric Binary Tree](symmetric_binary_tree.py) diff --git a/data_structures/binary_trees/largest_subtree_sum.py b/data_structures/binary_trees/largest_subtree_sum.py new file mode 100644 index 00000000..d79002f2 --- /dev/null +++ b/data_structures/binary_trees/largest_subtree_sum.py @@ -0,0 +1,31 @@ +# Find the largest subtree sum in a binary tree +# Do a postorder traversal + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def sum_util(root, ans): + if root is None: + return 0 + + s = root.val + sum_util(root.left, ans) + sum_util(root.right, ans) + + ans[0] = max(ans[0], s) + + return s + + +def find_sum(root): + if root is None: + return 0 + + ans = [-99999999] + + sum_util(root, ans) + + return ans[0] diff --git a/data_structures/binary_trees/left_right_to_down_right.py b/data_structures/binary_trees/left_right_to_down_right.py new file mode 100644 index 00000000..c043bcaa --- /dev/null +++ b/data_structures/binary_trees/left_right_to_down_right.py @@ -0,0 +1,24 @@ +# Convert a tree that is a left right representation to a +# down right representation + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.rigt = None + + +def convert(root): + if root is None: + return + + convert(root.left) + convert(root.right) + + if root.left == None: + root.left = root.right + else: + root.left.right = root.right + + root.right = None diff --git a/data_structures/binary_trees/left_view.py b/data_structures/binary_trees/left_view.py new file mode 100644 index 00000000..63cdbf9d --- /dev/null +++ b/data_structures/binary_trees/left_view.py @@ -0,0 +1,38 @@ +""" +Print left view of a binary tree +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def left_view_util(root, max_level, level): + if not root: + return + + if max_level[0] < level: + print(root.val) + max_level[0] = level + + left_view_util(root.left, max_level, level+1) + left_view_util(root.right, max_level, level+1) + + +def left_view(root): + max_level = [0] + left_view_util(root, max_level, 1) + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(4) +root.left.left.left = Node(5) +root.left.left.left.left = Node(6) +root.right.right = Node(7) +root.right.right.right = Node(8) +left_view(root) \ No newline at end of file diff --git a/data_structures/binary_trees/logical_and_tree.py b/data_structures/binary_trees/logical_and_tree.py new file mode 100644 index 00000000..1dddf0b0 --- /dev/null +++ b/data_structures/binary_trees/logical_and_tree.py @@ -0,0 +1,21 @@ +# Given a tree that contains only 0s and 1s, convert it into +# a logical AND tree. A logical AND tree is such that +# left_child AND right_child = AND_VALUE + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def convert(root): + if root is None: + return + + convert(root.left) + convert(root.right) + + if root.left and root.right: + root.val = root.left.val & root.right.val diff --git a/data_structures/binary_trees/longest_path_with_same_value.py b/data_structures/binary_trees/longest_path_with_same_value.py new file mode 100644 index 00000000..6c285fd7 --- /dev/null +++ b/data_structures/binary_trees/longest_path_with_same_value.py @@ -0,0 +1,37 @@ +""" +Find the longest path in the tree with the same value +""" + +class Node: + + def __init__(self, val): + self.val = val + self.right = None + self.left = None + + +def length(root, ans): + if not root: + return 0 + + left = length(root.left, ans) + right = length(root.right, ans) + + left_max = 0 + right_max = 0 + + if root.left and root.left.val == root.val: + left_max += 1 + + if root.right and root.right.val == root.val: + right_max += 1 + + ans[0] = max(ans[0], left_max + right_max) + + return max(left_max, right_max) + + +def longest_path(root): + ans = [0] + length(root, ans) + return ans[0] diff --git a/data_structures/binary_trees/maximum-depth-of-binary-tree.py b/data_structures/binary_trees/maximum-depth-of-binary-tree.py deleted file mode 100644 index cb65fb92..00000000 --- a/data_structures/binary_trees/maximum-depth-of-binary-tree.py +++ /dev/null @@ -1,53 +0,0 @@ -''' -Author : MiKueen -Level : Easy -Problem Statement : Maximum Depth of Binary Tree - -Given a binary tree, find its maximum depth. -The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. -Note: A leaf is a node with no children. - -Example: -Given binary tree [3,9,20,null,null,15,7], - - 3 - / \ - 9 20 - / \ - 15 7 -return its depth = 3. -''' - -from collections import deque - -# Definition for a binary tree node. -# class TreeNode(object): -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - -class Solution(object): - def maxDepth(self, root): - """ - :type root: TreeNode - :rtype: int - """ - - # BFS solution - if not root: - return 0 - if root.left is None and root.right is None: - return 1 - - depth, level = 0, deque([root]) - while level: - depth += 1 - for i in range(len(level)): - node = level.popleft() - if node.left: - level.append(node.left) - if node.right: - level.append(node.right) - return depth - \ No newline at end of file diff --git a/data_structures/binary_trees/maximum_left_node.py b/data_structures/binary_trees/maximum_left_node.py new file mode 100644 index 00000000..8e3f23b2 --- /dev/null +++ b/data_structures/binary_trees/maximum_left_node.py @@ -0,0 +1,22 @@ +# Find the max value in only the left nodes of the tree + +# Do inorder traversal and just keep track of the values + +class Node: + + def __init__(self, val): + self.val = val + self.right = None + self.left = None + + +def find(root): + res = -999999999999 + + if not root: + return res + + if root.left != None: + res = root.left.val + + return max(find(root.left), res, find(root.right)) diff --git a/data_structures/binary_trees/minimum-depth-of-binary-tree.py b/data_structures/binary_trees/minimum-depth-of-binary-tree.py deleted file mode 100644 index 2dbbf5df..00000000 --- a/data_structures/binary_trees/minimum-depth-of-binary-tree.py +++ /dev/null @@ -1,55 +0,0 @@ -''' -Author : MiKueen -Level : Easy -Problem Statement : Minimum Depth of Binary Tree - -Given a binary tree, find its minimum depth. -The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. -Note: A leaf is a node with no children. - -Example: -Given binary tree [3,9,20,null,null,15,7], - - 3 - / \ - 9 20 - / \ - 15 7 -return its minimum depth = 2. -''' - -from collections import deque - -# Definition for a binary tree node. -# class TreeNode(object): -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - -class Solution(object): - def minDepth(self, root): - """ - :type root: TreeNode - :rtype: int - """ - - # BFS solution - if not root: - return 0 - if root.left is None and root.right is None: - return 1 - - depth, level = 1, deque([root]) - while level: - for i in range(len(level)): - node = level.popleft() - if node.left is None and node.right is None: - return depth - if node.left: - level.append(node.left) - if node.right: - level.append(node.right) - depth += 1 - return depth - \ No newline at end of file diff --git a/data_structures/binary_trees/path-sum.py b/data_structures/binary_trees/path-sum.py deleted file mode 100644 index 69bc80be..00000000 --- a/data_structures/binary_trees/path-sum.py +++ /dev/null @@ -1,64 +0,0 @@ -''' -Author : MiKueen -Level : Medium -Problem Statement : Path Sum II - -Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. -Note: A leaf is a node with no children. - -Example: -Given the below binary tree and sum = 22, - 5 - / \ - 4 8 - / / \ - 11 13 4 - / \ / \ -7 2 5 1 -Return: -[ - [5,4,11,2], - [5,8,4,5] -] -''' - -# Definition for a binary tree node. -# class TreeNode(object): -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - -class Solution(object): - def pathSum(self, root, sum): - """ - :type root: TreeNode - :type sum: int - :rtype: List[List[int]] - """ - - # DFS solution - if not root: - return [] - if root.left is None and root.right is None: - if sum == root.val: - return [[root.val]] - else: - return [] - - res = [] - left, right = [], [] - sum -= root.val - - if root.left: - left = self.pathSum(root.left, sum) - if root.right: - right = self.pathSum(root.right, sum) - - for l in left: - res.append([root.val] + l) - for r in right: - res.append([root.val] + r) - - return res - \ No newline at end of file diff --git a/data_structures/binary_trees/perfect_tree.py b/data_structures/binary_trees/perfect_tree.py new file mode 100644 index 00000000..68c1dfe0 --- /dev/null +++ b/data_structures/binary_trees/perfect_tree.py @@ -0,0 +1,47 @@ +""" +Check if a given binary tree is perfect or not +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def find_depth(node): + d = 0 + while node: + d += 1 + node = node.left + return d + + +def is_perfect_util(root, d, level=0): + if not root: + return True + + if not root.left and not root.right: + return d == level + 1 + + if not root.left or not root.right: + return False + + return is_perfect_util(root.left, d, level+1) and is_perfect_util(root.right, d, level+1) + + +def is_perfect(root): + depth = find_depth(root) + return is_perfect_util(root, depth) + + +root = Node(10) +root.left = Node(20) +root.right = Node(30) +root.left.left = Node(40) +root.left.right = Node(50) +root.right.left = Node(60) +root.right.right = Node(70) + +print(is_perfect(root)) \ No newline at end of file diff --git a/data_structures/binary_trees/print_all_root_to_leaf_paths.py b/data_structures/binary_trees/print_all_root_to_leaf_paths.py new file mode 100644 index 00000000..f3c0d2a6 --- /dev/null +++ b/data_structures/binary_trees/print_all_root_to_leaf_paths.py @@ -0,0 +1,37 @@ +# Traverse the tree in preorder fashion +# append value in stack +# if root.left and root.right is None, then print all stack values +# traverse left +# traverse right +# pop from stack + +class Node: + + def __init__(self, val): + self.val = val + self.right = None + self.left = None + + +def print_route(root, stack): + if root == None: + return + + stack.append(root.val) + if root.left == None and root.right == None: + for i in stack: + print(i, end=' ') + print() + + print_route(root.left, stack) + print_route(root.right, stack) + stack.pop() + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(4) +root.left.right = Node(5) + +print_route(root, []) diff --git a/data_structures/binary_trees/print_full_nodes.py b/data_structures/binary_trees/print_full_nodes.py new file mode 100644 index 00000000..e76c63c8 --- /dev/null +++ b/data_structures/binary_trees/print_full_nodes.py @@ -0,0 +1,30 @@ +# Given a tree, print all the full nodes of it +# A full node is such that both its children are present + +class Node: + + def __init__(self, val): + self.val = val + self.right = None + self.left = None + + +def print_full(root): + if not root: + return + if root.left and root.right: + print(root.val, end=' ') + print_full(root.left) + print_full(root.right) + + +root = Node(10) +root.left = Node(8) +root.right = Node(2) + +root.left.left = Node(3) +root.left.right = Node(5) + +root.right.left = Node(7) + +print_full(root) diff --git a/data_structures/binary_trees/print_nodes_at_k_distance_from_root.py b/data_structures/binary_trees/print_nodes_at_k_distance_from_root.py new file mode 100644 index 00000000..a07e0211 --- /dev/null +++ b/data_structures/binary_trees/print_nodes_at_k_distance_from_root.py @@ -0,0 +1,16 @@ +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def print_k(root, k): + if root is None: + return + if k == 0: + print(root.val) + else: + print_k(root.left, k-1) + print_k(root.right, k-1) diff --git a/data_structures/binary_trees/print_nodes_with_no_sibling.py b/data_structures/binary_trees/print_nodes_with_no_sibling.py new file mode 100644 index 00000000..5c7ffc2a --- /dev/null +++ b/data_structures/binary_trees/print_nodes_with_no_sibling.py @@ -0,0 +1,24 @@ +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def no_siblings(root): + if root is None: + return + + if root.left is not None and root.right is not None: + no_siblings(root.left) + no_siblings(root.right) + + elif root.right is not None: + print(root.right.val) + no_siblings(root.right) + + elif root.left is not None: + print(root.left.val) + no_siblings(root.left) + diff --git a/data_structures/binary_trees/print_odd_level_nodes.py b/data_structures/binary_trees/print_odd_level_nodes.py new file mode 100644 index 00000000..bd83c75c --- /dev/null +++ b/data_structures/binary_trees/print_odd_level_nodes.py @@ -0,0 +1,19 @@ +# Print all the nodes at odd levels of a binary tree + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def print_odd(root, is_odd=True): + if not root: + return + + if is_odd: + print(root.val, end=' ') + + print_odd(root, not is_odd) + print_odd(root, not is_odd) diff --git a/data_structures/binary_trees/print_path_to_a_node.py b/data_structures/binary_trees/print_path_to_a_node.py new file mode 100644 index 00000000..7202dbc6 --- /dev/null +++ b/data_structures/binary_trees/print_path_to_a_node.py @@ -0,0 +1,32 @@ +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def has_path(root, stack, x): + if not root: + return False + + stack.append(root.val) + + if root.val == x: + return True + + if has_path(root.left, stack, x) or has_path(root.right, stack, x): + return True + + stack.pop() + return False + + +def print_path(root, x): + arr = [] + + if has_path(root, arr, x): + for i in arr: + print(i, end=' ') + else: + print('Path not present') diff --git a/data_structures/binary_trees/print_spiral_tree_two_stacks.py b/data_structures/binary_trees/print_spiral_tree_two_stacks.py new file mode 100644 index 00000000..463823ee --- /dev/null +++ b/data_structures/binary_trees/print_spiral_tree_two_stacks.py @@ -0,0 +1,34 @@ +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def print_spiral(root): + + s1 = [] + s2 = [] + + s1.append(root) + + while not len(s1) == 0 or not len(s2) == 0: + + while not len(s1) == 0: + temp = s1.pop() + print(temp.data, end=' ') + + if temp.right: + s2.append(temp.right) + if temp.left: + s2.append(temp.left) + + while not len(s2) == 0: + temp = s2.pop() + print(temp.data, end=' ') + + if temp.left: + s1.append(temp.left) + if temp.right: + s1.append(temp.right) diff --git a/data_structures/binary_trees/right_view.py b/data_structures/binary_trees/right_view.py new file mode 100644 index 00000000..cc6aa796 --- /dev/null +++ b/data_structures/binary_trees/right_view.py @@ -0,0 +1,42 @@ +""" +Print right view of a binary tree + +The idea behind using max_level[0] is that - +1. Its value when changes will be reflected in every recursion call +2. We are visiting right side first. So this acts as a check that that level was done +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def right_view_util(root, max_level, level): + if not root: + return + + if max_level[0] < level: + print(root.val) + max_level[0] = level + + right_view_util(root.right, max_level, level+1) + right_view_util(root.left, max_level, level+1) + + +def right_view(root): + max_level = [0] + right_view_util(root, max_level, 1) + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(4) +root.left.right = Node(5) +root.right.left = Node(6) +root.right.right = Node(7) +root.right.left.right = Node(8) +right_view(root) \ No newline at end of file diff --git a/data_structures/binary_trees/spiral_tree.py b/data_structures/binary_trees/spiral_tree.py new file mode 100644 index 00000000..e8505384 --- /dev/null +++ b/data_structures/binary_trees/spiral_tree.py @@ -0,0 +1,56 @@ +""" +Do level order traversal of a tree in spiral form +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def height(root): + if not root: + return 0 + + lheight = height(root.left) + rheight = height(root.right) + + return 1 + max(lheight, rheight) + + +def print_spiral(root): + h = height(root) + + left_to_right = False + + for i in range(1, h+1): + print_level(root, i, left_to_right) + left_to_right = not left_to_right + + +def print_level(root, level, left_to_right): + if not root: + return + + if level == 1: + print(root.val, end=' ') + elif level > 1: + if left_to_right: + print_level(root.left, level-1, left_to_right) + print_level(root.right, level-1, left_to_right) + else: + print_level(root.right, level-1, left_to_right) + print_level(root.left, level-1, left_to_right) + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(7) +root.left.right = Node(6) +root.right.left = Node(5) +root.right.right = Node(4) + +print_spiral(root) \ No newline at end of file diff --git a/data_structures/binary_trees/sum_of_all_left_leaves.py b/data_structures/binary_trees/sum_of_all_left_leaves.py new file mode 100644 index 00000000..4ef261f8 --- /dev/null +++ b/data_structures/binary_trees/sum_of_all_left_leaves.py @@ -0,0 +1,47 @@ +# Find the sum of all left leaves of a binary tree + + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def is_leaf(root): + if root is None: + return False + if root.left is None and root.right is None: + return True + return False + + +def sum_left(root): + s = 0 + stack = [] + + while True: + if root: + stack.append(root) + root = root.left + else: + if not stack: + break + root = stack.pop() + if is_leaf(root.left): + s += root.left.val + root = root.right + + return s + + +root = Node(9) +root.left = Node(8) +root.right = Node(6) +root.right.left = Node(1) + +root.left.left = Node(5) +root.left.right = Node(2) + +print(sum_left(root)) diff --git a/data_structures/binary_trees/sum_of_all_nodes.py b/data_structures/binary_trees/sum_of_all_nodes.py new file mode 100644 index 00000000..e5fe0dd1 --- /dev/null +++ b/data_structures/binary_trees/sum_of_all_nodes.py @@ -0,0 +1,15 @@ +# Find the sum of all nodes of a binary tree + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def sum_nodes(root): + if root is None: + return 0 + + return root.val + sum_nodes(root.left) + sum_nodes(root.right) diff --git a/data_structures/binary_trees/sum_of_perfect_binary_tree.py b/data_structures/binary_trees/sum_of_perfect_binary_tree.py new file mode 100644 index 00000000..f438015f --- /dev/null +++ b/data_structures/binary_trees/sum_of_perfect_binary_tree.py @@ -0,0 +1,12 @@ +# https://www.geeksforgeeks.org/find-sum-nodes-given-perfect-binary-tree/ + +import math + +def sum_nodes(l): + leaf_nodes = math.pow(2, l-1) + + s = (leaf_nodes + (leaf_nodes + 1)) / 2 + + res = s * l + + return int(res) diff --git a/data_structures/binary_trees/sum_predecessor_successor.py b/data_structures/binary_trees/sum_predecessor_successor.py new file mode 100644 index 00000000..be333a01 --- /dev/null +++ b/data_structures/binary_trees/sum_predecessor_successor.py @@ -0,0 +1,73 @@ +# Convert a binary tree such that each node is the sum of +# its inorder predecessor and successor + +# Store the inorder traversal in an array. Now while traversing +# replace each node with the values arr[i-1] + arr[i+1] as these +# are the inorder predecessor and successor + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +def inorder(root): + if not root: + return None + + arr = [] + stack = [] + + while True: + if root: + stack.append(root) + root = root.left + else: + if not stack: + break + root = stack.pop() + arr.append(root.val) + root = root.right + + return arr + + +def replace_nodes(root, arr): + if not root: + return + + stack = [] + i = 1 + while True: + if root: + stack.append(root) + root = root.left + else: + if not stack: + break + root = stack.pop() + root.val = arr[i-1] + arr[i+1] + i += 1 + root = root.right + + + return root + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(4) +root.left.right = Node(5) +root.right.right = Node(7) +root.right.left = Node(6) + +arr = inorder(root) +print('Inorder traversal original - ', arr) + +new_root = replace_nodes(root, arr) +new_arr = inorder(new_root) +print('New traversal - ', new_arr) + diff --git a/data_structures/binary_trees/symmetric_binary_tree.py b/data_structures/binary_trees/symmetric_binary_tree.py deleted file mode 100644 index 8f742715..00000000 --- a/data_structures/binary_trees/symmetric_binary_tree.py +++ /dev/null @@ -1,37 +0,0 @@ -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - -def isSymmetric(self, root: TreeNode) -> bool: - """ - function to determine if the provided TreeNode is the root - of a symmetric binary tree - """ - - def isMirror(left: TreeNode, right: TreeNode) -> bool: - """ - Utility function to determine if two trees mirror each other. If the root and - all subtrees searched here are mirrors, then the tree as a whole is symmetric - """ - - # Two null values are mirrors - if left is None and right is None: - return True - - # If only one value is null, these trees do not mirror each other - if left is None or right is None: - return False - - # If the values are not equal, the trees do not mirror each other - if left.val != right.val: - return False - - # If left.left mirrors right.right, and left.right mirrors right.left, - # the subtrees mirror each other - return isMirror(left.left, right.right) and isMirror(left.right, right.left) - - return isMirror(root, root) - diff --git a/data_structures/binary_trees/top_view.py b/data_structures/binary_trees/top_view.py new file mode 100644 index 00000000..565e9e00 --- /dev/null +++ b/data_structures/binary_trees/top_view.py @@ -0,0 +1,52 @@ +""" +Print the top view of a binary tree + +Almost like vertical traversal +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + self.col = None + + +def top_view(root): + if not root: + return + + queue = [] + col = 0 + d = {} + + queue.append(root) + root.col = col + + while queue: + root = queue.pop(0) + col = root.col + + if col not in d: + d[col] = root.val + + if root.left: + queue.append(root.left) + root.left.col = col - 1 + if root.right: + queue.append(root.right) + root.right.col = col + 1 + + for i in sorted(d): + print(d[i], end=" ") + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.right = Node(4) +root.left.right.right = Node(5) +root.left.right.right.right = Node(6) + +top_view(root) \ No newline at end of file diff --git a/data_structures/binary_trees/traversals.py b/data_structures/binary_trees/traversals.py new file mode 100644 index 00000000..5c26eb39 --- /dev/null +++ b/data_structures/binary_trees/traversals.py @@ -0,0 +1,15 @@ +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + + +class Tree: + + def __init__(self, root): + self.root = root + + + def inorder() diff --git a/data_structures/binary_trees/vertical_traversal.py b/data_structures/binary_trees/vertical_traversal.py new file mode 100644 index 00000000..344a546f --- /dev/null +++ b/data_structures/binary_trees/vertical_traversal.py @@ -0,0 +1,102 @@ +""" +Print vertical view of a binary tree + + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + + +The output of print this tree vertically will be: +4 +2 +1 5 6 +3 8 +7 +9 + +source - https://www.geeksforgeeks.org/print-binary-tree-vertical-order-set-2/ +""" + +class Node: + + def __init__(self, val): + self.val = val + self.left = None + self.right = None + self.col = None + + +def print_vertical_util(root, col, d): + if not root: + return + + if col in d: + d[col].append(root.val) + else: + d[col] = [root.val] + + print_vertical_util(root.left, col-1, d) + print_vertical_util(root.right, col+1, d) + + +def print_vertical(root): + d = {} + col = 0 + + print_vertical_util(root, col, d) + + for k, v in sorted(d.items()): + for i in v: + print(i, end=' ') + print() + + +def print_vertical_iterative(root): + queue = [] + col = 0 + d = {} + + queue.append(root) + root.col = col + + while queue: + root = queue.pop(0) + col = root.col + + if col not in d: + d[col] = [root.val] + else: + d[col].append(root.val) + + if root.left: + queue.append(root.left) + root.left.col = col - 1 + if root.right: + queue.append(root.right) + root.right.col = col + 1 + + for k, v in sorted(d.items()): + for i in v: + print(i, end=' ') + print() + + + +root = Node(1) +root.left = Node(2) +root.right = Node(3) +root.left.left = Node(4) +root.left.right = Node(5) +root.right.left = Node(6) +root.right.right = Node(7) +root.right.left.right = Node(8) +root.right.right.right = Node(9) + +print_vertical(root) + +print("Iterative solution - ") +print_vertical_iterative(root) \ No newline at end of file diff --git a/data_structures/bst/average_of_levels.py b/data_structures/bst/average_of_levels.py index 5028cf58..273c18d6 100644 --- a/data_structures/bst/average_of_levels.py +++ b/data_structures/bst/average_of_levels.py @@ -1,3 +1,7 @@ +""" +Find the mathematical average of all levels of a BST +""" + import collections class Node(): diff --git a/data_structures/bst/duplicate_keys.py b/data_structures/bst/duplicate_keys.py index e044b7ce..a09dc2c1 100644 --- a/data_structures/bst/duplicate_keys.py +++ b/data_structures/bst/duplicate_keys.py @@ -1,3 +1,11 @@ +""" +Create a BST such that it can have duplicate nodes. +Technically BST cannot have duplicate nodes. The work around is to +have a counter associated with every node. Increment its count whenever +there is a duplicate. If the count goes to zero, delete that node +""" + + class Node(): def __init__(self, val): @@ -37,15 +45,41 @@ def insert(root, val): return root +def min_value_node(root): + curr = root + while curr: + curr = curr.left + return curr.val + + def delete(root, val): if not root: - return - if root.val == val: + return None + + if val < root.val: + root.left = delete(root.left, val) + elif val > root.val: + root.right = delete(root.right, val) + else: if root.count > 1: root.count -= 1 else: + # check if left node is None + if not root.left: + temp = root.right + root = None + return temp + # chec if right node is None + if not root.right: + temp = root.left + root = None + return temp + + temp = min_value_node(root.right) + root.val = temp.val + root.right = delete(root.right, temp.val) - + return root root = Node(5) @@ -56,3 +90,7 @@ def delete(root, val): insert(root, 10) inorder(root) + +print("After deletion") +root = delete(root, 8) +inorder(root) \ No newline at end of file diff --git a/data_structures/bst/index.md b/data_structures/bst/index.md deleted file mode 100644 index 7e286a69..00000000 --- a/data_structures/bst/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# Index of BST - -* [[Deletion](deletion.py) -* [Sorted Array To BST](sorted_array_to_bst.py) -* [Print Left Node](print_left_node.py) -* [Diameter](diameter.py) -* [BFS](bfs.py) -* [Check if BT is BST](check_if_bt_if_bst.py) -* [Trim BST](trim_bst.py) -* [BT to BST](bt_to_bst.py) -* [Binary Search Tree](binary_search_tree.py) -* [Convert BST to Right Node Tree](convert_bst_to_right_node_tree.py) -* [kth largest in BST](kth_largest_in_bst.py) -* [Range Sum](range_sum.py) -* [DFS Iterative](dfs_iterative.py) -* [Print Ancestor](print_ancestor.py) -* [Min Max Value in BST](min_max_value_in_bst.py) -* [Check BT is Subtree of another BT](check_bt_is_subtree_of_another_bt.py) -* [Ceil](ceil.py) -* [Closest Element](closest_element.py) -* [kth smallest in BST](kth_smallest_in_bst.py) -* [Insertion Iterative](insertion_iterative.py) -* [Lowest Common Ancestor](lowest_common_ancestor.py) -* [DFS Recursion](dfs_recursion.py) -* [Search](search.py) -* [Insertion Recursive](insertion_recursive.py) -* [Duplicate Keys](duplicate_keys.py) -* [Merge Sum](merge_sum.py) -* [Linked List to BST](linked_list_to_bst.py) -* [Reverse Inorder Traversal](reverse_inorder_traversal.py) -* [Average of Levels](average_of_levels.py) diff --git a/data_structures/bst/kth_largest_in_bst.py b/data_structures/bst/kth_largest_in_bst.py index 1a013baf..e96eb84c 100644 --- a/data_structures/bst/kth_largest_in_bst.py +++ b/data_structures/bst/kth_largest_in_bst.py @@ -1,3 +1,11 @@ +""" +Print the kth largest element in a BST + +The inorder traversal gives elements of BST in ascending order. Do reverse inorder +(Right-Node-Left) and print the kth element +""" + + class Node(): def __init__(self, val): diff --git a/data_structures/bst/kth_smallest_in_bst.py b/data_structures/bst/kth_smallest_in_bst.py index aefa1345..26131ee1 100644 --- a/data_structures/bst/kth_smallest_in_bst.py +++ b/data_structures/bst/kth_smallest_in_bst.py @@ -1,3 +1,11 @@ +""" +Print the kth smallest number in BST + +The inorder traversal of BST gives elements in ascending order. So do inorder, +keep count and return the kth value +""" + + class Node(): def __init__(self, val): diff --git a/data_structures/bst/linked_list_to_bst.py b/data_structures/bst/linked_list_to_bst.py index 4b3a9aca..ac980985 100644 --- a/data_structures/bst/linked_list_to_bst.py +++ b/data_structures/bst/linked_list_to_bst.py @@ -28,5 +28,6 @@ def linked_list_to_bst(head): def ll_to_bst_recur(head, n): if n <= 0: return None - left = ll_to_bst_recur( + # TODO: Fix me! + # left = ll_to_bst_recur( diff --git a/data_structures/bst/print_ancestor.py b/data_structures/bst/print_ancestor.py index 8158d037..b065efa0 100644 --- a/data_structures/bst/print_ancestor.py +++ b/data_structures/bst/print_ancestor.py @@ -11,9 +11,6 @@ def print_ancestor_recursive(root, key): return False if root.val == key: return True - if print_ancestor_recursive(root.left, key) || print_ancestor_recursive(root.right, key): + if print_ancestor_recursive(root.left, key) or print_ancestor_recursive(root.right, key): return root.data return False - - - diff --git a/data_structures/bst/second_largest_in_bst.py b/data_structures/bst/second_largest_in_bst.py index 598b36bb..334a3dd6 100644 --- a/data_structures/bst/second_largest_in_bst.py +++ b/data_structures/bst/second_largest_in_bst.py @@ -1,3 +1,14 @@ +""" +There can be two condition for finding the second largest element in a BST + +1. If right subtree does not exist, find the largest on the left side +Otherwise, +2. If right exists but right's left and right's right do not, that means +you are currently at the second largest element + +Move to right +""" + class Node: def __init__(self, val): diff --git a/data_structures/bst/trim_bst.py b/data_structures/bst/trim_bst.py index 31a17422..1ba86545 100644 --- a/data_structures/bst/trim_bst.py +++ b/data_structures/bst/trim_bst.py @@ -1,3 +1,7 @@ +""" +Trim a BST so that all elements lie within a given high and low range +""" + class Node(): def __init__(self, val): @@ -12,7 +16,7 @@ def trim(root, L, R): if root.val > R: return trim(root.left, L, R) if root.val < L: - return trim(root. right, L, R) + return trim(root.right, L, R) root.left = trim(root.left, L, R) root.right = trim(root.right, L, R) return root diff --git a/data_structures/circular_linked_list/check_circular_linked_list.py b/data_structures/circular_linked_list/check_circular_linked_list.py index 6a72e6e7..4da76507 100644 --- a/data_structures/circular_linked_list/check_circular_linked_list.py +++ b/data_structures/circular_linked_list/check_circular_linked_list.py @@ -1,3 +1,7 @@ +""" +Check if a linked list is a circular linked list +""" + class Node(): def __init__(self, val): diff --git a/data_structures/circular_linked_list/index.md b/data_structures/circular_linked_list/index.md deleted file mode 100644 index a14e9993..00000000 --- a/data_structures/circular_linked_list/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Index of circular linked list - -* [Check Circular Linked List](check_circular_linked_list.py) -* [Delete](delete.py) -* [Traversal](traversal.py) diff --git a/data_structures/deque/deque.py b/data_structures/deque/deque.py new file mode 100644 index 00000000..85c7dd2c --- /dev/null +++ b/data_structures/deque/deque.py @@ -0,0 +1,68 @@ +class Deque(): + def __init__(self): + self.data = list() + + def push_front(self, elem): + temp = list() + temp.append(elem) + + for i in self.data: + temp.append(i) + + self.data = temp + + def push_back(self, elem): + self.data.append(elem) + + def pop_front(self): + temp = list() + + for i in range(0, len(self.data)): + if not i==0: + temp.append(self.data[i]) + + self.data = temp + + def pop_back(self): + temp = list() + + for i in range(0, len(self.data)): + if not i==len(self.data)-1: + temp.append(self.data[i]) + + self.data = temp + + def get_first(self): + if(len(self.data)>0): + return self.data[0] + else: + return "Deque is empty" + + def get_last(self): + if(len(self.data)>0): + return self.data[len(self.data)-1] + else: + return "Deque is empty" + + def size(self): + return len(self.data) + + def is_empty(self): + if len(self.data) == 0: + return True + return False + + def contains(self, elem): + for i in self.data: + if i==elem: + return True + + return False + + def print_elements(self): + result = "" + + for i in self.data: + result += str(i) + " | " + + print(result) \ No newline at end of file diff --git a/data_structures/doubly_linked_list/doubly_linked_list.py b/data_structures/doubly_linked_list/doubly_linked_list.py index 2702ac1a..9feb64b0 100644 --- a/data_structures/doubly_linked_list/doubly_linked_list.py +++ b/data_structures/doubly_linked_list/doubly_linked_list.py @@ -1,7 +1,7 @@ class Node: - def __init__(self, data = None, next, previous = None): + def __init__(self, data = None, next = None, previous = None): self.data = data - self.next = None + self.next = next self.previous = previous class Lista: diff --git a/data_structures/doubly_linked_list/index.md b/data_structures/doubly_linked_list/index.md deleted file mode 100644 index 0a7d6f6d..00000000 --- a/data_structures/doubly_linked_list/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index of doubly_linked_list - -* [Doubly Linked List](doubly_linked_list.py) diff --git a/data_structures/graphs/DFS.py b/data_structures/graphs/DFS.py deleted file mode 100644 index cc133a1f..00000000 --- a/data_structures/graphs/DFS.py +++ /dev/null @@ -1,46 +0,0 @@ -# An Iterative DFS solution. -class Graph: - def __init__(self, V): - self.V = V - self.adj = [[] for i in range(V)] - - def add_edge(self, v, w): - self.adj[v].append(w) - - def DFS_util(self, s, visited): - - stack = [] - - stack.append(s) - - while (len(stack) != 0): - - s = stack.pop() - - if (not visited[s]): - print(s, end=" ") - visited[s] = True - - i = 0 - while i < len(self.adj[s]): - if (not visited[self.adj[s][i]]): - stack.append(self.adj[s][i]) - i += 1 - - def DFS(self): - visited = [False] * self.V - for i in range(self.V): - if (not visited[i]): - self.DFS_util(i, visited) - - -if __name__ == '__main__': - - g = Graph(5) - g.add_edge(1, 0) - g.add_edge(2, 1) - g.add_edge(3, 4) - g.add_edge(4, 0) - - print("Following is Depth First Traversal") - g.DFS() diff --git a/data_structures/graphs/PrimsMST.py b/data_structures/graphs/PrimsMST.py deleted file mode 100644 index b0156210..00000000 --- a/data_structures/graphs/PrimsMST.py +++ /dev/null @@ -1,58 +0,0 @@ -# Uses adjacency matrix representation of graph to calculate Minimum -# Spanning Tree - -import sys - - -class Graph(): - - def __init__(self, vertices): - self.V = vertices - self.graph = [[0 for column in range(vertices)] - for row in range(vertices)] - - # Function to print generated MST - def print_MST(self, parent): - print("Edge \t Weight") - for i in range(1, self.V): - print(parent[i], "-", i, "\t", self.graph[i][parent[i]]) - - # Function to find vertex with min. distance from set of vertices - #not included in MST - def min_value(self, key, MSTSet): - minval = sys.maxsize - - for v in range(self.V): - if (key[v] < minval) and (MSTSet[v] == False): - minval = key[v] - min_index = v - return min_index - - # Function to construct Prim's MST - def MST(self): - key = [sys.maxsize] * self.V - parent = [None] * self.V - key[0] = 0 - MSTSet = [False] * self.V - parent[0] = -1 - - for j in range(self.V): - u = self.min_value(key, MSTSet) - MSTSet[u] = True - - for v in range(self.V): - if (self.graph[u][v] > 0) and (MSTSet[v] == - False) and (key[v] > self.graph[u][v]): - key[v] = self.graph[u][v] - parent[v] = u - - self.print_MST(parent) - - -g = Graph(4) -g.graph = [[0, 2, 0, 6], - [2, 0, 3, 8], - [0, 3, 0, 0], - [6, 8, 0, 0]] - -g.MST() diff --git a/data_structures/graphs/TopoSort.py b/data_structures/graphs/TopoSort.py deleted file mode 100644 index 4cb5dccb..00000000 --- a/data_structures/graphs/TopoSort.py +++ /dev/null @@ -1,44 +0,0 @@ -# Topological sorting of Directed Acyclic Graph -from collections import defaultdict - - -class Graph: - def __init__(self, vertices): - self.graph = defaultdict(list) - self.V = vertices - - def add_edge(self, u, v): - self.graph[u].append(v) - - def topological_sort_util(self, v, visited, stack): - - visited[v] = True - - for i in self.graph[v]: - if not visited[i]: - self.topological_sort_util(i, visited, stack) - - stack.insert(0, v) - - # Topological sort function - def topological_sort(self): - visited = [False] * self.V - stack = [] - - for i in range(self.V): - if (visited[i] == False): - self.topological_sort_util(i, visited, stack) - - print(stack) - - -g = Graph(6) -g.add_edge(5, 2) -g.add_edge(5, 0) -g.add_edge(4, 0) -g.add_edge(4, 1) -g.add_edge(2, 3) -g.add_edge(3, 1) - -print("One possible Topological Sort of the graph is:") -g.topological_sort() diff --git a/data_structures/graphs/adjacency_matrix.py b/data_structures/graphs/adjacency_matrix.py new file mode 100644 index 00000000..f173bc37 --- /dev/null +++ b/data_structures/graphs/adjacency_matrix.py @@ -0,0 +1,37 @@ +class Graph: + + def __init__(self, vertices, directed: bool): + self.V = vertices + self.e = 0 + self.d = directed + self.graph = [[0 for i in range(vertices)] for j in range(vertices)] + + def add_edge(self, ver1, ver2): + if self.d: + self.graph[ver1][ver2] = 1 + else: + self.graph[ver1][ver2] = 1 + self.graph[ver2][ver1] = 1 + + def remove_edge(self, ver1, ver2): + if self.graph[ver1][ver2] == 0: + print("No edge between %d and %d" % (ver1, ver2)) + return + if self.d: + self.graph[ver1][ver2] = 0 + else: + self.graph[ver1][ver2] = 0 + self.graph[ver2][ver1] = 0 + + def print_graph(self): + for i in self.graph: + print(i) + +if __name__=="__main__": + g1 = Graph(3,0) + g1.add_edge(0,0) + g1.add_edge(1,1) + g1.add_edge(2,2) + g1.remove_edge(2,1) + g1.print_graph() + diff --git a/data_structures/graphs/all_paths_between_two_vertices.py b/data_structures/graphs/all_paths_between_two_vertices.py new file mode 100644 index 00000000..3632d36e --- /dev/null +++ b/data_structures/graphs/all_paths_between_two_vertices.py @@ -0,0 +1,50 @@ +# Use backtracking +# The only poroblem with this approach is that if there is a cycle, then +# it can show infinitely many paths +# Reference - https://www.geeksforgeeks.org/count-possible-paths-two-vertices/ + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = [[] for i in range(vertices)] + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def count_paths_util(self, u, v, visited, counter): + visited[u] = True + + # If the destination vertex is found + if u == v: + counter[0] += 1 + + else: + for i in range(len(self.graph[u])): + if not visited[self.graph[u][i]]: + self.count_paths_util(self.graph[u][i], v, visited, counter) + + visited[u] = False + + + def count_paths(self, u, v): + visited = [False] * self.vertices + + counter = [0] + + self.count_paths_util(u, v, visited, counter) + + return counter[0] + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(0, 3) +g.add_edge(2, 0) +g.add_edge(2, 1) +g.add_edge(1, 3) + +print(g.count_paths(2, 3)) diff --git a/data_structures/graphs/bellman_ford.py b/data_structures/graphs/bellman_ford.py new file mode 100644 index 00000000..49aabe8a --- /dev/null +++ b/data_structures/graphs/bellman_ford.py @@ -0,0 +1,102 @@ +""" +Bellman Ford algorithm is used to find the single source shortest path in a weighted directed graph. +Example - find the shortest path from node A to F + +Advantages of this algorithm over Djikstra's - +1. Bellman Ford also works for negative weight edges +2. Also finds negative weight cycles in a graph + +Disvantage - +1. Slower than Dijsktra's + +** Idea (https://www.youtube.com/watch?v=-mOEd_3gTK0&t) + +0. Shortest path can be found as - +if distance[v] > distance[u] + weight(u, v), then the right side is the shorter path +1. Mark parent and the new weight each time this is run +2. Do this V-1 times. In the first iteration, the algorithm will find the shortest path between the source +and source's neighbours +3. In the second, between source and source's neighbours' neighbours and so on (V - 1) times +4. Do this again. If in this Vth iteration, the weights of path decrease, then there is a negative cycle in the +graph + +Time complexity - O(V * E) +Space complexity - O(V) +""" + +from collections import defaultdict + +class Edge: + + def __init__(self, source, dest, weight): + self.source = source + self.dest = dest + self.weight = weight + + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + self.edges = {} + + + def add_edge(self, u, v, weight): + self.graph[u].append(v) + self.edges[(u, v)] = Edge(u, v, weight) + + + def bellman_ford(self, source, destination): + parent = [-1] * self.vertices + distance = [float("inf")] * self.vertices + + parent[source] = source + distance[source] = source + + for i in range(self.vertices - 1): # Doing V - 1 times to find shortest distance + + for (u, v) in self.edges: + wt = self.edges[(u, v)].weight + + if distance[v] > distance[u] + wt: + distance[v] = distance[u] + wt + parent[v] = u + + # Now for the Vth iteration, check for the negative cycle + + negative_cycle_present = False + + for (u, v) in self.edges: + wt = self.edges[(u, v)].weight + if distance[v] > distance[u] + wt: + negative_cycle_present = True + break + + if negative_cycle_present: + print('Contains negative cycle') + else: + print('No negative cycle') + + # Printing the shortest path from source to destination + + while True: + print(f'{destination}', end=' ') + destination = parent[destination] + + if destination == source: + break + + +g = Graph(5) + +g.add_edge(0, 1, 4) +g.add_edge(0, 2, 5) +g.add_edge(0, 3, 8) +g.add_edge(1, 2, -3) +g.add_edge(2, 4, 4) +g.add_edge(3, 4, 2) +g.add_edge(4, 3, 1) + +g.bellman_ford(0, 3) \ No newline at end of file diff --git a/data_structures/graphs/bfs.py b/data_structures/graphs/bfs.py new file mode 100644 index 00000000..67722def --- /dev/null +++ b/data_structures/graphs/bfs.py @@ -0,0 +1,42 @@ +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def bfs(self, s): + visited = [False] * self.vertices + + queue = [] + + queue.append(s) + visited[s] = True + + bfs = [] + + while queue: + s = queue.pop(0) + print(s, end=' ') + for i in self.graph[s]: + if visited[i] == False: + queue.append(i) + visited[i] = True + +g = Graph(6) + +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(0, 3) +g.add_edge(2, 4) +g.add_edge(3, 4) +g.add_edge(3, 5) + +g.bfs(0) diff --git a/data_structures/graphs/bipartite_graph.py b/data_structures/graphs/bipartite_graph.py new file mode 100644 index 00000000..0296de6e --- /dev/null +++ b/data_structures/graphs/bipartite_graph.py @@ -0,0 +1,84 @@ +""" +Check for bipartite graph + +Do a BFS and make source of red color and its neighbour blue +Keep on doing this. +If self loop, return false +If current color == neighbour color, false, else true + +Reference - https://www.geeksforgeeks.org/bipartite-graph/ + +A bipartite graph is a graph whose vertices can be divided into two disjoint sets U and V +such that every edge connects a vertex in U to one in V. Also, a bipartite graph does not +contain any odd length cycles + +Also, a graph with no edge is a bipartite graph - https://math.stackexchange.com/a/53947 +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def bfs(self, s): + visited = [False] * self.V + queue = [] + queue.append(s) + visited[s] = True + + while queue: + s = queue.pop(0) + print(s, end=' ') + + for i in self.graph[s]: + if not visited[i]: + visited[i] = True + queue.append(i) + + + def is_bipartite(self, s): + # 0 -> color 0 (blue) + # 1 -> color 1 (red) + # -1 -> no color assigned + + colors = [-1] * self.V + colors[s] = 1 # Color soure vertex red + + queue = [] + queue.append(s) + + while queue: + s = queue.pop(0) + + if s in self.graph[s]: # Check for self loop + return False + + # An edge u to v exists and destination is not + # colored + for i in self.graph[s]: + if colors[i] == -1: + colors[i] = 1 - colors[s] + queue.append(i) + + elif colors[i] == colors[s]: + return False + + return True + + +g = Graph(5) +g.add_edge(0, 1) +g.add_edge(1, 2) +g.add_edge(2, 3) +g.add_edge(3, 4) +g.add_edge(4, 0) +print(g.is_bipartite(0)) \ No newline at end of file diff --git a/data_structures/graphs/check_if_graph_is_tree.py b/data_structures/graphs/check_if_graph_is_tree.py new file mode 100644 index 00000000..90ed3425 --- /dev/null +++ b/data_structures/graphs/check_if_graph_is_tree.py @@ -0,0 +1,59 @@ +""" +A graph is a tree if - +1. It does not contain cycles +2. The graph is connected + +Do DFS and see if every vertex can be visited from a source vertex and check for cycle +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def is_tree(self, s): + visited = [False] * self.vertices + parent = [-1] * self.vertices + stack = [] + + visited[s] = True + no_of_visited = 1 + stack.append(s) + + while stack: + s = stack.pop() + + for i in self.graph[s]: + if visited[i] == False: + parent[i] = s + visited[i] = True + stack.append(i) + no_of_visited += 1 + elif parent[s] != i: + return "Not a tree" + + if no_of_visited == self.vertices: + return "Graph is Tree" + else: + return "Not a tree" + + +g = Graph(7) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(2, 4) +g.add_edge(4, 5) +g.add_edge(1, 6) + +print(g.is_tree(0)) \ No newline at end of file diff --git a/data_structures/graphs/connected_components_undirected_graphs.py b/data_structures/graphs/connected_components_undirected_graphs.py new file mode 100644 index 00000000..feb3dce4 --- /dev/null +++ b/data_structures/graphs/connected_components_undirected_graphs.py @@ -0,0 +1,46 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs(self, v, temp, visited): + visited[v] = True + temp.append(v) + + for i in self.graph[v]: + if visited[i] == False: + temp = self.dfs(i, temp, visited) + + return temp + + + def connected_components(self): + visited = [False] * self.vertices + cc = [] + + for i in range(self.vertices): + if visited[i] == False: + temp = [] + cc.append(self.dfs(i, temp, visited)) + + return cc + + +g = Graph(5) +g.add_edge(0, 1) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(0, 3) +g.add_edge(3, 4) + +print(g.connected_components()) \ No newline at end of file diff --git a/data_structures/graphs/count_edges.py b/data_structures/graphs/count_edges.py new file mode 100644 index 00000000..6b9d1f87 --- /dev/null +++ b/data_structures/graphs/count_edges.py @@ -0,0 +1,48 @@ +""" +Count the number of edges in an undirected graph + +Use Handshaking Lemma (Note: Handshaking Lemma is only for undirected graph) + +For all v in V, deg(v) = 2|E| +""" + +Time - O(V) + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = [[] for i in range(vertices)] + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def count_edges(self): + s = 0 + + for i in range(self.V): + s += len(self.graph[i]) + + return s // 2 + + +g = Graph(9) +g.add_edge(0, 1 ) +g.add_edge(0, 7 ) +g.add_edge(1, 2 ) +g.add_edge(1, 7 ) +g.add_edge(2, 3 ) +g.add_edge(2, 8 ) +g.add_edge(2, 5 ) +g.add_edge(3, 4 ) +g.add_edge(3, 5 ) +g.add_edge(4, 5 ) +g.add_edge(5, 6 ) +g.add_edge(6, 7 ) +g.add_edge(6, 8 ) +g.add_edge(7, 8 ) + +print(g.count_edges()) \ No newline at end of file diff --git a/data_structures/graphs/count_paths_between_nodes.py b/data_structures/graphs/count_paths_between_nodes.py new file mode 100644 index 00000000..3b1f71de --- /dev/null +++ b/data_structures/graphs/count_paths_between_nodes.py @@ -0,0 +1,47 @@ +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def count_paths_util(self, u, d, visited, path_count): + visited[u] = True + + if u == d: + path_count[0] += 1 + + else: + i = 0 + for i in range(len(self.graph[u])): + if not visited[self.graph[u][i]]: + self.count_paths_util(self.graph[u][i], d, visited, path_count) + + visited[u] = False + + + def count_paths(self, s, d): + visited = [False] * self.V + path_count = [0] + self.count_paths_util(s, d, visited, path_count) + return path_count[0] + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(0, 3) +g.add_edge(2, 0) +g.add_edge(2, 1) +g.add_edge(1, 3) + +s = 2 +d = 3 + +print(g.count_paths(s, d)) diff --git a/data_structures/graphs/count_sink_nodes.py b/data_structures/graphs/count_sink_nodes.py new file mode 100644 index 00000000..7cce157f --- /dev/null +++ b/data_structures/graphs/count_sink_nodes.py @@ -0,0 +1,31 @@ +""" +Count the number of sink nodes + +Sink nodes are the nodes without any outgoing edge + +Solution - +vertices contains the total number of edges and graph is an adjacency list. +Simply subtract the vertices and len(graph) +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def count_sink_nodes(self): + return self.vertices - len(self.graph) + + +g = Graph(3) +g.add_edge(0, 1) +g.add_edge(0, 2) +print(g.count_sink_nodes()) \ No newline at end of file diff --git a/data_structures/graphs/count_trees.py b/data_structures/graphs/count_trees.py new file mode 100644 index 00000000..28033ee9 --- /dev/null +++ b/data_structures/graphs/count_trees.py @@ -0,0 +1,51 @@ +""" +Count the number of trees in a forest + +A forest is a collection of trees. Do a DFS. If any vertex if not reachable +from any other, then it means it is not a part of that subgraph (tree). Hence +it is a different tree, so increment the count +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[v].append(u) + self.graph[u].append(v) + + + def count_trees(self): + visited = [False] * self.vertices + count = 0 + + for s in range(self.vertices): + if not visited[s]: + visited[s] = True + stack = [] + stack.append(s) + count += 1 + + while stack: + print(stack) + s = stack.pop() + + for i in self.graph[s]: + if not visited[i]: + visited[i] = True + stack.append(i) + + return count + + +g = Graph(5) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(3, 4) + +print('Count of trees - ', g.count_trees()) \ No newline at end of file diff --git a/data_structures/graphs/cycle_in_directed_graph.py b/data_structures/graphs/cycle_in_directed_graph.py new file mode 100644 index 00000000..2a2b3253 --- /dev/null +++ b/data_structures/graphs/cycle_in_directed_graph.py @@ -0,0 +1,40 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def is_cyclic_util(self, v, visited, rec_stack): + visited[v] = True + rec_stack[v] = True + + for neighbour in self.graph[v]: + if visited[neighbour] == False: + if self.is_cyclic_util(neighbour, visited, rec_stack): + return True + + elif rec_stack[neighbour] == True: + return True + + rec_stack[v] = False + return False + + + def is_cyclic(self): + visited = [False] * self.V + rec_stack = [False] * self.V + + for i in range(self.V): + if visited[i] == False: + if self.is_cyclic_util(node, visited, rec_stack) == True: + return True + + return False diff --git a/data_structures/graphs/cycle_in_directed_graph_iterative.py b/data_structures/graphs/cycle_in_directed_graph_iterative.py new file mode 100644 index 00000000..cc0b3cd2 --- /dev/null +++ b/data_structures/graphs/cycle_in_directed_graph_iterative.py @@ -0,0 +1,53 @@ +from collections import defaultdict + + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def dfs(self): + visited = [False] * self.vertices + recursion_stack = [False] * self.vertices + stack = [] + + for v in range(self.vertices): + if visited[v] == False: + visited[v] = True + recursion_stack[v] = True + + stack.append(v) + + while stack: + s = stack.pop() + + recursion_stack[s] = True + + for i in self.graph[s]: + if visited[i] == False: + stack.append(i) + visited[i] = True + recursion_stack[i] = True + elif recursion_stack[i] == True: + return "Contains Cycle" + + recursion_stack[v] = False + + return "No cycle" + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(2, 3) +g.add_edge(3, 3) +print(g.dfs()) \ No newline at end of file diff --git a/data_structures/graphs/cycle_in_directed_graph_using_colors.py b/data_structures/graphs/cycle_in_directed_graph_using_colors.py new file mode 100644 index 00000000..44dfb5ac --- /dev/null +++ b/data_structures/graphs/cycle_in_directed_graph_using_colors.py @@ -0,0 +1,60 @@ +""" +Using three colors - white, gray and black +White - vertices that are not processed (inital state of all vertices) +Gray - vertices that are in DFS +Black - fully traversed vertices (i.e its progenies are also done) + +If while traversing any adjacent node is colored Gray, that means cycle exists +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def detect_cycle(self): + color = ['white'] * self.vertices + visited = [False] * self.vertices + + stack = [] + + for v in range(self.vertices): + if color[v] == 'white': + color[v] = 'gray' + + stack.append(v) + + while stack: + s = stack.pop() + + for i in self.graph[s]: + if color[i] == 'white': + stack.append(i) + color[i] = 'gray' + elif color[i] == 'gray': + return "Cycle detected" + + color[v] = 'black' + + return "Cycle not present" + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(2, 3) +g.add_edge(3, 3) +# g.add_edge(0, 1) +# g.add_edge(0, 2) +# g.add_edge(1, 3) +print(g.detect_cycle()) \ No newline at end of file diff --git a/data_structures/graphs/cycle_in_directed_graph_using_colors_recursive.py b/data_structures/graphs/cycle_in_directed_graph_using_colors_recursive.py new file mode 100644 index 00000000..c4a687ed --- /dev/null +++ b/data_structures/graphs/cycle_in_directed_graph_using_colors_recursive.py @@ -0,0 +1,58 @@ +""" +Using three colors - white, gray and black +White - vertices that are not processed (inital state of all vertices) +Gray - vertices that are in DFS +Black - fully traversed vertices (i.e its progenies are also done) + +If while traversing any adjacent node is colored Gray, that means cycle exists +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def dfs(self, vertex, colors): + colors[vertex] = 'Gray' + + for v in self.graph[vertex]: + + if colors[v] == 'Gray': + return True + + elif colors[v] == 'White' and self.dfs(v, colors) == True: + return True + + colors[vertex] = 'Black' + return False + + + def is_cyclic(self): + colors = ['White'] * self.vertices + + for vertex in self.graph.keys(): + if colors[vertex] == 'White': + if self.dfs(vertex, colors) == True: + return True + + return False + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(2, 3) +g.add_edge(3, 3) + +print(g.is_cyclic()) \ No newline at end of file diff --git a/data_structures/graphs/cycle_in_undirected_graph.py b/data_structures/graphs/cycle_in_undirected_graph.py new file mode 100644 index 00000000..bb16b122 --- /dev/null +++ b/data_structures/graphs/cycle_in_undirected_graph.py @@ -0,0 +1,37 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def is_cyclic_util(self, v, visited, parent): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + if self.is_cyclic_util(i, visited, v): + return True + + elif parent != i: + return True + + return False + + + def is_cyclic(self): + visited = [False] * (self.V) + + for i in range(self.V): + if visited[i] == False: + if self.is_cyclic_util(i, visited, -1): + return True + + return False diff --git a/data_structures/graphs/cycle_in_undirected_graph_iterative.py b/data_structures/graphs/cycle_in_undirected_graph_iterative.py new file mode 100644 index 00000000..565cedde --- /dev/null +++ b/data_structures/graphs/cycle_in_undirected_graph_iterative.py @@ -0,0 +1,60 @@ +""" +This is almost same as the iterative one for directed graph but we cannot use the +concept of recursion stack here because in directed graphs, there is defined path to +traverse but in undirected its possible that an edge (or a path) can be traversed +infite number of times. + +Instead check for parents (which means vertex from which you reached the current vertex). +If a vertex is visited and you are not coming to this vertex from the current "source" vertex +(source - vertex from which DFS has started), then it means that in the same DFS chain, there is +another path to reach this vertex - hence a cycle +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs(self): + visited = [False] * self.vertices + parent = [-1] * self.vertices + + stack = [] + + for v in range(self.vertices): + if visited[v] == False: + visited[v] = True + + stack.append(v) + + while stack: + s = stack.pop() + + for i in self.graph[s]: + if visited[i] == False: + parent[i] = s + stack.append(i) + visited[i] = True + elif parent[s] != i: + return "Contains Cycle" + + return "No cycle" + + +g = Graph(5) +g.add_edge(1, 0) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(0, 3) +g.add_edge(3, 4) +print(g.dfs()) diff --git a/data_structures/graphs/cycle_in_undirected_graph_union_find.py b/data_structures/graphs/cycle_in_undirected_graph_union_find.py new file mode 100644 index 00000000..e8e0af42 --- /dev/null +++ b/data_structures/graphs/cycle_in_undirected_graph_union_find.py @@ -0,0 +1,59 @@ +""" +This method cannot be used in Directed graph because of the direction of the edge. +In a union of set containing element A and B, you cannot specify whether A is going to B +or vice versa +""" + +from collections import defaultdict + + +class Graph: + + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def union(self, parent, x, y): + parent[x] = y + + + def find_parent(self, parent, i): + if parent[i] == -1: + return i + else: + return self.find_parent(parent, parent[i]) + + + def check_cyclic(self): + parent = [-1] * self.vertices + + for i in self.graph.keys(): + for j in self.graph[i]: + x = self.find_parent(parent, i) + y = self.find_parent(parent, j) + + if x == y: + return "Cyclic" + + self.union(parent, x, y) + + return "Not cyclic" + + +g = Graph(4) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 2) +g.add_edge(2, 0) +g.add_edge(2, 3) +g.add_edge(3, 3) +# g.add_edge(0, 1) +# g.add_edge(0, 2) +# g.add_edge(1, 3) +print(g.check_cyclic()) \ No newline at end of file diff --git a/data_structures/graphs/dag_longest_path.py b/data_structures/graphs/dag_longest_path.py new file mode 100644 index 00000000..796c43da --- /dev/null +++ b/data_structures/graphs/dag_longest_path.py @@ -0,0 +1,71 @@ +""" +The idea is similar to DAG Shortest Path. Only the comparision part changes +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v, w): + self.graph[u].append((v, w)) + + + def topological_sort_util(self, vertex, visited, stack): + visited[vertex] = True + + for v, weight in self.graph[vertex]: + if visited[v] == False: + self.topological_sort_util(v, visited, stack) + + stack.insert(0, vertex) + + + def topological_sort(self): + visited = [False] * self.vertices + stack = [] + + for v in range(self.vertices): + if visited[v] == False: + self.topological_sort_util(v, visited, stack) + + return stack + + + def longest_path(self, s): + stack = self.topological_sort() + + distance = [-float("inf")] * self.vertices + distance[s] = 0 + + while stack: + i = stack.pop(0) + + for vertex, weight in self.graph[i]: + if distance[vertex] < distance[i] + weight: + distance[vertex] = distance[i] + weight + + for i in range(self.vertices): + print(f"{s} -> {i} = {distance[i]}") + + +g = Graph(6) +g.add_edge(0, 1, 5) +g.add_edge(0, 2, 3) +g.add_edge(1, 3, 6) +g.add_edge(1, 2, 2) +g.add_edge(2, 4, 4) +g.add_edge(2, 5, 2) +g.add_edge(2, 3, 7) +g.add_edge(3, 5, 1) +g.add_edge(3, 4, -1) +g.add_edge(4, 5, -2) + +source = 0 + +g.longest_path(source) \ No newline at end of file diff --git a/data_structures/graphs/dag_shortest_path.py b/data_structures/graphs/dag_shortest_path.py new file mode 100644 index 00000000..36bebda4 --- /dev/null +++ b/data_structures/graphs/dag_shortest_path.py @@ -0,0 +1,79 @@ +""" +Find shortest path from source vertex to all vertices + +In general, Bellaman-Ford and Dijkstra can be used to find shortest path. But for +DAG, we can improvise + +Bellman-Ford: O(V*E), Dijsktra: O(E+VlogV) + +For DAG, using Topological sort will solve this problem in O(V+E). We are using Topo +sort because it lays out a graph in its linear representation according to the paths. +Hence we can proceed in this order to find shortest path +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v, w): + self.graph[u].append((v, w)) + + + def topological_sort_util(self, vertex, visited, stack): + visited[vertex] = True + + for v, weight in self.graph[vertex]: + if visited[v] == False: + self.topological_sort_util(v, visited, stack) + + stack.insert(0, vertex) + + + def topological_sort(self): + visited = [False] * self.vertices + stack = [] + + for v in range(self.vertices): + if visited[v] == False: + self.topological_sort_util(v, visited, stack) + + return stack + + + def shortest_path(self, s): + stack = self.topological_sort() + + distance = [float("inf")] * self.vertices + distance[s] = 0 + + while stack: + i = stack.pop(0) + + for vertex, weight in self.graph[i]: + if distance[vertex] > distance[i] + weight: + distance[vertex] = distance[i] + weight + + for i in range(self.vertices): + print(f"{s} -> {i} = {distance[i]}") + + +g = Graph(6) +g.add_edge(0, 1, 5) +g.add_edge(0, 2, 3) +g.add_edge(1, 3, 6) +g.add_edge(1, 2, 2) +g.add_edge(2, 4, 4) +g.add_edge(2, 5, 2) +g.add_edge(2, 3, 7) +g.add_edge(3, 4, -1) +g.add_edge(4, 5, -2) + +source = 0 + +g.shortest_path(source) \ No newline at end of file diff --git a/data_structures/graphs/dfs.py b/data_structures/graphs/dfs.py new file mode 100644 index 00000000..6af4a6cc --- /dev/null +++ b/data_structures/graphs/dfs.py @@ -0,0 +1,35 @@ +from collections import defaultdict + +class Graph: + + def __init__(self): + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs_util(self, v, visited): + visited[v] = True + print(v, end=' ') + + for i in self.graph[v]: + if visited[i] == False: + self.dfs_util(i, visited) + + + def dfs(self, v): + visited = [False] * (len(self.graph)) + self.dfs_util(v, visited) + + +g = Graph() +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(0, 3) +g.add_edge(1, 4) +g.add_edge(2, 5) +g.add_edge(3, 6) +print(g.dfs(0)) diff --git a/data_structures/graphs/dijsktra_algorithm.py b/data_structures/graphs/dijsktra_algorithm.py new file mode 100644 index 00000000..e69de29b diff --git a/data_structures/graphs/index.md b/data_structures/graphs/index.md deleted file mode 100644 index 7baba1cf..00000000 --- a/data_structures/graphs/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index of graphs - -* [Adjacency List](adjacency_list.py) diff --git a/data_structures/graphs/iterative_dfs.py b/data_structures/graphs/iterative_dfs.py new file mode 100644 index 00000000..c2eb7169 --- /dev/null +++ b/data_structures/graphs/iterative_dfs.py @@ -0,0 +1,49 @@ +# The code is almost same to BFS, the only difference being queue +# is replaced by stack + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def dfs(self): + visited = [False] * self.vertices + stack = [] + + for s in range(self.vertices): + if visited[s] == False: + visited[s] = True + + stack.append(s) + + while stack: + s = stack.pop() + print(s, end=' ') + + for i in self.graph[s]: + if visited[i] == False: + stack.append(i) + visited[i] = True + + +# g = Graph(4) +# g.add_edge(0, 1) +# g.add_edge(0, 2) +# g.add_edge(1, 2) +# g.add_edge(2, 0) +# g.add_edge(2, 3) +# g.add_edge(3, 3) +g = Graph(5) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(3, 4) + +g.dfs() diff --git a/data_structures/graphs/kosaraju_algorithm.py b/data_structures/graphs/kosaraju_algorithm.py new file mode 100644 index 00000000..99277672 --- /dev/null +++ b/data_structures/graphs/kosaraju_algorithm.py @@ -0,0 +1,88 @@ +""" +Reference - https://www.geeksforgeeks.org/strongly-connected-components/ + +Algorithm - https://youtu.be/RpgcYiky7uw + +This is used to find all the strongly connected components and does DFS +2 times. + +Note: A single vertex is also strongly connected +""" + +from collections import defaultdict + + +class Graph: + + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def fill_time(self, v, visited, stack): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + self.fill_time(i, visited, stack) + + stack = stack.append(v) + + + def dfs(self, v, visited): + visited[v] = True + print(v, end=' ') + + for i in self.graph[v]: + if visited[i] == False: + self.dfs(i, visited) + + + def create_tranpose(self): + tgraph = Graph(self.vertices) + + for i in self.graph: + for j in self.graph[i]: + tgraph.add_edge(j, i) + + return tgraph + + + def kosaraju(self): + visited = [False] * self.vertices + stack = [] + + for v in range(self.vertices): + if visited[v] == False: + self.fill_time(v, visited, stack) + + tgraph = self.create_tranpose() + visited = [False] * self.vertices + + while stack: + s = stack.pop() + + if visited[s] == False: + tgraph.dfs(s, visited) + print() + + +g = Graph(11) +g.add_edge(0, 1) +g.add_edge(2, 0) +g.add_edge(1, 2) +g.add_edge(1, 3) +g.add_edge(3, 4) +g.add_edge(4, 5) +g.add_edge(5, 3) +g.add_edge(6, 5) +g.add_edge(6, 7) +g.add_edge(7, 8) +g.add_edge(8, 9) +g.add_edge(9, 10) +g.kosaraju() \ No newline at end of file diff --git a/data_structures/graphs/level_of_nodes.py b/data_structures/graphs/level_of_nodes.py new file mode 100644 index 00000000..c453fa95 --- /dev/null +++ b/data_structures/graphs/level_of_nodes.py @@ -0,0 +1,49 @@ +""" +Level is the distance of a node from a source node. This concept can be used to find +the distance between 2 nodes in an unweighted graph as well. A simple BFS traversal +between these 2 nodes will give the level and level will always be the shortest distance +between nodes. +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def print_levels(self, s): + levels = [None] * self.vertices + levels[s] = 0 + queue = [] + queue.append(s) + + while queue: + s = queue.pop(0) + + for i in self.graph[s]: + if levels[i] == None: + levels[i] = levels[s] + 1 + queue.append(i) + + print('Node \t Level') + for node, level in enumerate(levels): + print(f'{node} \t {level}') + +g = Graph(8) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(1, 4) +g.add_edge(1, 5) +g.add_edge(2, 5) +g.add_edge(2, 6) +g.add_edge(6, 7) +g.print_levels(0) \ No newline at end of file diff --git a/data_structures/graphs/longest_path.py b/data_structures/graphs/longest_path.py new file mode 100644 index 00000000..e3334433 --- /dev/null +++ b/data_structures/graphs/longest_path.py @@ -0,0 +1 @@ +# https://www.geeksforgeeks.org/longest-path-between-any-pair-of-vertices/ diff --git a/data_structures/graphs/max_edges_that_can_be_added_to_dag.py b/data_structures/graphs/max_edges_that_can_be_added_to_dag.py new file mode 100644 index 00000000..f4ba0900 --- /dev/null +++ b/data_structures/graphs/max_edges_that_can_be_added_to_dag.py @@ -0,0 +1,83 @@ +""" +Find the max number of edges that can be added to a Directed Acyclic Graph such that +it remains a DAG. + +Solution - +The trick is to add all edges from left to right. Now adding any edge from right to left +will make the graph cyclic because that edge's counterpart will exist from left-to-right. +Hence a cycle will be formed. + +1. Topologically sort all the edges +2. If edge is not there left to right, create the edge +3. Count the number of edges added + +source - https://www.geeksforgeeks.org/maximum-edges-can-added-dag-remains-dag/ + +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def topological_sort_util(self, v, visited, stack): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + self.topological_sort_util(i, visited, stack) + + stack.insert(0, v) + + + def topological_sort(self): + visited = [False] * self.vertices + stack = [] + + for i in range(self.vertices): + if visited[i] == False: + self.topological_sort_util(i, visited, stack) + + return stack + + + def max_edges(self): + topo = self.topological_sort() + visited = [False] * self.vertices + count = 0 + + for i in range(len(topo)): + vertex = topo[i] + # Mark the connected vertices visited + for j in self.graph[vertex]: + visited[j] = True + + # Print the unmarked nodes from topo + for j in range(i+1, len(topo)): + if visited[topo[j]] == False: + print(f"{vertex} -> {topo[j]}") + count += 1 + + visited[topo[j]] = False + + print('Maximum edges that can be added - ', count) + + +g = Graph(6) +g.add_edge(5, 2) +g.add_edge(5, 0) +g.add_edge(4, 0) +g.add_edge(4, 1) +g.add_edge(2, 3) +g.add_edge(3, 1) + +g.max_edges() \ No newline at end of file diff --git a/data_structures/graphs/max_edges_to_make_bipartite.py b/data_structures/graphs/max_edges_to_make_bipartite.py new file mode 100644 index 00000000..9b42fb82 --- /dev/null +++ b/data_structures/graphs/max_edges_to_make_bipartite.py @@ -0,0 +1,60 @@ +""" +Find the max edges that can be added to a tree so that it stays a bipartite graph + +keep count of nodes of each color - say count_color1, count_color2 +These nodes can be connected in count_color1 x count_color2 ways = max no of edges of a bipartite graph +Also, a tree has n-1 edges +So answer = (count_color1 x count_color2) - (n - 1) + +If the answer comes negative, then its impossible to add any edge because of odd cycles +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def bfs(self, s): + count_color0 = 0 + count_color1 = 0 + + colors = [-1] * self.vertices + colors[s] = 1 + count_color1 += 1 + + queue = [] + queue.append(s) + + while queue: + s = queue.pop(0) + + for i in self.graph[s]: + if colors[i] == -1: + colors[i] = 1 - colors[s] + if colors[i] == 0: + count_color0 += 1 + else: + count_color1 += 1 + queue.append(i) + + ans = (count_color0 * count_color1) - (self.vertices - 1) + return ans if ans > 0 else 0 + + +g = Graph(5) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(2, 4) + +print(g.bfs(0)) \ No newline at end of file diff --git a/data_structures/graphs/min_edges_between_two_vertices.py b/data_structures/graphs/min_edges_between_two_vertices.py new file mode 100644 index 00000000..13456d5f --- /dev/null +++ b/data_structures/graphs/min_edges_between_two_vertices.py @@ -0,0 +1,52 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def find_min_edges(self, u, v): + visited = [False] * self.vertices + distance = [0] * self.vertices + queue = [] + + queue.append(u) + visited[u] = True + + while queue: + s = queue.pop(0) + + for i in self.graph[s]: + if visited[i] == False: + distance[i] = distance[s] + 1 + queue.append(i) + visited[i] = True + + return distance[v] + + +g = Graph(9) + +g.add_edge(0, 1) +g.add_edge(0, 7) +g.add_edge(1, 7) +g.add_edge(1, 2) +g.add_edge(2, 3) +g.add_edge(2, 5) +g.add_edge(2, 8) +g.add_edge(3, 4) +g.add_edge(3, 5) +g.add_edge(4, 5) +g.add_edge(5, 6) +g.add_edge(6, 7) +g.add_edge(7, 8) + +print(g.find_min_edges(0, 5)) diff --git a/data_structures/graphs/min_nodes_to_reach_all_nodes.py b/data_structures/graphs/min_nodes_to_reach_all_nodes.py new file mode 100644 index 00000000..d60f6f9b --- /dev/null +++ b/data_structures/graphs/min_nodes_to_reach_all_nodes.py @@ -0,0 +1,11 @@ +""" +We are given a DAG. Find the smallest set of vertices from which all +nodes in the graph are reachable. It's guaranteed that a unique solution exists. + +Solution - +The crux of the question is that - +1. If any node has an indegree > 1 (i.e it is reachable from any other node), then it means +in a connected graph it will be possible to reach here if its parent node also has an indegree +2. So the min will be the set of nodes with indegree = 0 +""" + diff --git a/data_structures/graphs/min_number_of_operations.py b/data_structures/graphs/min_number_of_operations.py new file mode 100644 index 00000000..7d783e30 --- /dev/null +++ b/data_structures/graphs/min_number_of_operations.py @@ -0,0 +1,54 @@ +""" +Minimum number of operations required to transform a number x to y +Valid operations - multiplication by 2, subtraction by 1 +Ex - x = 4, y = 7 +1. 4 * 2 = 8 +2. 8 - 1 = 7 +Answer = 2 +""" + + +from collections import defaultdict + + +class Node: + + + def __init__(self, value, level): + self.value = value + self.level = level + + +def min_steps(x, y): + node_x = Node(x, 0) + + visited = [] + queue = [] + queue.append(node_x) + + while queue: + s = queue.pop(0) + + if s.value == y: + return s.level + + visited.append(s.value) + + if s.value * 2 == y or s.value - 1 == y: + return s.level + 1 + + # If not visited already, add its children + + if s.value * 2 not in visited: + new_node = Node(s.value * 2, s.level + 1) + queue.append(new_node) + + if s.value - 1 not in visited: + new_node = Node(s.value - 1, s.level + 1) + queue.append(new_node) + + +x = 2 +y = 5 + +print(min_steps(x, y)) \ No newline at end of file diff --git a/data_structures/graphs/mother_vertex.py b/data_structures/graphs/mother_vertex.py new file mode 100644 index 00000000..794c88ee --- /dev/null +++ b/data_structures/graphs/mother_vertex.py @@ -0,0 +1,65 @@ +""" +A mother vertex is a vertex such that all other vertices +can be reached by a path from this vertex + +Reference - https://www.geeksforgeeks.org/find-a-mother-vertex-in-a-graph/ + +Time complexity - 2 * O(V + E) = O(V + E) +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def dfs_util(self, v, visited): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + self.dfs_util(i, visited) + + + def add_edge(self, v, w): + self.graph[v].append(w) + + + def find_mother(self): + visited = [False] * self.V + + v = 0 + + for i in range(self.V): + if visited[i] == False: + self.dfs_util(i, visited) + v = i + + # Now check if v is one of the mother vertex + # Reset all the values of visited and do DFS beginning from v + # to check if all the vertices are reachable from it or not + + visited = [False] * self.V + self.dfs_util(v, visited) + + if any(i == False for i in visited): + return -1 + + else: + return v + + +g = Graph(7) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(4, 1) +g.add_edge(6, 4) +g.add_edge(5, 6) +g.add_edge(5, 2) +g.add_edge(6, 0) + +print("Mother vertex is - ", g.find_mother()) diff --git a/data_structures/graphs/not_reachable_nodes.py b/data_structures/graphs/not_reachable_nodes.py new file mode 100644 index 00000000..df2e765a --- /dev/null +++ b/data_structures/graphs/not_reachable_nodes.py @@ -0,0 +1,44 @@ +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs_util(self, v, visited): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + self.dfs_util(i, visited) + + + def count_nodes(self, v): + visited = [False] * self.V + + self.dfs_util(v, visited) + + count = 0 + + for i in range(self.V): + if visited[i] == False: + count += 1 + + return count + +g = Graph(8) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 2) +g.add_edge(3, 4) +g.add_edge(4, 5) +g.add_edge(6, 7) + +print(g.count_nodes(0)) diff --git a/data_structures/graphs/number_of_triangles.py b/data_structures/graphs/number_of_triangles.py new file mode 100644 index 00000000..aa0a1669 --- /dev/null +++ b/data_structures/graphs/number_of_triangles.py @@ -0,0 +1,30 @@ +# Reference - https://www.geeksforgeeks.org/number-of-triangles-in-a-undirected-graph/ +# Reference - https://www.geeksforgeeks.org/number-of-triangles-in-directed-and-undirected-graphs/ + +# In case of undirected graph - trace(A^3)/6 +# For undirected graph, just check if an edge exists among all the three vertices + +class Graph: + + def __init__(self, vertices, is_directed): + self.V = vertices + self.graph = [[] for i in range(vertices)] + self.is_directed = is_directed + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def count_triangles(self): + nodes = len(self.graph) + count = 0 + + for i in range(nodes): + for j in range(nodes): + for k in range(nodes): + if i != j and j != k and k != i and self.graph[i][j] and self.graph[j][k] and self.graph[k][i]: + count += 1 + + return count // 3 if is_directed else count // 6 diff --git a/data_structures/graphs/print_all_paths_between_nodes.py b/data_structures/graphs/print_all_paths_between_nodes.py new file mode 100644 index 00000000..0b370237 --- /dev/null +++ b/data_structures/graphs/print_all_paths_between_nodes.py @@ -0,0 +1,52 @@ +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def print_path(self, s, d, visited, path): + visited[s] = True + path.append(s) + + if s == d: + print(path) + + else: + for i in self.graph[s]: + if visited[i] == False: + self.print_path(i, d, visited, path) + + # If path from this node does not lead to the destination, remove it + # from the path stack and mark it as not visited + path.pop() + visited[s] = False + + + def print_all_paths(self, s, d): + visited = [False] * self.vertices + path = [] + self.print_path(s, d, visited, path) + + +g = Graph(4) + +g.add_edge(0, 3) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(1, 3) +g.add_edge(2, 0) +g.add_edge(2, 1) + +s = 2 +d = 3 + +print(f'Paths from {s} to {d} are - ') +g.print_all_paths(s, d) \ No newline at end of file diff --git a/data_structures/graphs/root_which_gives_min_height.py b/data_structures/graphs/root_which_gives_min_height.py new file mode 100644 index 00000000..7a079196 --- /dev/null +++ b/data_structures/graphs/root_which_gives_min_height.py @@ -0,0 +1,58 @@ +""" +Find the node in an undirected graph which gives the minimum height +Reference - https://www.geeksforgeeks.org/roots-tree-gives-minimum-height/ +""" + +from collections import defaultdict +from queue import Queue + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + self.degree = [0] * vertices + + + def add_edge(self, v, w): + self.graph[v].append(w) + self.graph[w].append(v) + self.degree[v] += 1 + self.degree[w] += 1 + + + def root_min_height(self): + q = Queue() + + for i in range(self.V): + if self.degree[i] == 1: # To identify leaf nodes + q.put(i) + + # now move inwards from the leaf node + while self.V > 2: + for i in range(q.qsize()): + t = q.get() + self.V -= 1 + + # For each neighbour decrease its degree and if it becomes + # leaf, insert into the queue + for j in self.graph[t]: + self.degree[j] -= 1 + if self.degree[j] == 1: + q.put(j) + + res = list() + while q.qsize() > 0: + res.append(q.get()) + + + return res + +g = Graph(6) +g.add_edge(0, 3) +g.add_edge(1, 3) +g.add_edge(2, 3) +g.add_edge(4, 3) +g.add_edge(5, 4) + +print(g.root_min_height()) diff --git a/data_structures/graphs/same_path.py b/data_structures/graphs/same_path.py new file mode 100644 index 00000000..82219691 --- /dev/null +++ b/data_structures/graphs/same_path.py @@ -0,0 +1,80 @@ +""" +Check if two nodes are on the same path in a tree. Use DFS and the concept of intime and outtime. +Intime - time when a node is visited for the first time +Outtime - time when a node is visited for the second time after all its children have been visited +For any pair of node if they are on the same path - +intime[u] < intime[v] and outtime[u] > outtime[v] +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + self.intime = None + self.outtime = None + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs(self): + visited = [False] * self.vertices + intime = [0] * self.vertices + outtime = [0] * self.vertices + timer = 0 + num_children_visited = [0] * self.vertices + + for s in range(self.vertices): + if not visited[s]: + visited[s] = True + + stack = [] + stack.append(s) + + while stack: + s = stack.pop() + timer += 1 + intime[s] = timer + + for i in self.graph[s]: + if visited[i] == False: + stack.append(i) + visited[i] = True + num_children_visited[s] += 1 + + if num_children_visited[i] == len(self.graph[i]): + timer += 1 + outtime[i] = timer + + print('intime - ', intime) + print('outtime - ', outtime) + print('num_children_visited - ', num_children_visited) + self.intime = intime + self.outtime = outtime + + + def check_same_path(self, u, v): + if (self.intime[u] < self.intime[v] and self.outtime[u] > self.outtime[v]) or \ + (self.intime[v] < self.intime[u] and self.outtime[v] > self.outtime[u]): + return True + return False + + +g = Graph(7) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(0, 3) +g.add_edge(1, 4) +g.add_edge(2, 5) +g.add_edge(3, 6) + +g.dfs() + +print('Same path - ', g.check_same_path(0, 6)) +print(g.graph[6]) \ No newline at end of file diff --git a/data_structures/graphs/same_path_recursive.py b/data_structures/graphs/same_path_recursive.py new file mode 100644 index 00000000..3b2087aa --- /dev/null +++ b/data_structures/graphs/same_path_recursive.py @@ -0,0 +1,65 @@ +""" +Check if two nodes are on the same path in a undirected graph. Use DFS and the concept of intime and outtime. +Intime - time when a node is visited for the first time +Outtime - time when a node is visited for the second time after all its children have been visited +For any pair of node if they are on the same path - +intime[u] < intime[v] and outtime[u] > outtime[v] +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def dfs(self, vertex, intime, outtime, timer, visited): + visited[vertex] = True + timer += 1 + intime[vertex] = timer + + for v in self.graph[vertex]: + if visited[v] == False: + self.dfs(v, intime, outtime, timer, visited) + + timer += 1 + outtime[vertex] = timer + + + def on_same_path(self, u, v): + intime = [-1] * self.vertices + outtime = [-1] * self.vertices + timer = 0 + visited = [False] * self.vertices + + for vertex in self.graph: + if visited[vertex] == False: + self.dfs(vertex, intime, outtime, timer, visited) + + + if (intime[u] < intime[v] and outtime[u] > outtime[v]) \ + or (intime[v] < intime[u] and outtime[v] > outtime[u]): + return True + return False + + +g = Graph(9) +g.add_edge(0, 1) +g.add_edge(0, 2) +g.add_edge(2, 5) +g.add_edge(1, 3) +g.add_edge(1, 4) +g.add_edge(4, 6) +g.add_edge(4, 7) +g.add_edge(4, 8) + +print(g.on_same_path(0, 4)) +print(g.on_same_path(1, 8)) +print(g.on_same_path(1, 5)) \ No newline at end of file diff --git a/data_structures/graphs/shortest_path_unweighted_graph.py b/data_structures/graphs/shortest_path_unweighted_graph.py new file mode 100644 index 00000000..ca5cd60b --- /dev/null +++ b/data_structures/graphs/shortest_path_unweighted_graph.py @@ -0,0 +1,68 @@ +""" +Find the shortest path between two nodes in an unweighted undirected graph. Remember this +is about finding the shortest path, not the shortest distance. For shortest +distance you can simply calculate the level of nodes from the source vertex +and that will give the answer. For shortest path, use the concept of parents of +Bellman-Ford algorithm. + +Simply do a BFS and keep track of parents of each node. Then recursively print the +parents of destination node until the source node. +""" + +from collections import defaultdict + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def bfs(self, s): + parent = [-1] * self.vertices + visited = [False] * self.vertices + visited[s] = True + queue = [] + queue.append(s) + + while queue: + s = queue.pop(0) + + for i in self.graph[s]: + if visited[i] == False: + queue.append(i) + parent[i] = s + visited[i] = True + + return parent + + + def shortest_path(self, source, dest): + parent = self.bfs(source) + + while True: + print(dest, end=' ') + dest = parent[dest] + + if dest == source: + break + + +g = Graph(8) +g.add_edge(0, 1) +g.add_edge(0, 3) +g.add_edge(1, 2) +g.add_edge(3, 4) +g.add_edge(3, 7) +g.add_edge(4, 5) +g.add_edge(4, 6) +g.add_edge(4, 7) +g.add_edge(5, 6) +g.add_edge(6, 7) + +g.shortest_path(0, 7) \ No newline at end of file diff --git a/data_structures/graphs/topological_sort.py b/data_structures/graphs/topological_sort.py new file mode 100644 index 00000000..d0fb65fa --- /dev/null +++ b/data_structures/graphs/topological_sort.py @@ -0,0 +1,33 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def topo_sort_util(self, v, visited, stack): + visited[v] = True + + for i in self.graph[v]: + if visited[i] == False: + self.topo_sort_util(i, visited, stack) + + stack.insert(0, v) + + + def topo_sort(self): + visited = [False] * self.V + stack = [] + + for i in range(self.V): + if visited[i] == False: + self.topo_sort_util(i, visited, stack) + + print(stack) diff --git a/data_structures/graphs/transpose.py b/data_structures/graphs/transpose.py new file mode 100644 index 00000000..02270c55 --- /dev/null +++ b/data_structures/graphs/transpose.py @@ -0,0 +1,44 @@ +from collections import defaultdict + + +class Graph: + + def __init__(self, vertices): + self.V = vertices + self.graph = defaultdict(list) + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def print_graph(self): + for i in self.graph: + print(i, " --> ", end=" ") + for j in self.graph[i]: + print(j, end=" ") + print() + + + def transpose(self): + g = Graph(self.V) + + for i in self.graph: + for j in self.graph[i]: + g.add_edge(j, i) + + return g + + +g = Graph(5) +g.add_edge(1, 0) +g.add_edge(0, 2) +g.add_edge(2, 1) +g.add_edge(0, 3) +g.add_edge(3, 4) +g.print_graph() + +print('---------') + +t = g.transpose() +t.print_graph() diff --git a/data_structures/graphs/tree_stays_bipartite.py b/data_structures/graphs/tree_stays_bipartite.py new file mode 100644 index 00000000..cda2299a --- /dev/null +++ b/data_structures/graphs/tree_stays_bipartite.py @@ -0,0 +1,56 @@ +# All trees are bipartite graphs +# Reference - https://www.geeksforgeeks.org/maximum-number-edges-added-tree-stays-bipartite-graph/ + +# Traverse the tree (dfs or bfs) and color nodes with two colors and count each like +# count_color1 and count_color2. Then the max edges that the graph can have are +# count_color1 * count_color2, and the max edges of a tree are n-1 + +# Therefore the answer is = count_color1 * count_color2 - (n-1) + +class Graph: + + def __init__(self, vertices): + self.vertices = vertices + self.graph = [[] for i in range(vertices)] + + + def add_edge(self, u, v): + self.graph[u].append(v) + + + def bfs(self, s): + visited = [False] * self.vertices + color_count = [0, 0] + + colors = [-1] * self.vertices + colors[s] = 1 + + queue = [] + + queue.append(s) + visited[s] = True + + while queue: + u = queue.pop(0) + + for v in self.graph[u]: + if colors[v] == -1: # This is a tree. So not visited and not colored is same as there is no cycle + colors[v] = 1 - colors[u] + queue.append(v) + color_count[colors[v]] += 1 + visited[v] = True + + # Counting the max number of edges for graph + graph_edges = color_count[0] * color_count[1] + + return graph_edges + + +# Number of tree nodes +n = 5 + +g = Graph(5) +g.add_edge(1, 2) +g.add_edge(1, 3) +g.add_edge(2, 4) +g.add_edge(3, 5) diff --git a/data_structures/graphs/two_cliques.py b/data_structures/graphs/two_cliques.py new file mode 100644 index 00000000..4eefba9c --- /dev/null +++ b/data_structures/graphs/two_cliques.py @@ -0,0 +1,75 @@ +""" +Find if a graph can be divided into two cliques + +A clique is a subgraph such that all vertices in it are completely +connected with each other + +Solution - +A bipartite graph is a graph which can be divided into two sets U and V such +that every edge from u or v has a destination in the other. +So take the complement of the graph (create edge where there is none, and destroy +edges where present) and if the complement is a bipartite, it means that there are edges +in U and V that connect each other. So complement's complement (i.e the original graph) will +not have these edges and hence it can be divided into two clique +""" + +from collections import defaultdict + +class Graph: + + + def __init__(self, vertices): + self.graph = defaultdict(list) + self.cgraph = defaultdict(list) + self.vertices = vertices + + + def add_edge(self, u, v): + self.graph[u].append(v) + self.graph[v].append(u) + + + def is_bipartite(self): + colors = [-1] * self.vertices + queue = [] + + for v in range(self.vertices): + if colors[v] == -1: + colors[v] = 1 + + queue.append(v) + + while queue: + s = queue.pop(0) + + for i in self.cgraph[s]: + if colors[i] == -1: + colors[i] = 1 - colors[s] + queue.append(i) + + elif colors[i] == colors[s]: + return False + + return True + + + def make_complement(self): + for src, dest in self.graph.items(): + for v in range(self.vertices): + if v not in dest and src != v: + self.cgraph[src].append(v) + self.cgraph[v].append(src) + + + def two_cliques(self): + self.make_complement() + return self.is_bipartite() + + +g = Graph(5) +g.add_edge(0, 3) +g.add_edge(3, 4) +g.add_edge(0, 1) +g.add_edge(1, 2) +g.add_edge(2, 0) +print(g.two_cliques()) \ No newline at end of file diff --git a/data_structures/hash/hash_table.py b/data_structures/hash/hash_table.py new file mode 100644 index 00000000..c1c05ced --- /dev/null +++ b/data_structures/hash/hash_table.py @@ -0,0 +1,29 @@ +""" +Create a hash table from scratch. Use chaining for hash collision +""" + +class HashTable: + + + def __init__(self): + self.hash_table = + + + def check_collision(self): + pass + + + def add_to_linked_list(self): + pass + + + def insert(self): + pass + + + def delete(self): + pass + + + def get(self): + pass diff --git a/data_structures/heap/heap_using_heapq.py b/data_structures/heap/heap_using_heapq.py new file mode 100644 index 00000000..7e28d109 --- /dev/null +++ b/data_structures/heap/heap_using_heapq.py @@ -0,0 +1,26 @@ +""" +Heap in python using heapq library function + +Note: by default, heapq creates a min-heap. To make it a +max-heap, add items after multiplying them by -1 +""" + +from heapq import heappop, heappush, heapify + +heap = [] +heapify(heap) + +heappush(heap, 10) +heappush(heap, 11) +heappush(heap, 2) +heappush(heap, 4) +heappush(heap, 14) +heappush(heap, 1) + +print('first element - ', heap[0]) +print('popping min element - ', heappop(heap)) +print('first element - ', heap[0]) + +# Heap prints as an array and can be access using indexes +print(heap) +print(heap[2]) diff --git a/data_structures/heap/kth_largest_element_in_stream.py b/data_structures/heap/kth_largest_element_in_stream.py new file mode 100644 index 00000000..dfc278e4 --- /dev/null +++ b/data_structures/heap/kth_largest_element_in_stream.py @@ -0,0 +1,61 @@ +""" +Use a priority queue - min heap + +as soon as the stream reaches a length of K, start finding the number + +Since we are using a priority queue (min heap), the minimum number will be +at the first index. O(1) time to extract it + +We have to make sure that the length of the stream does not go above K because +we to find the kth largest element which in terms of this heap means the smallest +element. For example lets say we have K = 4. So as soon as the stream reaches a +length of 4, we start to find the 4th largest number. Now as we are maintaining the +length of the array at 4, 4th largest number will mean the smallest number. Using this +we are designing the program. + +If the number entered in the stream is less than the current min, we dont take it as it +wont affect the result. +""" + +from heapq import heapify, heappop, heappush + +class Stream: + + def __init__(self, k): + self.heap = [] + self.stream = [] + self.k = k + self.curr_min = None + heapify(self.heap) + + + def insert(self, x): + self.stream.append(x) + + if len(self.heap) < self.k: # when the heap is empty or size is less than K + heappush(self.heap, x) + self.curr_min = self.heap[0] + else: + if x > self.curr_min: + heappop(self.heap) # remove the curr min element + heappush(self.heap, x) # insert x + self.curr_min = self.heap[0] + + + def find_kth_max(self): + if len(self.heap) == self.k: + print(f'{self.k}th max number - {self.heap[0]}') + +k = 3 +x = Stream(k) + +num = input() + +while num != 'q': + if num == 's': + print(f'Stream - {x.stream} | K - {k}') + else: + num = int(num) + x.insert(num) + x.find_kth_max() + num = input() diff --git a/data_structures/heap/max_heap.py b/data_structures/heap/max_heap.py new file mode 100644 index 00000000..29fcef70 --- /dev/null +++ b/data_structures/heap/max_heap.py @@ -0,0 +1,143 @@ +""" +Thing to remember - +* index of parent = i / 2 +* index of left child = 2i + 1 +* index of right child = 2i + 2 +""" + +class MaxHeap: + + def __init__(self, maxsize): + self.maxsize = maxsize + self.size = 0 # current number of elements in the heap + self.heap = [0] * self.maxsize + self.front = 0 + + + def parent(self, pos): + return (pos) // 2 + + + def left_child(self, pos): + return 2*pos + 1 + + + def right_child(self, pos): + return 2*pos + 2 + + + def mid_index(self): + return self.size // 2 + + + def last_index(self): + return self.size - 1 + + + def is_leaf(self, pos): + """ + Every node that is after the middle index of the heap + is a leaf node because their children cannot exist as the + index of children are twice their index as those indexes + do not exist in the heap + """ + if self.mid_index() <= pos <= self.last_index(): + return True + return False + + + def is_empty(self): + if self.size == 0: + return True + return False + + + def insert(self, value): + if self.is_empty(): # if the heap is empty + self.heap[self.front] = value + self.size += 1 + return + + if self.size >= self.maxsize: # if max size has been reached + return + + self.size += 1 + self.heap[self.last_index()] = value + + curr = self.last_index() + + # While inserting the element in the heap we have to + # make sure that the inserted element is always smaller + # than its parent. So basically here we are adjusting the + # position of the parent + while self.heap[curr] > self.heap[self.parent(curr)]: + self.swap(curr, self.parent(curr)) + curr = self.parent(curr) + + + def max_heapify(self, pos): + """ + This function will run whenever a node is non-leaf + node and smaller than its childen + """ + if not self.is_leaf(pos): + left = self.heap[self.left_child(pos)] + right = self.heap[self.right_child(pos)] + curr = self.heap[pos] + + if curr < left or curr < right: + + # This check is only to prevent out-of-index error + if left > right: + self.swap(pos, self.left_child(pos)) + self.max_heapify(self.left_child(pos)) + else: + self.swap(pos, self.right_child(pos)) + self.max_heapify(self.right_child(pos)) + + + def swap(self, x, y): + self.heap[x], self.heap[y] = self.heap[y], self.heap[x] + + + def pop_max(self): + max_element = self.heap[self.front] # max element is always at the front + self.heap[self.front] = self.heap[self.last_index()] # placing last element at the front + self.heap[self.last_index()] = 0 + self.size -= 1 # decrease size as one element has been popped + self.max_heapify(self.front) # heapify the heap again + return max_element + + + def print(self): + """ + Priting in inorder + """ + for i in range(0, self.mid_index() + 1): + parent = self.heap[i] + left = self.heap[self.left_child(i)] + right = self.heap[self.right_child(i)] + + print(f"Parent: {self.heap[i]}") + + if left: + print(f"Left child: {left}") + if right: + print(f"Right child: {right}") + + +if __name__ == '__main__': + max_heap = MaxHeap(15) + max_heap.insert(5) + max_heap.insert(3) + max_heap.insert(17) + max_heap.insert(10) + max_heap.insert(84) + max_heap.insert(19) + max_heap.insert(6) + max_heap.insert(22) + max_heap.insert(9) + + max_heap.print() + print('Max element is - ', max_heap.pop_max()) + max_heap.print() diff --git a/data_structures/heap/median_of_infinite_stream.py b/data_structures/heap/median_of_infinite_stream.py new file mode 100644 index 00000000..9ce834a8 --- /dev/null +++ b/data_structures/heap/median_of_infinite_stream.py @@ -0,0 +1,95 @@ +""" +Awesome explanation - https://youtu.be/1LkOrc-Le-Y + +Median is the middle element in a sorted array. The stream of input +integers can be in any order and we will have to store the integers in +such a way that the stream is maintained in an increasing order. + +So the main idea is that take an array and divide it into two parts of +equal length and the median will be as follows - + +* if the total number of integers in the stream is even, then both part will +have the same length. Hence the median will be the average of last element of the first +part (i.e max element of the first part) and the first element of the second part (i.e +min element of the second part) + +* if total number of integers in the stream is odd, add the extra element in the first part. +In this case, the median will be the last element of the first part + +Now we just have to maintain the order of both the parts of the array. Since we want the max +element from the first part, we can use a max heap there. And we can use min heap for the second +part as we need the min element from the second part + +Time complexity: + if the size of stream is N, we will have to iterate N times. LogN because insertion in heap + takes this much time. O(1) for getting the max or min element + + N * LogN +""" + +from heapq import heappush, heappop, heapify + +class MedianStream: + + def __init__(self): + self.stream = [] + self.min_heap = [] + self.max_heap = [] + heapify(self.max_heap) + heapify(self.min_heap) + self.curr_median = None + + + def add_number(self, num): + """ + min heap length <= maxheap length <= min heap length + 1 + """ + self.stream.append(num) + + if len(self.max_heap) == len(self.min_heap) == 0: + self.curr_median = num + + if len(self.max_heap) > len(self.min_heap): + if num < self.curr_median: + max_popped = -1 * heappop(self.max_heap) + heappush(self.min_heap, max_popped) + heappush(self.max_heap, -1 * num) + self.find_median('avg') + else: + heappush(self.min_heap, num) + self.find_median('avg') + else: + if num > self.curr_median: + # num will go to the min heap and the min element + # of min heap will go the max heap + min_popped = heappop(self.min_heap) + heappush(self.max_heap, -1 * min_popped) + heappush(self.min_heap, num) + self.find_median('max') + + else: + # num will go to the max heap + heappush(self.max_heap, -1 * num) + self.find_median('max') + + + def find_median(self, how): + if how == 'max': + self.curr_median = -1 * self.max_heap[0] + elif how == 'avg': + self.curr_median = (self.min_heap[0] + (-1 * self.max_heap[0])) / 2 + + +x = MedianStream() + +num = input() + +while num != 'q': + if num == 's': + print('Stream of integers - ', x.stream) + else: + num = int(num) + x.add_number(num) + print('Median - ', x.curr_median) + + num = input() diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py new file mode 100644 index 00000000..069524d4 --- /dev/null +++ b/data_structures/heap/min_heap.py @@ -0,0 +1,122 @@ +""" +See max_heap.py for more detailed comments +""" + +class MinHeap: + + + def __init__(self, maxsize): + self.maxsize = maxsize + self.size = 0 + self.first = 0 + self.heap = [0] * self.maxsize + + + def is_empty(self): + return self.size == 0 + + + def is_leaf(self, pos): + if self.mid_index() <= pos <= self.last_index(): + return True + return False + + + def parent(self, pos): + return pos // 2 + + + def left_child(self, pos): + return 2*pos + 1 + + + def right_child(self, pos): + return 2*pos + 2 + + + def last_index(self): + return self.size - 1 + + + def mid_index(self): + return self.size // 2 + + + def swap(self, x, y): + self.heap[x], self.heap[y] = self.heap[y], self.heap[x] + + + def pop_min(self): + min_element = self.heap[self.first] + self.heap[self.first] = self.heap[self.last_index()] + self.heap[self.last_index()] = 0 + self.size -= 1 + self.min_heapify(self.first) + return min_element + + + def min_heapify(self, pos): + if not self.is_leaf(pos): + left = self.heap[self.left_child(pos)] + right = self.heap[self.right_child(pos)] + curr = self.heap[pos] + + if curr > left or curr > right: + + if left > right: + self.swap(pos, self.left_child(pos)) + self.min_heapify(self.left_child(pos)) + else: + self.swap(pos, self.right_child(pos)) + self.min_heapify(self.right_child(pos)) + + + def insert(self, element): + if self.is_empty(): + self.heap[self.first] = element + self.size += 1 + return + + if self.size >= self.maxsize: + return + + self.size += 1 + self.heap[self.last_index()] = element + + curr = self.last_index() + + while self.heap[curr] < self.heap[self.parent(curr)]: + self.swap(curr, self.parent(curr)) + curr = self.parent(curr) + + + def print(self): + for i in range(0, self.mid_index() + 1): + parent = self.heap[i] + left = self.heap[self.left_child(i)] + right = self.heap[self.right_child(i)] + + print(f"Parent: {self.heap[i]}") + + if left: + print(f"Left child: {left}") + if right: + print(f"Right child: {right}") + + +if __name__ == '__main__': + min_heap = MinHeap(15) + min_heap.insert(5) + min_heap.insert(3) + min_heap.insert(17) + min_heap.insert(10) + min_heap.insert(84) + min_heap.insert(19) + min_heap.insert(6) + min_heap.insert(22) + min_heap.insert(9) + + min_heap.print() + print(min_heap.heap) + print('Min element is - ', min_heap.pop_min()) + min_heap.print() diff --git a/data_structures/heap/sum_elements_range.py b/data_structures/heap/sum_elements_range.py new file mode 100644 index 00000000..4a3b6645 --- /dev/null +++ b/data_structures/heap/sum_elements_range.py @@ -0,0 +1,24 @@ +""" +Find the sum of elements between k1th and k2th smallest elements +""" + +from heapq import heappush, heapify, heappop + +heap = [20, 8, 22, 4, 12, 10, 14] +k1 = 3 +k2 = 6 + +heapify(heap) + +# extracting min k1 times + +for i in range(k1): + heappop(heap) + +# now do extract min k2 - (k1 + 1) times +s = 0 + +for i in range(k2 - k1 - 1): + s += heappop(heap) + +print(s) diff --git a/data_structures/linked_list/delete_last_occurrence.py b/data_structures/linked_list/delete_last_occurrence.py new file mode 100644 index 00000000..80951ab0 --- /dev/null +++ b/data_structures/linked_list/delete_last_occurrence.py @@ -0,0 +1,36 @@ +""" +Delete last occurence of a number in linked list. +""" + +class Node(): + + def __init__(self, val): + self.val = val + self.next = None + +def delete_last_occurrence(head, val): + if not head: + return None + + curr = head + prev = None + final_prev = None + final_occ = None + + while curr != None: + if curr.val == val: + final_prev = prev + final_occ = curr + + prev = curr + curr = curr.next + + + if final_occ: + # special case that checks for a 1 node list that equals the val + if final_prev: + final_prev.next = final_occ.next + else: + head = None + + return head \ No newline at end of file diff --git a/data_structures/linked_list/index.md b/data_structures/linked_list/index.md index 7c598aed..47a0074e 100644 --- a/data_structures/linked_list/index.md +++ b/data_structures/linked_list/index.md @@ -9,3 +9,4 @@ * [Odd-Even Arrangement](odd_even_arrangement.py) * [Merge Linked List](merge_linked_list.py) * [Remove Duplicates](remove_duplicates.py) +* [Delete Last Occurrence](delete_last_occurrence.py) diff --git a/data_structures/linked_list/merge_linked_list.py b/data_structures/linked_list/merge_linked_list.py index 572f18c4..11220bd8 100644 --- a/data_structures/linked_list/merge_linked_list.py +++ b/data_structures/linked_list/merge_linked_list.py @@ -29,6 +29,6 @@ def merge(l1, l2): ptr = head_merged while ptr: - print ptr.val + print(ptr.val) ptr = ptr.next diff --git a/data_structures/linked_list/sum_of_two_numbers.py b/data_structures/linked_list/sum_of_two_numbers.py new file mode 100644 index 00000000..2f0ea65a --- /dev/null +++ b/data_structures/linked_list/sum_of_two_numbers.py @@ -0,0 +1,71 @@ +class Node(): + + def __init__(self, val): + self.val = val + self.next = None + + +def push(head, data): + node = Node(data) + curr = head + while curr.next: + curr = curr.next + curr.next = node + + +def print_list(head): + curr = head + while curr: + print(curr.val, end=' ') + curr = curr.next + + +def sum_numbers(head1, head2): + carry = 0 + prev = None + res = None + while head1 is not None or head2 is not None: + data1 = 0 if head1 is None else head1.val + data2 = 0 if head2 is None else head2.val + s = carry + data1 + data2 + carry = 1 if s >= 10 else 0 + s = s if s < 10 else s % 10 + + temp = Node(s) + + # if this is the first node, make it head + if res is None: + res = temp + else: + prev.next = temp + + prev = temp + + # move pointers ahead + if head1 is not None: + head1 = head1.next + if head2 is not None: + head2 = head2.next + + if carry > 0: + temp.next = Node(carry) + + return res + + + + +head1 = Node(1) +push(head1, 2) +push(head1, 3) +push(head1, 4) +print_list(head1) +print() +head2 = Node(5) +push(head2, 2) +push(head2, 1) +push(head2, 3) +print_list(head2) +print() +summmed_list = sum_numbers(head1, head2) +print_list(summmed_list) diff --git a/data_structures/matrix/find_in_row_and_column_wise_sorted_matrix.py b/data_structures/matrix/find_in_row_and_column_wise_sorted_matrix.py new file mode 100644 index 00000000..cd1b07e6 --- /dev/null +++ b/data_structures/matrix/find_in_row_and_column_wise_sorted_matrix.py @@ -0,0 +1,17 @@ +def search(mat, n, x): + + i = 0 + + j = n - 1 + + while i < n and j >=0: + if mat[i][j] == x: + print("Found at - ", i, j) + return 1 + if mat[i][j] > x: + j -= 1 + else: + i += 1 + + print("Element not found") + return 0 diff --git a/data_structures/matrix/find_pair.py b/data_structures/matrix/find_pair.py new file mode 100644 index 00000000..e69de29b diff --git a/data_structures/matrix/flood_fill_algorithm.py b/data_structures/matrix/flood_fill_algorithm.py new file mode 100644 index 00000000..935bfd4b --- /dev/null +++ b/data_structures/matrix/flood_fill_algorithm.py @@ -0,0 +1,23 @@ +# Reference - https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/ + +# Dimensions of paint screen +M = 8 +N = 8 + +def flood_fill_util(screen, x, y, prev_color, new_color): + if x < 0 or x >= M or y < 0 or y >= N or screen[x][y] != prev_color or screen[x][y] == new_color: + return + + screen[x][y] = new_color + flood_fill_util(screen, x + 1, y, prev_color, new_color) + flood_fill_util(screen, x - 1, y, prev_color, new_color) + flood_fill_util(screen, x, y + 1, prev_color, new_color) + flood_fill_util(screen, x, y - 1, prev_color, new_color) + + +def flood_fill(screen, x, y, new_color): + prev_color = screen[x][y] # Finding the previous color on x and y + flood_fill_util(screen, x, y, prev_color, new_color) + + + diff --git a/data_structures/palindromic_tree/palindromic_tree.py b/data_structures/palindromic_tree/palindromic_tree.py new file mode 100644 index 00000000..47992793 --- /dev/null +++ b/data_structures/palindromic_tree/palindromic_tree.py @@ -0,0 +1,68 @@ +import typing + + +class Node: + def __init__(self): + self.next: typing.Dict[str, Node] = {} + self.frequency = 0 + self.length = 0 + self.suffix = None + + +class PalindromicTree: + def __init__(self): + self.null_root = Node() + self.imaginary_root = Node() + + self.null_root.length = 0 + self.null_root.suffix = self.imaginary_root + self.imaginary_root.length = -1 + self.imaginary_root.suffix = self.imaginary_root + + self.counter = 0 + self.all_palindromes: list[Node] = [] + self.previous = self.imaginary_root + + def add_letter(self, string, index): + while index - 1 - self.previous.length < 0 or string[index - 1 - self.previous.length] != string[index]: + self.previous = self.previous.suffix + + if self.previous.next.get(string[index]) is not None: + node = self.previous.next.get(string[index]) + node.frequency += 1 + self.previous = node + return + + new_node = Node() + + self.counter += 1 + new_node.frequency = 1 + new_node.length = self.previous.length + 2 + self.previous.next[string[index]] = new_node + + if new_node.length == 1: + new_node.suffix = self.null_root + self.previous = new_node + else: + self.previous = self.previous.suffix + while index - 1 - self.previous.length < 0 or string[index - 1 - self.previous.length] != string[index]: + self.previous = self.previous.suffix + new_node.suffix = self.previous.next[string[index]] + self.previous = new_node + self.all_palindromes.append(new_node) + + def how_many_palindromes(self): + all_nr = 0 + for node in reversed(self.all_palindromes): + node.suffix.frequency += node.frequency + all_nr += node.frequency + print(f"There are {self.counter} unique palindromes and {all_nr} in total") + + +tree = PalindromicTree() + +s = "abaxxaba" +for i in range(len(s)): + tree.add_letter(s, i) +tree.how_many_palindromes() + diff --git a/data_structures/queue/index.md b/data_structures/queue/index.md deleted file mode 100644 index 8506ea63..00000000 --- a/data_structures/queue/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index of Queue - -[Queue](queue.py) \ No newline at end of file diff --git a/data_structures/segment_tree/seg_tree_max.py b/data_structures/segment_tree/seg_tree_max.py new file mode 100644 index 00000000..eb40f116 --- /dev/null +++ b/data_structures/segment_tree/seg_tree_max.py @@ -0,0 +1,44 @@ +import sys + +class MaxSegTree(): + + def __init__(self, n, arr = None): + self._t = [-sys.maxsize - 1] * (4 * n) + self._n = n + if arr: self._build(arr, 1 , 0 , n - 1) + + def _build(self, a, v, tl, tr): + if tl == tr: + self._t[v] = a[tl] + else: + tm = (tl + tr) // 2 + self._build(a, v*2, tl, tm) + self._build(a, v*2+1, tm+1, tr) + self._t[v] = max(self._t[v*2] ,self._t[v*2+1]) + + def _max_util(self, v, tl, tr, l, r): + if l > r: return -sys.maxsize - 1 + + if l == tl and r == tr : return self._t[v] + + tm = (tl + tr) // 2 + return max(self._max_util(v*2, tl, tm, l, min(r, tm)) ,self._max_util(v*2+1, tm+1, tr, max(l, tm+1), r)) + + def _update_util(self, v, tl, tr, pos, new_val): + if tl == tr: + self._t[v] = new_val + else: + tm = (tl + tr) // 2 + if pos <= tm: self._update_util(v*2, tl, tm, pos, new_val) + else: self._update_util(v*2+1, tm+1, tr, pos, new_val) + self._t[v] = max(self._t[v*2] , self._t[v*2+1]) + + def max_element(self, l, r): + return self._max_util(1 , 0 , self._n - 1 , l , r) + + def update(self, pos, new_val): + self._update_util(1 , 0 , self._n - 1 , pos , new_val) + + def add(self, pos, change): + value = self.max_element(pos , pos) + self._update_util(1 , 0 , self._n - 1 , pos , value + change) \ No newline at end of file diff --git a/data_structures/segment_tree/seg_tree_sum.py b/data_structures/segment_tree/seg_tree_sum.py new file mode 100644 index 00000000..ee7481f9 --- /dev/null +++ b/data_structures/segment_tree/seg_tree_sum.py @@ -0,0 +1,42 @@ +class SumSegTree(): + + def __init__(self, n, arr = None): + self._t = [0] * (4 * n) + self._n = n + if arr: self._build(arr, 1 , 0 , n - 1) + + def _build(self, a, v, tl, tr): + if tl == tr: + self._t[v] = a[tl] + else: + tm = (tl + tr) // 2 + self._build(a, v*2, tl, tm) + self._build(a, v*2+1, tm+1, tr) + self._t[v] = self._t[v*2] + self._t[v*2+1] + + def _sum_util(self, v, tl, tr, l, r): + if l > r: return 0 + + if l == tl and r == tr : return self._t[v] + + tm = (tl + tr) // 2 + return self._sum_util(v*2, tl, tm, l, min(r, tm)) + self._sum_util(v*2+1, tm+1, tr, max(l, tm+1), r) + + def _update_util(self, v, tl, tr, pos, new_val): + if tl == tr: + self._t[v] = new_val + else: + tm = (tl + tr) // 2 + if pos <= tm: self._update_util(v*2, tl, tm, pos, new_val) + else: self._update_util(v*2+1, tm+1, tr, pos, new_val) + self._t[v] = self._t[v*2] + self._t[v*2+1] + + def sum(self, l, r): + return self._sum_util(1 , 0 , self._n - 1 , l , r) + + def update(self, pos, new_val): + self._update_util(1 , 0 , self._n - 1 , pos , new_val) + + def add(self, pos, change): + value = self._sum(pos , pos) + self._update_util(1 , 0 , self._n - 1 , pos , value + change) \ No newline at end of file diff --git a/data_structures/stack/balanced_expression.py b/data_structures/stack/balanced_expression.py index 784666c0..60e3c506 100644 --- a/data_structures/stack/balanced_expression.py +++ b/data_structures/stack/balanced_expression.py @@ -1,24 +1,30 @@ -# simple program to check if an expression is balanced using stack stack = [] def checkBalanced(expr): for i in expr: if i == "{" or i == "[" or i == "(": stack.append(i) elif i == "}" or i == "]" or i == ")": - temp = stack.pop() - if i == "}" and temp != "{": + if not stack: return False - elif i == "]" and temp != "[": + top = stack.pop() + if i == "}" and top != "{": return False - elif i == ")" and temp != "(": + elif i == "]" and top != "[": return False + elif i == ")" and top != "(": + return False + else: + print("Invalid Expression") + return False - return True + if not len(stack): + return True + else: + return False # main function expr = input() -result = checkBalanced(expr) -if result: - print("Expression is balanced") +if not checkBalanced(expr): + print("Not Balanced") else: - print("Expression is not balanced") + print('Balanced') diff --git a/data_structures/stack/largest_rectangle_area_in_histogram.py b/data_structures/stack/largest_rectangle_area_in_histogram.py index bee42b5b..6aa55947 100644 --- a/data_structures/stack/largest_rectangle_area_in_histogram.py +++ b/data_structures/stack/largest_rectangle_area_in_histogram.py @@ -1,8 +1,7 @@ ''' Largest rectangle area in a histogram:: -Find the largest rectangular area possible in a given histogram where the largest rectangle can be made of a number of contiguous bars. For simplicity, assume that all bars have same width and the width is 1 unit. - - +Find the largest rectangular area possible in a given histogram where the largest rectangle can be made of a number of contiguous bars. +For simplicity, assume that all bars have same width and the width is 1 unit. ''' def max_area_histogram(histogram): @@ -12,36 +11,26 @@ def max_area_histogram(histogram): max_area = 0 # Initialize max area index = 0 - while index < len(histogram): - + + while index < len(histogram): if (not stack) or (histogram[stack[-1]] <= histogram[index]): stack.append(index) index += 1 - - else: - top_of_stack = stack.pop() - - area = (histogram[top_of_stack] * - ((index - stack[-1] - 1) - if stack else index)) - + area = (histogram[top_of_stack] * ((index - stack[-1] - 1) if stack else index)) max_area = max(max_area, area) while stack: - top_of_stack = stack.pop() - - area = (histogram[top_of_stack] * - ((index - stack[-1] - 1) - if stack else index)) + area = (histogram[top_of_stack] * ((index - stack[-1] - 1) if stack else index)) max_area = max(max_area, area) - return max_area + + hist = [4, 7, 1, 8, 4, 9, 5] print("Maximum area is", - max_area_histogram(hist)) +max_area_histogram(hist)) diff --git a/data_structures/stack/stack_using_linked_list.py b/data_structures/stack/stack_using_linked_list.py index 35323074..794541b5 100644 --- a/data_structures/stack/stack_using_linked_list.py +++ b/data_structures/stack/stack_using_linked_list.py @@ -62,9 +62,9 @@ def pop(self): # Test stack functionality stack.push(e2) stack.push(e3) -print stack.pop().value -print stack.pop().value -print stack.pop().value -print stack.pop() +print(stack.pop().value) +print(stack.pop().value) +print(stack.pop().value) +print(stack.pop()) stack.push(e4) -print stack.pop().value +print(stack.pop().value) diff --git a/data_structures/stack/stack_using_list.py b/data_structures/stack/stack_using_list.py new file mode 100644 index 00000000..172892dd --- /dev/null +++ b/data_structures/stack/stack_using_list.py @@ -0,0 +1,25 @@ +class Stack(): + """ + Stack follows Last-In-First-Out methodology + """ + + def __init__(self): + self.entries = [] + + def size(self): + return len(self.entries) + + def push(self, val): + self.entries.append(val) + + def pop(self): + if self.size() > 0: + self.entries.pop(self.size() - 1) + + + + + + + + \ No newline at end of file diff --git a/data_structures/strings/KMP_Pattern_Search.py b/data_structures/strings/KMP_Pattern_Search.py new file mode 100644 index 00000000..37bfcd2c --- /dev/null +++ b/data_structures/strings/KMP_Pattern_Search.py @@ -0,0 +1,57 @@ +#Python program for KMP Algorithm +def KMP_pattern_search(pattern, text): + P = len(pattern) + Q = len(text) + + # create long_prefix_suffix[] that will hold the longest prefix suffix + # values for pattern + long_prefix_suffix = [0]*P + + # index for pattern[] + j=0 + + # preprocess the pattern (caluclate long_prefix_suffix[] array) + long_Prefix_Suffix_Array(pattern, P, long_prefix_suffix) + + # index for text[] + i=0 + + while i < Q: + if pattern[j] == text[i]: + i += 1 + j += 1 + + if j == P: + print("Pattern found at index " + str(i-j)) + j=long_prefix_suffix[j-1] + + # mismatch after j matches + elif i < Q and pattern[j] != text[i]: + if j != 0: + j=long_prefix_suffix[j-1] + else: + i += 1 + +def long_Prefix_Suffix_Array(pattern, P, long_prefix_suffix): + # length of the previous longest prefix suffix + l=0 + long_prefix_suffix[0]=0 + i=1 + + # the loop calculates long_prefix_suffix[i] for i = 1 to P-1 + while i < P: + if pattern[i] == pattern[l]: + l += 1 + long_prefix_suffix[i] = l + i += 1 + else: + if l != 0: + l=long_prefix_suffix[l-1] + else: + long_prefix_suffix[i] = 0 + i += 1 + +text = "ABABDABACDABABCABAB" +pattern = "ABABCABAB" +KMP_pattern_search(pattern, text) + diff --git a/data_structures/strings/check_interleaving_of_strings.py b/data_structures/strings/check_interleaving_of_strings.py new file mode 100644 index 00000000..e232ed51 --- /dev/null +++ b/data_structures/strings/check_interleaving_of_strings.py @@ -0,0 +1,57 @@ +''' +Question: Given three strings A, B and C. Write a function that checks whether C +is an interleaving of A and B. C is said to be interleaving A and B, if +it contains all characters of A and B and order of all characters in +individual strings is preserved. + +Solution: We include one character from String A or String B and check whether the resultant +string formed so far by one particular interleaving of the the current prefix of String A +and String B form a prefix of String C. This can be achieved using dynamic programming. +This approach relies on fact that in order to determine whether a +substring of String C(upto index k), can be formed by interleaving strings String A +and String B upto indices i and j respectively depends on the characters of +String A and String B upto indices i and j only and not on characters coming afterwards. + +Complexity analysis - +Time complexity : O(m x n) . dp array of size n is filled m times where m and n are lengths of +String A and String B respectively. +Space complexity : O(n) + +''' + +#Function to check if String C is formed by interleaving of String A and B +#Returns a boolean value +def isInterleaving(string_A, string_B, string_C): + #Check if the length of String C is equal to sum of lengths of String A and B + #In other words, check if String C contains all characters of String A and B + if(len(string_C) != len(string_A) + len(string_B)): return False + + #Create an empty array of length of String B + dp = [None] * (len(string_B) + 1) + + for i in range(len(string_A) + 1): + for j in range(len(string_B) + 1): + if(i == 0 and j == 0): + #The first value of array dp always holds True + dp[j] = True + elif(i == 0): + dp[j] = dp[j - 1] and string_B[j - 1] == string_C[j - 1] + elif(j == 0): + dp[j] = dp[j] and string_A[i - 1] == string_C[i - 1] + else: + dp[j] = ((dp[j] and string_A[i - 1] == string_C[i + j - 1]) or (dp[j - 1] and string_B[j - 1] == string_C[i + j - 1])) + + + return dp[len(string_B)] + +#Driver Code +string_A = input("Enter string A") +string_B = input("Enter string B") +string_C = input("Enter string C") + +result = isInterleaving(string_A, string_B, string_C) + +if(result): + print("String C is interleaving String A and String B") +else: + print("String C is not interleaving String A and String B") \ No newline at end of file diff --git a/data_structures/strings/index.md b/data_structures/strings/index.md index 3f7bf484..df56b743 100644 --- a/data_structures/strings/index.md +++ b/data_structures/strings/index.md @@ -3,4 +3,5 @@ * [Check Permutations](check_permutations.py) * [Are Anagrams](are_anagrams.py) * [Adjacent Vowel Pairs](adjacent_vowel_pairs.py) -* [Toggle String](toggle_string.py) \ No newline at end of file +* [Toggle String](toggle_string.py) +* [Is Rotate String](rotate_string.py) diff --git a/data_structures/strings/reverse_with_special_chars.py b/data_structures/strings/reverse_with_special_chars.py new file mode 100644 index 00000000..6d4aa95b --- /dev/null +++ b/data_structures/strings/reverse_with_special_chars.py @@ -0,0 +1,25 @@ +# Reverse a string making sure that special characters remain +# in their place + +# a#bcd#efg -> g#fed#cba + + +def reverse(s): + i = 0 + j = len(s) - 1 + + while i < j: + if s[i].isalnum() and s[j].isalnum(): + s[i], s[j] = s[j], s[i] + i += 1 + j -= 1 + if not s[i].isalnum(): + i += 1 + if not s[j].isalnum(): + j -= 1 + return ''.join(s) + + +s = 'a#bcd#efg' +print(s) +print(reverse(list(s))) diff --git a/data_structures/strings/rotate_string.py b/data_structures/strings/rotate_string.py new file mode 100644 index 00000000..48edb9d9 --- /dev/null +++ b/data_structures/strings/rotate_string.py @@ -0,0 +1,26 @@ +def is_rotate_string(A, B): + """ + Given two strings, A and B. + + A shift on A consists of taking string A and moving the leftmost character to the rightmost position. For example, if A = 'abcde', then it will be 'bcdea' after one shift on A. Return True if and only if A can become B after some number of shifts on A. + + :type A: str + :type B: str + :rtype: bool + """ + len_a = len(A) + len_b = len(B) + if len_a != len_b: + return False + if len_a == 0: + return True + for i in range(len_a): + A = A[-1:] + A[:-1] + if A == B: + return True + return False + + +# Test +print(is_rotate_string('abcde', 'cdeab')) +print(is_rotate_string('abcde', 'abced')) diff --git a/data_structures/strings/unique_char_check.py b/data_structures/strings/unique_char_check.py new file mode 100644 index 00000000..3af3083c --- /dev/null +++ b/data_structures/strings/unique_char_check.py @@ -0,0 +1,24 @@ +""" +Question +You are given a string S, check if all characters are unique. + +SAMPLE INPUT 1 +abcd +SAMPLE OUTPUT 1 +True + +SAMPLE INPUT 2 +aabc +SAMPLE OUTPUT 2 +False +""" +from collections import Counter +def unique_char_check(S): + character_count = Counter(S) + + if len(character_count) == len(S): + return True + return False + +S = input() +print(unique_char_check(S)) \ No newline at end of file diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py new file mode 100644 index 00000000..c4c533ba --- /dev/null +++ b/data_structures/trie/trie.py @@ -0,0 +1,91 @@ +class TrieNode(): + def __init__(self): + + self.children = {} + self.last = False + +class Trie(): + def __init__(self): + + self.root = TrieNode() + self.word_list = [] + + def formTrie(self, keys): + + for key in keys: + self.insert(key) + + def insert(self, key): + + node = self.root + + for a in list(key): + if not node.children.get(a): + node.children[a] = TrieNode() + + node = node.children[a] + + node.last = True + + def search(self, key): + + node = self.root + found = True + + for a in list(key): + if not node.children.get(a): + found = False + break + + node = node.children[a] + + return node and node.last and found + + def suggestionsRec(self, node, word): + + if node.last: + self.word_list.append(word) + + for a,n in node.children.items(): + self.suggestionsRec(n, word + a) + + def printAutoSuggestions(self, key): + + node = self.root + not_found = False + temp_word = '' + + for a in list(key): + if not node.children.get(a): + not_found = True + break + + temp_word += a + node = node.children[a] + + if not_found: + return 0 + elif node.last and not node.children: + return -1 + + self.suggestionsRec(node, temp_word) + + for s in self.word_list: + print(s) + return 1 + +keys = ["hello", "dog", "hell", "cat", "a", + "hel", "help", "helps", "helping"] +key = "hel" +status = ["Not found", "Found"] + +t = Trie() + +t.formTrie(keys) + +comp = t.printAutoSuggestions(key) + +if comp == -1: + print("No other strings found with this prefix\n") +elif comp == 0: + print("No string found with this prefix\n") diff --git a/index_updater.py b/index_updater.py deleted file mode 100755 index 296518da..00000000 --- a/index_updater.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os - -# Terminal parameters. -if len(sys.argv[1:]) == 0: - print('Please, give a path') - sys.exit(1) -else: - mypath = sys.argv[1] - -# Going trought folders and subfolders. -for root, dirs, files in os.walk(mypath): - curr_files = list() - curr_folder = os.path.basename(root) - - for name in files: - if name.endswith('.py') and not name == '__init__py' and not dirs: - curr_files.append(name) - - # If we have files, write "index.md" - if curr_files: - with open(os.path.join(root, 'index.md'), mode='w') as md_file: - md_file.write('# Index of {}\n\n'.format(curr_folder)) - for line in curr_files: - md_file.write('* ' + line + '\n') diff --git a/logo/logo.png b/logo/logo.png deleted file mode 100644 index 28749cb7..00000000 Binary files a/logo/logo.png and /dev/null differ diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..ad364f49 --- /dev/null +++ b/notes.md @@ -0,0 +1,180 @@ +# Notes + +## Linked List + +### Advantages over Array + +1. Dynamic size +2. Ease of insertion/deletion + +### Drawbacks + +1. Random access is not allowed +2. Extra memory is required with each pointer +3. Not cache friendly. Since array elements are contiguous locations, there is locality of reference which is not there in case of linked lists. + +### Applications + +1. Implementation of stack and queue and adjacency list representation of graphs +2. Dynamic memory allocation [Why?](https://www.slideshare.net/profansari/linked-list-static-and-dynamic-memory-allocation) +3. Maintaining directory of names +4. Image viewer, where images are linked in slideshows +5. Previous and next page in web browsers +6. Playlist in music players + +--- + +## Circular Linked List + +### Advantages + +1. Any node can be a starting point +2. Easy to go from the last node to the first node + +### Disadvantages + +1. More memory +2. Not easy to reverse the list + +### Applications + +1. Computers use Circular Linked List to cycle through active applications. Basically Round Robin Scheduling algorithm +2. Task queues +3. Snake game on Nokia phones +4. Never ending playlist + +--- + +## Doubly Linked List + +### Advantages + +1. Can be traversed in both directions +2. Deletion is more efficient as we can easily fetch the previous node + +### Disadvantages + +1. Consumes more space because of extra pointer + +[See XOR Linked List](https://www.geeksforgeeks.org/xor-linked-list-a-memory-efficient-doubly-linked-list-set-1/) + +### Applications + +1. Schedulers in operating systems +2. Undo-Redo functionality +3. MRU and LRU in operating systems + +--- + +## Tree + +### Applications + +1. Store hierarchial data +2. Trie are used in autocompletion and suggestion of words +3. BST can be used in searching +4. B-Tree and B+ Trees are used in database indexing + +### Misc + +1. [Advantages of BST over Hash Table](https://www.geeksforgeeks.org/advantages-of-bst-over-hash-table/) +2. [Ternary Search Tree - space efficient Trie](https://www.geeksforgeeks.org/ternary-search-tree/) +3. Heaps are better at finding Min/Max whereas BSTs are finding specific values +4. Insertion in heaps is O(1) whereas it is O(log(n)) for BST. This is the killer feature of heaps +5. Binary heap creation is O(n) whereas it is O(n log(n)) for BST. + +### Advantages of BST over heap + +1. Search for arbitrary element is O(log(n)) in BST whereas it is O(n) for heap. [Reference](https://stackoverflow.com/questions/6147242/heap-vs-binary-search-tree-bst) + +--- + +## Heap + +### Applications + +1. Graph algorithms like Djikstra +2. Priority queues + +### Misc + +For differences between Heap and BST - [Reference](https://stackoverflow.com/questions/6147242/heap-vs-binary-search-tree-bst) + +[Why is binary heap preferred over BST for priority queue](https://www.geeksforgeeks.org/why-is-binary-heap-preferred-over-bst-for-priority-queue/) + +--- + +## Graph + + +### Difference between Graph and Tree + +| Graphs | Trees | +| --- | --- | --- | +| No unique node called root | Has a unique root node | +| There can be a cycle | No cycle | +| Network model | Hierarchial model | + +### Applications + +1. Networking +2. Social networks to find friends +3. Maps +4. Finding dependencies (Topological Sort) +5. Topological sort - + 1. Finding dependencies + 2. Job scheduling among dependent jobs +6. Kosaraju's algorithm - + 1. To find strongly connected components (which are used in social networks) +7. Bipartite Graph - + 1. Student and class scheduling + 2. Stable marriage + 3. Text analyzer to cluster documents + 4. Netflix movie preference algorithm +8. Depth First Search - + 1. For a **weighted** graph, DFS produces Minimum Spanning Tree + 2. Cycle check + 3. Path finding + 4. Topological Sorting + 5. Finding strongly connected components +9. Breadth First Search - + 1. For a **unweighted** graph, BFS produces Minimum Spanning Tree + 2. Used in BitTorrent to find all neighbour nodes + 3. Crawlers in search engine [Why BFS not DFS is used in web crawlers](https://stackoverflow.com/a/20580936/12360506) + 4. Broadcasting in networking + 5. Garbage collection. Breadth First Search is preferred over Depth First Search because of better locality of reference. + 6. Ford-Fulkerson algorithm +10. Minimum Spanning Tree - + 1. Network design (like connecting all offices in a city with the lowest cost of cables) +11. Articulation Point - + 1. Articulation points represent vulnerabilities in a connected network – single points whose failure would split the network into 2 or more disconnected components. They are useful for designing reliable networks. + +--- + +## Stack + +### Application + +1. Expression handling, infix to postfix etc +2. Function calls +3. Parenthesis checking +4. Backtracking + +--- + +## Queue + +### Application + +1. BFS traversal +2. Handling interrupts in operating system +3. IO Buffers, pipes + +--- + +## Priority Queue + +### Application + +1. Prim's algorithm +2. Data compression. Used in Huffman coding