diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..72c0d14d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +# Description + +Please include a summary of the change and which issue is fixed or what question/feature you have added. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature/question +- [ ] This change requires a documentation update +- [ ] Bookmark link + +# Checklist: + +- [ ] My code follows the style guidelines of this project i.e. [Pep8](https://www.python.org/dev/peps/pep-0008/) +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] Any dependent changes have been merged and published in downstream modules +- [ ] I have squashed unnecessary commits 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 new file mode 100644 index 00000000..7fb881f7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,92 @@ +# Contributing + +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 + +## Contributions Best Practices + +### Commits + +* Write clear meaningful git commit messages (Do read [http://chris.beams.io/posts/git-commit/](http://chris.beams.io/posts/git-commit/)) + +* Make sure your PR's description contains GitHub's special numeric reference to the related issue. + +* While adding questions, please make sure that you add only a single new question per PR as it becomes easy to review and maintain many PRs. + +* When you make minor changes to a PR of yours, make sure you squash your commits afterwards so that you don't have an absurd number of commits for a single PR. (Learn how to squash commits at [https://davidwalsh.name/squash-commits-git](https://davidwalsh.name/squash-commits-git)) + +### 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. + +### Code Styling Guide + +Python follows a Pep8 styling. Styling the code according to it makes it universally appealing and more readable. Also, if all data structures and algorithms are written in a similar style code, then it will be easy for everyone to implement them. So please follow Pep8 styling conventions (most notably, avoid using camelCase and prefer snake_case while naming functions and files). You can find the Pep8 styling guide here [https://www.python.org/dev/peps/pep-0008/](https://www.python.org/dev/peps/pep-0008/) + +### 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. + +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 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. + +* 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. + +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, + +https://github.com/prabhupant/python-ds = upstream +https://github.com/your-username/python-ds = origin + +You will be making all your changes to origin and then file a PR to upstream from there. + +1. Make sure to make a clone of the repository. + +```bash +$ git clone https://github.com/prabhupant/python-ds +``` + +2. Now `cd` into the directory and create a new branch for the issue. Let's call the branch `max-number-in-array` + +```bash +$ git checkout -b max-number-in-array +``` + +3. Make your changes and add and commit them + +```bash +$ git add . +$ git commit -m "Add max number in array" +``` + +4. Now push the branch to your origin + +```bash +$ git push origin max-number-in-array +``` + +5. Now head to your GitHub account and open your fork of python-ds i.e, the origin, and create a PR to the upstream from there. GitHub will automatically detect the upstream for you. + +6. Write a good brief description and refer to the issue (if any) and submit the PR. + +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. + + + diff --git a/README.md b/README.md index 9db3c80c..7c05260f 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,72 @@ -# Python Data Structures +# Python Data Structures and Algorithms -This repository contains data structures and algorithms questions in Python. +No non-sense solutions to common Data Structure and Algorithm interview questions in Python. Follows a consistent approach throughout problems. -## :dart: Objective +## Objective -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: +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. -## :file_folder: Structure of the repository +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 -As of now, the repository contains a file called `useful_links.txt` and 2 main directories: **Data Structures** and **Algorithms**. +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! + +## Structure of the repository + +As of now, the repository contains 3 main directories: [**Bookmarks**](bookmarks), [**Data Structures**](data_structures) and [**Algorithms**](algorithms). ### Data Structures Contains all data structure questions categorised into sub-directories like stack, queue, etc according to their type. - 1. Array - 2. Dictionary - 3. Binary Search Tree - 4. Linked List - 5. Stack - 6. Graphs - 7. Circular Linked List +1. [Array](data_structures/array) +2. [Dictionary]() +3. [Binary Search Tree](data_structures/bst) +4. [Linked List](data_structures/linked_list) +5. [Stack](data_structures/stack) +6. [Graphs](data_structures/graphs) +7. [Circular Linked List](data_structures/circular_linked_list) +8. [Doubly Linked List](data_structures/doubly_linked_list) ### Algorithms -Contains algorithm-based questions like dynamic programming, greedy etc. +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) +3. [Greedy](algorithms/greedy) +4. [Math](algorithms/math) +5. [Misc](algorithms/miscellaneous) +6. [Sorting](algorithms/sorting) +7. [Bit Manipulation](algorithms/bit_manipulation) + +### Bookmarks -## :clipboard: Things need to be done +You can find useful links in this repository in the different markdown files. Below is a table of contents. + +| Category | Link | +| :-- | :--: | +| Articles | [Click Here](bookmarks/articles.md) | +| Books | [Click Here](bookmarks/books.md) | +| Topics | [Click Here](bookmarks/topics.md) | +| Tutorials | [Click Here](bookmarks/tutorials.md) | +| Videos | [Click Here](bookmarks/videos.md) | +| Misc. | [Click Here](bookmarks/misc.md) | + +## 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. +1. Queue questions +2. Algorithms +3. More questions in data structures, especially for graph, circular linked list, trees, heaps and hash. + +## Contributing -## :raised_hand: Contributing +Contributions are always welcomed. +Feel free to raise new issues, file new PRs. Consider giving it a star and fork this repo! -Contributions are always welcomed. :smiley: -Feel free to raise new issues, file new PRs and star and fork this repo! :wink: +To follow the guidelines, refer to [Contributing.md](CONTRIBUTING.md) -Here are some guidelines: +## License - 1. Clone the repo to your local machine - 2. Make the new branch and name it accordingly - 3. File the PR and wait for the review :) +[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/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/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/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/longest_common_subsequence.py b/algorithms/dynamic_programming/longest_common_subsequence.py new file mode 100644 index 00000000..2c60beda --- /dev/null +++ b/algorithms/dynamic_programming/longest_common_subsequence.py @@ -0,0 +1,18 @@ +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] = 1 + t[i-1][j-1] + else: + t[i][j] = max(t[i-1][j], t[i][j-1]) + + max_length = max(max_length, t[i][j]) + + 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_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/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/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/dijkstra_greedy.py b/algorithms/greedy/dijkstra_greedy.py new file mode 100644 index 00000000..32aaba6d --- /dev/null +++ b/algorithms/greedy/dijkstra_greedy.py @@ -0,0 +1,37 @@ +def dijkstra(graph, start, end): + shortest_distance = {} + non_visited_nodes = {} + for i in graph: + non_visited_nodes[i] = graph[i] + + infinit = float('inf') + + for no in non_visited_nodes: + shortest_distance[no] = infinit + shortest_distance[start] = 0 + + while non_visited_nodes != {}: + shortest_extracted_node = None + for i in non_visited_nodes: + if shortest_extracted_node is None: + shortest_extracted_node = i + elif shortest_distance[i] < shortest_distance[shortest_extracted_node]: + shortest_extracted_node = i + + for no_v, Weight in graph[shortest_extracted_node]: + if Weight + shortest_distance[shortest_extracted_node] < shortest_distance[no_v]: + shortest_distance[no_v] = Weight + shortest_distance[shortest_extracted_node] + non_visited_nodes.pop(shortest_extracted_node) + return shortest_distance + +#in this case, I made a graph within the code, but I didn't put here, you can create your graph the way you like. +#this algorithm needs the start, end, and weight, but you can remove the weight as well +#I will leave my example here, how I use this algorithm to solve the shortest path problem +# V is vertex, u is edges and W IS WEIGHT. + +cities, origin, destiny = map(int, input().split()) +graph = {i:[] for i in range(1, cities+1)} +for i in range(cities-1): + u, v, w = map(int, input().split()) + graph[v].append((u, w)) + graph[u].append((v, w)) \ No newline at end of file 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/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 new file mode 100644 index 00000000..a355f32f --- /dev/null +++ b/algorithms/math/fibonacci_number.py @@ -0,0 +1,15 @@ +# Program that prints out the fibonacci sequence until a given number + + +def main(): + 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]) + + print(fib) + + +if __name__ == '__main__': + main() diff --git a/algorithms/math/greatest_common_divisor.py b/algorithms/math/greatest_common_divisor.py new file mode 100644 index 00000000..53e70f1d --- /dev/null +++ b/algorithms/math/greatest_common_divisor.py @@ -0,0 +1,35 @@ +""" +High Level Description: +Given two input integers, find their Greatest Common Divisor. + +Time Complexity: +O(log(n)) +""" +# Iterative Solution +def gcd(x, y): + if x == 0: + return y + if y == 0: + return x + + while x % y != 0: + rem = x % y; + x = y + y = rem + + return y + +# Recursive Solution +def gcd(x, y): + if x == 0: + return y + if y == 0: + return x + + 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 new file mode 100644 index 00000000..d6248d88 --- /dev/null +++ b/algorithms/math/index.md @@ -0,0 +1,10 @@ +# Index of math + +* [Greatest Common Divisor](greatest_common_divisor.py) +* [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/least_common_multiple.py b/algorithms/math/least_common_multiple.py new file mode 100644 index 00000000..4c11c390 --- /dev/null +++ b/algorithms/math/least_common_multiple.py @@ -0,0 +1,8 @@ +""" +Given two input integers, find their Least Common Multiple. +""" + +from greatest_common_divisor import gcd + +def lcm(x, y): + return abs(x * y) // gcd(x, y) diff --git a/algorithms/math/modular_exponentiation.py b/algorithms/math/modular_exponentiation.py new file mode 100644 index 00000000..1d512b45 --- /dev/null +++ b/algorithms/math/modular_exponentiation.py @@ -0,0 +1,24 @@ +# to compute modular power + +# Iterative Function to calculate +# (x^y)%p in O(log y) + +def power(x, y, p) : + res = 1 # Initialize result + + # Update x if it is more + # than or equal to p + x = x % p + + while (y > 0) : + + # If y is odd, multiply + # x with result + if ((y & 1) == 1) : + res = (res * x) % p + + # y must be even now + y = y >> 1 # y = y/2 + x = (x * x) % p + + return res \ No newline at end of file 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/prime.py b/algorithms/math/prime.py new file mode 100644 index 00000000..8dc7143c --- /dev/null +++ b/algorithms/math/prime.py @@ -0,0 +1,16 @@ +def prime(limit): + + count = 1 + while (count < limit): + + flag = 0 + for i in range(3, count, 2): + if (count % i == 0): + flag = 1 + + if (flag == 0): + print(count) + + count += 2 + +prime(100) diff --git a/algorithms/math/recursive_fibonacci.py b/algorithms/math/recursive_fibonacci.py new file mode 100644 index 00000000..eb319cd9 --- /dev/null +++ b/algorithms/math/recursive_fibonacci.py @@ -0,0 +1,33 @@ +""" + 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): + if n == 1: + return 1 + elif n == 0: + return 0 + 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(binary_rec_fib(i)) + +if __name__ == "__main__": + main() diff --git a/algorithms/math/sieve_of_eratosthenes.py b/algorithms/math/sieve_of_eratosthenes.py new file mode 100644 index 00000000..929d17e0 --- /dev/null +++ b/algorithms/math/sieve_of_eratosthenes.py @@ -0,0 +1,18 @@ +""" +Description: +Sieve of Eratosthenes is a very fast method (loglog(n)) of finding primes upto a given number. +""" + + +def sieve(n): + prime_list = [] + for i in range(2, n+1): + if i not in prime_list: + print(i) + for j in range(i*i, n+1, i): + prime_list.append(j) + + +if __name__ == '__main__': + input_number = int(input("Provide a number upto which primes are to be found :")) + sieve(input_number) diff --git a/algorithms/math/sumofdigits.py b/algorithms/math/sumofdigits.py new file mode 100644 index 00000000..8dd9040c --- /dev/null +++ b/algorithms/math/sumofdigits.py @@ -0,0 +1,11 @@ +# 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/index.md b/algorithms/miscellaneous/index.md new file mode 100644 index 00000000..efd1c3ae --- /dev/null +++ b/algorithms/miscellaneous/index.md @@ -0,0 +1,3 @@ +# Index of miscellaneous + +* [Luhn Algorithm](luhn_algorithm.py) \ No newline at end of file diff --git a/algorithms/miscellaneous/luhn_algorithm.py b/algorithms/miscellaneous/luhn_algorithm.py new file mode 100644 index 00000000..e6d4b20d --- /dev/null +++ b/algorithms/miscellaneous/luhn_algorithm.py @@ -0,0 +1,48 @@ +def check_luhn(card_number): + """ + Luhn algorithm or Luhn formula is a simple checksum formula + used to validate a variety of identification numbers, such as + credit card numbers, IMEI numbers, National Provider Identifier numbers + in some of the countries. + + It takes a number as an input + (Assuming cardnumber as a string) + and returns true or false + based upon whether number is valid or not + + :param card_number: + :return: bool: valid or not + + Examples: + + >>> check_luhn("950123440000") + False + >>> check_luhn("490154203237518") + True + """ + card_len = len(card_number) + + check_sum = 0 + + is_parity = False + + for digit in range(card_len - 1, -1, -1): + if is_parity: + cal = int(card_number[digit]) * 2 + else: + cal = int(card_number[digit]) + + if cal > 9: + check_sum += cal - 9 + else: + check_sum += cal + + is_parity = not is_parity + + return check_sum % 10 == 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() 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 new file mode 100644 index 00000000..1c4a24cc --- /dev/null +++ b/algorithms/sorting/bubble_sort.py @@ -0,0 +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 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] + 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/heap_sort.py b/algorithms/sorting/heap_sort.py new file mode 100644 index 00000000..49e30a59 --- /dev/null +++ b/algorithms/sorting/heap_sort.py @@ -0,0 +1,51 @@ +''' +High Level Description: +This algorithm segments the list into sorted and unsorted parts. +It converts the unsorted segment of the list to a Heap data structure, so that we can efficiently determine the largest element. +Time Complexity: +The overall time complexity is O(nlog(n)). +''' + +def heapify(nums, heap_size, root_index): + # Assume the index of the largest element is the root index + largest = root_index + left_child = (2 * root_index) + 1 + right_child = (2 * root_index) + 2 + + # If the left child of the root is a valid index, and the element is greater + # than the current largest element, then update the largest element + if left_child < heap_size and nums[left_child] > nums[largest]: + largest = left_child + + # Do the same for the right child of the root + if right_child < heap_size and nums[right_child] > nums[largest]: + largest = right_child + + # If the largest element is no longer the root element, swap them + if largest != root_index: + nums[root_index], nums[largest] = nums[largest], nums[root_index] + # Heapify the new root element to ensure it's the largest + heapify(nums, heap_size, largest) + + +def heap_sort(nums): + n = len(nums) + + # Create a Max Heap from the list + # The 2nd argument of range means we stop at the element before -1 i.e. + # the first element of the list. + # The 3rd argument of range means we iterate backwards, reducing the count + # of i by 1 + for i in range(n, -1, -1): + heapify(nums, n, i) + + # Move the root of the max heap to the end of + for i in range(n - 1, 0, -1): + nums[i], nums[0] = nums[0], nums[i] + heapify(nums, i, 0) + + +# Verify it works +random_list_of_nums = [35, 12, 43, 8, 51] +heap_sort(random_list_of_nums) +print(random_list_of_nums) \ No newline at end of file diff --git a/algorithms/sorting/index.md b/algorithms/sorting/index.md new file mode 100644 index 00000000..6e83b669 --- /dev/null +++ b/algorithms/sorting/index.md @@ -0,0 +1,7 @@ +# Index of sorting + +* [Insertion Sort](insertion_sort.py) +* [Merge Sort](merge_sort.py) +* [Quick Sort](qsort.py) +* [Select Sort](select_sort.py) +* [Bubble Sort](bubble_sort.py) \ No newline at end of file diff --git a/algorithms/sorting/insertion_sort.py b/algorithms/sorting/insertion_sort.py new file mode 100644 index 00000000..5d0ec752 --- /dev/null +++ b/algorithms/sorting/insertion_sort.py @@ -0,0 +1,25 @@ +""" +High Level Description: +For every element in the given list, find its correct index by iterating +backwards and finding a slot. This forms a sorted array. +Time Complexity: +Every element is visited, which contributes O(n). Swapping backwards takes +O(n/2) time on average, meaning that the total complexity is O(n^2) +""" + +def insertion_sort(lst): + for i in range(1,len(lst)): + while(i > 0 and lst[i] < lst[i - 1]): + lst[i], lst[i - 1] = lst[i - 1], lst[i] + i -= 1 + return lst + +test_data = [5,9,4,27,3,6] +print(insertion_sort(test_data)) + +test_data = ['f','b','z','a','x'] +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 new file mode 100644 index 00000000..8dbf1d37 --- /dev/null +++ b/algorithms/sorting/merge_sort.py @@ -0,0 +1,51 @@ +""" +High level explanation: + +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: + +merge_sort has a time complexity of O(n log n). +""" + +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] + i+=1 + else: + arr[k] = R[j] + j+=1 + k+=1 + + # 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] + j+=1 + k+=1 + +test_array = [10,30,20,100,40,80,90,210,34] + +merge_sort(test_array) + +print(test_array) + +# This code is contributed by Mayank Khanna +# and extented by thanasis mpalatsoukas diff --git a/algorithms/sorting/permutation_sort.py b/algorithms/sorting/permutation_sort.py new file mode 100644 index 00000000..4912ff9e --- /dev/null +++ b/algorithms/sorting/permutation_sort.py @@ -0,0 +1,36 @@ +import random + +# Sorts array a[0..n-1] using Bogo sort + + +def bogo_sort(a): + n = len(a) + while (is_sorted(a) == False): + shuffle(a) + +# To check if array is sorted or not + + +def is_sorted(a): + n = len(a) + for i in range(0, n - 1): + if (a[i] > a[i + 1]): + return False + return True + +# To generate permuatation of the array + + +def shuffle(a): + n = len(a) + for i in range(0, n): + r = random.randint(0, n - 1) + a[i], a[r] = a[r], a[i] + + +# Driver code to test above +a = [3, 2, 4, 1, 0, 5] +bogo_sort(a) +print("Sorted array :") +for i in range(len(a)): + print("%d" % a[i]), diff --git a/algorithms/sorting/pigeonhole_sort.py b/algorithms/sorting/pigeonhole_sort.py new file mode 100644 index 00000000..5e2777b1 --- /dev/null +++ b/algorithms/sorting/pigeonhole_sort.py @@ -0,0 +1,36 @@ +#Pigeonhole sorting is a sorting algorithm that is suitable for sorting lists of elements where the number of elements and the number of possible key values are approximately the same. +#It requires O(n + Range) time where n is number of elements in input array and ‘Range’ is number of possible values in array. + +def pigeonhole_sort(a): + my_min = min(a) + my_max = max(a) + size = my_max - my_min + 1 + + # our list of pigeonholes + + + holes = [0] * size + + # Populate the pigeonholes. + + + for x in a: + assert type(x) is int, "integers only please" + holes[x - my_min] += 1 + + # Put the elements back into the array in order. + + + i = 0 + for count in range(size): + while holes[count] > 0: + holes[count] -= 1 + a[i] = count + my_min + i += 1 + + +a = [8, 3, 2, 7, 4, 6, 8] +print("Sorted order is : ", end = ' ') +pigeonhole_sort(a) +for i in range(0, len(a)): + print(a[i], end = ' ') diff --git a/algorithms/sorting/qsort.py b/algorithms/sorting/qsort.py new file mode 100644 index 00000000..395955a0 --- /dev/null +++ b/algorithms/sorting/qsort.py @@ -0,0 +1,27 @@ +""" +High level explanation: +Quicksort algorithm is that if we can efficiently partition a list, +then we can efficiently sort it. Partitioning a list means that +we pick a pivot item in the list, and then modify the list +to move all items larger than the pivot to the right and all +smaller items to the left. + +Once the pivot is done, we can do the same operation to the +left and right sections of the list recursively until the list is sorted. + +Time complexity: +Quicksort has a time complexity of O(n log n). +""" + + +def qsort(arr): + if len(arr) <= 1: + return arr + pivot = arr.pop() + greater, lesser = [], [] + for item in arr: + if item > pivot: + greater.append(item) + else: + lesser.append(item) + return qsort(lesser) + [pivot] + qsort(greater) diff --git a/algorithms/sorting/select_sort.py b/algorithms/sorting/select_sort.py new file mode 100644 index 00000000..8ba2cad7 --- /dev/null +++ b/algorithms/sorting/select_sort.py @@ -0,0 +1,26 @@ + +def find_smallest(arr): + smallest = arr[0] + smallest_index = 0 + + for i in range(1, len(arr)): + + if arr[i] < smallest: + smallest = arr[i] + smallest_index = i + return smallest_index + + + +def selection_sort(arr): + new_arr = [] + + for i in range(len(arr)): + smallest = find_smallest(arr) + new_arr.append(arr.pop(smallest)) + + return new_arr + +array = [100, 5, 72, 41, 80, 1, 99, 36, 27, 78] + +print(selection_sort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] diff --git a/algorithms/sorting/shell_sort.py b/algorithms/sorting/shell_sort.py new file mode 100644 index 00000000..27269ec7 --- /dev/null +++ b/algorithms/sorting/shell_sort.py @@ -0,0 +1,40 @@ +''' +Time complexity of shell_sort is O(n2). +In the above implementation gap is reduce by half in every iteration. +here are many other ways to reduce gap which lead to better time complexity. +See this for more details. +''' + +def shell_sort(arr): + + # Start with a big gap, then reduce the gap + n = len(arr) + gap = int(n / 2) + + # Do a gapped insertion sort for this gap size. + # The first gap elements a[0..gap-1] are already in gapped + # order keep adding one more element until the entire array + # is gap sorted + while gap > 0: + + for i in range(gap, n): + + # add a[i] to the elements that have been gap sorted + # save a[i] in temp and make a hole at position i + temp = arr[i] + + # shift earlier gap-sorted elements up until the correct + # location for a[i] is found + j = i + while j >= gap and arr[j - gap] > temp: + arr[j] = arr[j - gap] + j -= gap + + # put temp (the original a[i]) in its correct location + arr[j] = temp + gap = int(gap / 2) + +arr = [ 12, 34, 54, 2, 3] + +shell_sort(arr) +print(arr) \ No newline at end of file diff --git a/useful_links.md b/bookmarks/articles.md similarity index 56% rename from useful_links.md rename to bookmarks/articles.md index 3058ba55..ccdd64e2 100644 --- a/useful_links.md +++ b/bookmarks/articles.md @@ -1,39 +1,19 @@ -# Useful links +# Articles -This is a list of articles, tutorials, questions and videos that may be useful for algorithms and data structures learning. +This is a list of articles that may be useful for algorithms and data structures learning. -## Questions +## Links: -- https://stackoverflow.com/questions/35390533/actual-difference-between-data-compression-and-data-deduplication +- https://www.topcoder.com/community/competitive-programming/tutorials/dynamic-programming-from-novice-to-advanced/ -- https://stackoverflow.com/questions/40200413/sessions-vs-token-based-authentication - -- https://stackoverflow.com/questions/15678406/when-to-use-myisam-and-innodb - -## Tutorials - -- https://cstack.github.io/db_tutorial/ - -- https://github.com/eon01/kubernetes-workshop - -## Videos - -- https://www.youtube.com/watch?v=mSzUb7f47qk - -## Topics - -- https://en.wikipedia.org/wiki/Data_deduplication - -- https://searchstorage.techtarget.com/definition/data-deduplication - -- https://docs.gitlab.com/ce/development/architecture.html - -## Articles +- https://www.hackerearth.com/practice/notes/getting-started-with-the-sport-of-programming/ - https://github.com/alex/what-happens-when - https://medium.com/@maneesha.wijesinghe1/what-happens-when-you-type-an-url-in-the-browser-and-press-enter-bb0aa2449c1a +- https://blog.sucuri.net/2018/10/owasp-top-10-security-risks-part-i.html + - https://www.cloudflare.com/learning/dns/what-is-dns/ - https://www.keycdn.com/blog/difference-between-http-and-https @@ -62,15 +42,20 @@ This is a list of articles, tutorials, questions and videos that may be useful f - https://hacktoberfestswaglist.com/#least-involvement-to-most-involvement +- https://www.quora.com/How-is-VPN-different-from-proxy + +- https://skerritt.blog/dynamic-programming/ + +- https://cs.nyu.edu/courses/fall17/CSCI-UA.0102-001/Notes/LinearSort.html -## Others +- https://martinfowler.com/articles/serverless.html -- https://google.github.io/eng-practices/review/reviewer/ +- https://samnewman.io/patterns/architectural/bff/ -- https://www.preining.info +- https://www.ultravioletsoftware.com/single-post/2017/03/23/An-introduction-into-the-WSGI-ecosystem -- https://cheatsheetseries.owasp.org +- https://jwt.io/introduction/ -- https://wsvincent.com/ +- https://khashtamov.com/en/how-to-become-a-data-engineer/ -- https://machinelearningmastery.com \ No newline at end of file +- https://blog.mirrorfly.com/xmpp-vs-websockets-instant-messaging-protocol-comparison/ diff --git a/bookmarks/books.md b/bookmarks/books.md new file mode 100644 index 00000000..88b69d20 --- /dev/null +++ b/bookmarks/books.md @@ -0,0 +1,10 @@ +# Books + +This is a list of links to books that may be useful for algorithms and data structures learning. + +## Links: + +- https://drive.google.com/open?id=1d23_sJdK8XPBJEi-4oUrMnrYMhU2fDcI + +- https://drive.google.com/open?id=1sBSGJ8gUakcn0NuQam4BRhRCQfsTrrwh + 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 new file mode 100644 index 00000000..2afa94e6 --- /dev/null +++ b/bookmarks/misc.md @@ -0,0 +1,21 @@ +# Miscellaneous + +This is a list of misc links that may be useful when learning or researching data structures and algorithms. + +## Links: + +- https://google.github.io/eng-practices/review/reviewer/ + +- https://www.preining.info + +- https://cheatsheetseries.owasp.org + +- 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 new file mode 100644 index 00000000..d1d54010 --- /dev/null +++ b/bookmarks/topics.md @@ -0,0 +1,19 @@ +# Topics + +This is a list of links to topics that may be helpful in learning or researching data structures and algorithms. + +## Links: + +- https://en.wikipedia.org/wiki/Data_deduplication + +- https://searchstorage.techtarget.com/definition/data-deduplication + +- https://docs.gitlab.com/ce/development/architecture.html + +- https://stackoverflow.com/questions/35390533/actual-difference-between-data-compression-and-data-deduplication + +- https://stackoverflow.com/questions/40200413/sessions-vs-token-based-authentication + +- https://stackoverflow.com/questions/15678406/when-to-use-myisam-and-innodb + +- https://www.bigocheatsheet.com/ diff --git a/bookmarks/tutorials.md b/bookmarks/tutorials.md new file mode 100644 index 00000000..3f4c4bb1 --- /dev/null +++ b/bookmarks/tutorials.md @@ -0,0 +1,15 @@ +# Tutorials + +This is a list of links to tutorials that may be helpful in learning or researching data structures and algorithms. + +## Links: + +- https://cstack.github.io/db_tutorial/ + +- https://github.com/eon01/kubernetes-workshop + +- http://cp-algorithms.com/ + +- https://realpython.com/python-data-classes/ + +- https://chrisalbon.com 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 43a598e6..4838b1aa 100644 --- a/data_structures/array/duplicate.py +++ b/data_structures/array/duplicate.py @@ -1,3 +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] @@ -8,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/dutch_flag_problem.py b/data_structures/array/dutch_flag_problem.py index f898486c..e2e9b596 100644 --- a/data_structures/array/dutch_flag_problem.py +++ b/data_structures/array/dutch_flag_problem.py @@ -1,3 +1,8 @@ +# 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] + def dutch(arr): low = 0 mid = 0 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 new file mode 100644 index 00000000..89477411 --- /dev/null +++ b/data_structures/array/first_repeating_char.py @@ -0,0 +1,19 @@ +# 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 + +# Without extra space idea - +# The idea is to use an integer variable and uses bits in its binary representation to store whether a +# character is present or not. +# Typically an integer has at-least 32 bits and we need to store presence/absence of only 26 characters. + +def first_recurrence(s): + checker = 0 + pos = 0 + for i in s: + val = ord(i) - ord('a') + if (checker & (1 << val) > 0): + return i + checker = checker | (1 << val) + pos += 1 + return -1 diff --git a/data_structures/array/flatten_array.py b/data_structures/array/flatten_array.py new file mode 100644 index 00000000..6be1af3b --- /dev/null +++ b/data_structures/array/flatten_array.py @@ -0,0 +1,13 @@ +# Input - [1,2,3,4,[5,6,7,8],[9,10]] +# Output - [1,2,3,4,5,6,7,8,9,10] + +lst = [1,2,3,4,[5,6,7,8],[9,10]] + +flat = [] + +for sub in lst: + if isinstance(sub, list): + flat.extend(sub) + else: + flat.append(sub) + diff --git a/data_structures/array/index.md b/data_structures/array/index.md new file mode 100644 index 00000000..c549554c --- /dev/null +++ b/data_structures/array/index.md @@ -0,0 +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) + +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) + +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) + +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) + +Find out maximium sum of any three elements for a given array + +* [Square of Sorted Array](square_of_sorted_array.py) + +For a given array return an array that contains square of elements. + +* [All Numbers Divisible](all_numbers_divisible.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) + +### 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) + +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) + + 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/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/product_of_array_except_self.py b/data_structures/array/product_of_array_except_self.py index 8db6aac1..3ab5bf0c 100644 --- a/data_structures/array/product_of_array_except_self.py +++ b/data_structures/array/product_of_array_except_self.py @@ -12,3 +12,5 @@ def product(arr): for i in reversed(range(len(arr))): prods[i] = prods[i] * temp temp = temp * arr[i] + + return temp diff --git a/data_structures/array/quick_sort.py b/data_structures/array/quick_sort.py new file mode 100644 index 00000000..ddc87147 --- /dev/null +++ b/data_structures/array/quick_sort.py @@ -0,0 +1,23 @@ +""" +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) +""" + +# quicksort function +def quicksort(array): + if len(array) < 2: + return array + else: + pivot = array[0] + less = [i for i in array[1:] if i <= pivot] + greater = [i for i in array[1:] if i > pivot] + return quicksort(less) + [pivot] + quicksort(greater) + +array = [100, 5, 72, 41, 80, 1, 99, 36, 27, 78] +print(quicksort(array)) # [1, 5, 27, 36, 41, 72, 78, 80, 99, 100] + 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/rotation_simplified.py b/data_structures/array/rotation_simplified.py new file mode 100644 index 00000000..554b18d5 --- /dev/null +++ b/data_structures/array/rotation_simplified.py @@ -0,0 +1,5 @@ +def rotate(arr, d): + return arr[d:]+arr[:d] + +arr = [1,2,3,4,5] +print(rotate(arr, 1)) 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/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_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/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/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/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 new file mode 100644 index 00000000..334a3dd6 --- /dev/null +++ b/data_structures/bst/second_largest_in_bst.py @@ -0,0 +1,66 @@ +""" +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): + self.val = val + self.left = None + self.right = None + + +def find_largest(root): + curr = root + while curr: + if not curr.right: + return curr.val + curr = curr.right + + +def second_largest(root): + if not root or (not root.left and not root.right): + return "BST should have atleast 2 nodes" + + curr = root + + while curr: + if curr.left and not curr.right: + return find_largest(curr.left) + + if curr.right and not curr.right.left and not curr.right.right: + return curr.val + + curr = curr.right + + +def insert(root, key): + if root == None: + return Node(key) + if key < root.val: + root.left = insert(root.left, key) + elif key > root.val: + root.right = insert(root.right, key) + return root + + +if __name__ == '__main__': + root = Node(6) + insert(root, 5) + insert(root, 3) + insert(root, 10) + insert(root, 4) + insert(root, 11) + insert(root, 14) + insert(root, 1) + + print(second_largest(root)) + + 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/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 new file mode 100644 index 00000000..9feb64b0 --- /dev/null +++ b/data_structures/doubly_linked_list/doubly_linked_list.py @@ -0,0 +1,75 @@ +class Node: + def __init__(self, data = None, next = None, previous = None): + self.data = data + self.next = next + self.previous = previous + +class Lista: + + def __init__(self): + self.first = self.last = Node() + + def empty(self): + return self.first.data == self.last.data + + + def search(self, data): + if self.empty(): return None + + auxiliar = self.first.next + while auxiliar.next != None and auxiliar.data != data: + auxiliar = auxiliar.next + + if auxiliar.data == data: + return auxiliar.data + + return None + + def append(self, data): + self.last.next = Node(data = data, next = None, previous = self.last) + self.last = self.last.next + + + def __str__(self): + + if self.empty(): + return "" + + aux = self.first + format_ = "" + + while aux.next != None: + if aux.data!= None: + format_ += str(aux.data) + " " + aux = aux.next + format_ += str(aux.data) + "" + + return format_ + + def remove(self, data): + if self.empty(): return None + + auxiliar = self.first.next + + while auxiliar != None and auxiliar.data != data: + auxiliar = auxiliar.next + + if auxiliar == None: return None + else: + item = auxiliar.data + + if auxiliar.previous != None: + auxiliar.previous.next = auxiliar.next + if auxiliar.next != None: + auxiliar.next.previous = auxiliar.previous + + if self.empty(): self.last = self.first = Node() + elif auxiliar.next == None: self.last = auxiliar.previous + + del auxiliar + return item + + + + #the auxiliar variable is to help like a flag in the code, like aux too + #If something isn't right, or in another language it is bc I am a native portuguese speaker, so I translated my code diff --git a/data_structures/fenwick_tree/fenwick_tree.py b/data_structures/fenwick_tree/fenwick_tree.py new file mode 100644 index 00000000..94d42224 --- /dev/null +++ b/data_structures/fenwick_tree/fenwick_tree.py @@ -0,0 +1,54 @@ + + +# Returns the right most significant bit of an integer. +# For example, 12 is represented by 1100 in binary, so it's LSB is the third position, 4. +def LSB(n): + return n & -n + + +class FenwickTree: + ''' + A Fenwick Tree allows you to sum a range of elements and update an element in an array in O(log(n)). A segment tree + can also achieve this, but it takes twice the memory, making it less memory efficient. On the other hand, a segment tree + can compute more functions in a range of elements, like the minimum or maximum. + The way it works is that given an index (note that we start with 1 and not 0), the element in the array will be the sum + of the LSB(index) elements below (including index). For example: with index 3, 3 = 11 in binary, and LSB(3) = 1, so this + element is equal to the corresponding element in the original array. But 4 = 100 in binary, so LSB(4) = 4, so it is equal + to the sum of A[0] + A[1] + A[2] + A[3] (the first 4 elements of the original array). + ''' + + def __init__(self, array): + self.len = len(array)+1 + # Initialize an array of length n+1 to 0 + self.arr = [0] * self.len + # Update the array to the one passed in the parameters + for i in range(0, self.len - 1): + self.update(i, array[i]) + + def update(self, index, val): + j = index+1 + # Update the value for every element that sums A[index] + while j < self.len: + self.arr[j] += val + j += LSB(j) + + # Get the sum of all elements before index (included) + def get_sum(self, index): + sum = 0 + index += 1 + while index > 0: + sum += self.arr[index] + index -= LSB(index) + return sum + + # Get the sum of all elements in the range [start, end] + def get_sum_range(self, start, end): + return self.get_sum(end) - self.get_sum(start-1) + + +if __name__ == '__main__': + arr = [1, 2, 3, 4, 5, 6, 7, 8] + fw = FenwickTree(arr) + print(fw.get_sum(3)) + print(fw.get_sum(5)) + print(fw.get_sum_range(1, 7)) diff --git a/data_structures/graphs/adjeceny_list.py b/data_structures/graphs/adjacency_list.py similarity index 91% rename from data_structures/graphs/adjeceny_list.py rename to data_structures/graphs/adjacency_list.py index 1159430e..6fe60466 100644 --- a/data_structures/graphs/adjeceny_list.py +++ b/data_structures/graphs/adjacency_list.py @@ -17,7 +17,7 @@ def add_edge(self, src, dest): node = AdjNode(dest) node.next = self.graph[src] self.graph[src] = node - + # Adding source node to the destination as it is an # undirected graph node = AdjNode(src) @@ -27,7 +27,7 @@ def add_edge(self, src, dest): def print_graph(self): for i in range(self.V): - print("Adjency list of vertex {}\n head".format(i), end="") + print("Adjacency list of vertex {}\n head".format(i), end="") temp = self.graph[i] while temp: print(" -> {}".format(temp.vertex), end="") 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/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 new file mode 100644 index 00000000..47a0074e --- /dev/null +++ b/data_structures/linked_list/index.md @@ -0,0 +1,12 @@ +# Index of linked list + +* [Remove](remove.py) +* [Pair Swap](pair_swap.py) +* [Reverse](reverse.py) +* [Linked List](linked_list.py) +* [Cycle Detection](cycle_detection.py) +* [Remove nth Node from End](remove_nth_node_from_end.py) +* [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 f525d0ab..11220bd8 100644 --- a/data_structures/linked_list/merge_linked_list.py +++ b/data_structures/linked_list/merge_linked_list.py @@ -6,13 +6,29 @@ def __init__(self, val): def merge(l1, l2): - res = curr = Node(0) - while l1 and l2: - if l1.val < l2.val: - curr.next = l1 - l1 = l1.next - else: - curr.next = l2 - l2 = l2.next - curr = curr.next - curr.next = + if not l1 and not l2: + return + elif not l2: + return l1 + elif not l1: + return l2 + + if (l1.val < l2.val): + l1.next = merge(l1.next, l2) + return l1 + l2.next = merge(l1, l2.next) + return l2 + +head1 = Node(1) +head1.next = Node(3) + +head2 = Node(2) +head2.next = Node(4) + +head_merged = merge(head1,head2) + +ptr = head_merged +while ptr: + print(ptr.val) + ptr = ptr.next + diff --git a/data_structures/linked_list/merge_list_of_linked_lists.py b/data_structures/linked_list/merge_list_of_linked_lists.py new file mode 100644 index 00000000..de99e07e --- /dev/null +++ b/data_structures/linked_list/merge_list_of_linked_lists.py @@ -0,0 +1,37 @@ +class Node: + + def __init__(self, x): + self.val = x + self.next = None + + +def merge_two_lists(l1, l2): + if not l1 and not l2: + return + elif not l2: + return l1 + elif not l1: + return l2 + + if (l1.val < l2.val): + l1.next = merge_two_lists(l1.next, l2) + return l1 + l2.next = merge_two_lists(l1, l2.next) + return l2 + + +def merge_k_lists(lists): + length = len(lists) + if length == 0: + return; + elif length == 1: + return lists[0] + elif length == 2: + return merge_two_lists(lists[0], lists[1]) + + mid = length // 2 + + left_half = lists[:mid] + right_half = lists[mid:] + + return merge_two_lists(merge_k_lists(left_half), merge_k_lists(right_half)) 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/queue.py b/data_structures/queue/queue.py new file mode 100644 index 00000000..a02ae3ab --- /dev/null +++ b/data_structures/queue/queue.py @@ -0,0 +1,32 @@ +class Queue: + """ + Queue is an abstract data structure, somewhat similar to Stacks. + Unlike stacks, a queue is open at both its ends. One end is always used to insert data (enqueue) + and the other is used to remove data (dequeue). + Queue follows First-In-First-Out methodology, i.e., the data item stored first will be accessed first. + + """ + + def __init__(self): + self.entries = [] + self.length = 0 + self.front = 0 + + def put(self, item): + self.entries.append(item) + self.length += 1 + + def get(self): + if self.length <= 0: + return + self.length -= 1 + de_queued = self.entries[self.front] + self.entries = self.entries[1:] + return de_queued + + def rotate(self, rotation): + for i in range(rotation): + self.put(self.get()) + + def size(self): + return self.length diff --git a/data_structures/segment_tree/index.md b/data_structures/segment_tree/index.md new file mode 100644 index 00000000..712399ee --- /dev/null +++ b/data_structures/segment_tree/index.md @@ -0,0 +1,3 @@ +# Index of segment_tree + +* min_in_range_queries.py diff --git a/data_structures/segment_tree/min_in_range_queries.py b/data_structures/segment_tree/min_in_range_queries.py new file mode 100644 index 00000000..1992d4c9 --- /dev/null +++ b/data_structures/segment_tree/min_in_range_queries.py @@ -0,0 +1,107 @@ +''' +Question: +Given an array A of size N, there are two types of queries on this array. +1. q l r: In this query you need to print the minimum in the sub-array . +2. u l r v In this query you need to update the range from l to r by v. + +Time Complexity: +update query: log(n) +min in range query: log(n) +''' + +import math as mt + +class SegmentTree: + def __init__(self, array): + self.MAX_INT = 2**31-1 + self.src = array + self.len = len(array) + self.total_nodes = (2**mt.ceil(mt.log(self.len, 2))*2-1) + self.tree = [0] * self.total_nodes + self.dues = [0] * self.total_nodes + + def build(self): + self.build_tree(l=0, r=self.len-1, par=0) + + def build_tree(self, l, r, par): + if l == r: + self.tree[par] = self.src[l] + return + div = (l+r)//2 + self.build_tree(l, div, (par<<1)+1) + self.build_tree(div+1, r, (par<<1)+2) + self.tree[par]=min(self.tree[(par<<1)+1], self.tree[(par<<1)+2]) + + def min_in_range(self, low, high): + return self.min_(low, high, 0, self.len-1, 0) + + def min_(self, low, high, left, right, par): + if low > right or high < left: + return self.MAX_INT + + if self.dues[par] > 0: + self.tree[par] += self.dues[par] + if left is not right: + self.dues[(par<<1)+1] += self.dues[par] + self.dues[(par<<1)+2] += self.dues[par] + self.dues[par]=0; + + if low <= left and high >= right: + return self.tree[par] + + div = int((right+left)/2) + + return min(self.min_(low, high, left, div, (par<<1)+1), + self.min_(low, high, div+1, right, (par<<1)+2) ) + + def update_range(self, low, high, value): + return self.update(low, high, value, 0, self.len-1, 0) + + def update(self, low, high, value, left, right, par): + if self.dues[par] > 0: + self.tree[par] += self.dues[par] + if left is not right: + self.dues[(par<<1)+1] += self.dues[par] + self.dues[(par<<1)+2] += self.dues[par] + self.dues[par]=0; + + if low > right or high < left: + return; + + if low <= left and high >= right: + self.tree[par] += value + if(left is not right): + self.dues[(par<<1)+1] += value + self.dues[(par<<1)+2] += value + return + + div = (right+left)//2 + self.update(low, high, value, left, div, (par<<1)+1) + self.update(low, high, value, div+1, right, (par<<1)+2) + + +if __name__=='__main__': + + array = list(map(int, input('Enter space seprated number\n').strip().split(' '))) + sg = SegmentTree(array) + sg.build() + + n_query = int(input('Enter number of queries\n')) + print('Query format\nq low high (to find minimum in range e.g: q 0 3)\nu low high value (to add "value" to all elements e.g: u 3 4 99)') + + while n_query: + n_query -= 1 + args = input('Enter query: ').strip().split(' ') + if args[0] == 'q' and len(args) == 3: + print(sg.min_in_range(low=int(args[1]), high=int(args[2]))) + elif args[0] == 'u' and len(args) == 4: + sg.update_range(low=int(args[1]), high=int(args[2]), value=int(args[3])) + print('Update Successfully!') + else: + print('Invalid query format') + n_query += 1 + + + + + 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 new file mode 100644 index 00000000..60e3c506 --- /dev/null +++ b/data_structures/stack/balanced_expression.py @@ -0,0 +1,30 @@ +stack = [] +def checkBalanced(expr): + for i in expr: + if i == "{" or i == "[" or i == "(": + stack.append(i) + elif i == "}" or i == "]" or i == ")": + if not stack: + return False + top = stack.pop() + if i == "}" and top != "{": + return False + elif i == "]" and top != "[": + return False + elif i == ")" and top != "(": + return False + else: + print("Invalid Expression") + return False + + if not len(stack): + return True + else: + return False + +# main function +expr = input() +if not checkBalanced(expr): + print("Not Balanced") +else: + print('Balanced') diff --git a/data_structures/stack/index.md b/data_structures/stack/index.md new file mode 100644 index 00000000..1d7bdf6a --- /dev/null +++ b/data_structures/stack/index.md @@ -0,0 +1,6 @@ +# Index of stack + +* [Valid Parentheses](valid_parenthesis.py) +* [Largest Rectangle Area in a Histogram](largest_rectangle_area_in_histogram.py) +* [Remove Duplicates Adjacent](remove_duplicates_adjacent.py) +* [Validate Stack Sequence](validate_stack_sequence.py) diff --git a/data_structures/stack/largest_rectangle_area_in_histogram.py b/data_structures/stack/largest_rectangle_area_in_histogram.py new file mode 100644 index 00000000..6aa55947 --- /dev/null +++ b/data_structures/stack/largest_rectangle_area_in_histogram.py @@ -0,0 +1,36 @@ +''' +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. +''' + +def max_area_histogram(histogram): + + stack = list() + + max_area = 0 # Initialize max area + + index = 0 + + 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)) + 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)) + + 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)) diff --git a/data_structures/stack/stack_using_linked_list.py b/data_structures/stack/stack_using_linked_list.py new file mode 100644 index 00000000..794541b5 --- /dev/null +++ b/data_structures/stack/stack_using_linked_list.py @@ -0,0 +1,70 @@ +""" Use LinkedList class to implement a Stack. """ + +class Element(object): + def __init__(self, value): + self.value = value + self.next = None + +class LinkedList(object): + def __init__(self, head=None): + self.head = head + + def append(self, new_element): + current = self.head + if self.head: + while current.next: + current = current.next + current.next = new_element + else: + self.head = new_element + + def insert_first(self, new_element): + "Insert new element as the head of the LinkedList" + # fetch the current head + current = self.head + new_element.next = current + self.head = new_element + + def delete_first(self): + "Delete the first (head) element in the LinkedList and return it" + current = self.head + if current: + if current.next: + self.head = current.next + else: + self.head = None + return current + else: + return None + +class Stack(object): + def __init__(self,top=None): + self.ll = LinkedList(top) + + def push(self, new_element): + "Push (add) a new element onto the top of the stack" + self.ll.insert_first(new_element) + + def pop(self): + "Pop (remove) the first element off the top of the stack and return it" + return self.ll.delete_first() + +# Test cases +# Set up some Elements +e1 = Element(1) +e2 = Element(2) +e3 = Element(3) +e4 = Element(4) + +# Start setting up a Stack +stack = Stack(e1) + +# Test stack functionality +stack.push(e2) +stack.push(e3) +print(stack.pop().value) +print(stack.pop().value) +print(stack.pop().value) +print(stack.pop()) +stack.push(e4) +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/adjacent_vowel_pairs.py b/data_structures/strings/adjacent_vowel_pairs.py new file mode 100644 index 00000000..e028b46c --- /dev/null +++ b/data_structures/strings/adjacent_vowel_pairs.py @@ -0,0 +1,34 @@ +''' +Question : +Given a string consisting of English alphabets, the task is to count the number of adjacent pairs of vowels. +For example : +Input: str = “abaebio” +Output: 2 +(a, e) and (i, o) are the only valid pairs. +Source:A very Common Interview Question. + +Time Complexity : The goal is to complete this question in O(n). +''' + +#function to check whether a character is vowel or not +def is_vowel(character): + if character.lower() in ['a', 'e', 'i', 'o', 'u']: + return True + else: + return False + + +#function to find the number of adjacent vowel pairs. +def adjacent_pairs(string): + string=string.lower() + n=len(string) + count = 0 + for i in range(0,n): + if ((is_vowel(string[i]) and is_vowel(string[i + 1]))): + count += 1 + return count + +#driver code +string=input("enter string") +print (adjacent_pairs(string),"is the number of adjacent pairs of vowels") + diff --git a/data_structures/strings/are_anagrams.py b/data_structures/strings/are_anagrams.py new file mode 100644 index 00000000..7eb7dbc1 --- /dev/null +++ b/data_structures/strings/are_anagrams.py @@ -0,0 +1,39 @@ +""" +Question: +Check whether two strings are anagram of each other: +Write a function to check whether two given strings are anagram of each other or not. An anagram of a string is another string that contains same characters, only the order of characters can be different. For example, “abcd” and “dabc” are anagram of each other. +Source:A very Common Interview Question,asked in companies like Amazon,Goldman Sachs and Nagarro. + +Time Complexity: +The goal is to complete this question in O(n). +This solution is optimized by using bit manipulation. If we start at a value of 0 and XOR all the characters of both strings, we should return an end value of 0 if they are anagrams because there would be an even occurrence of all characters in the anagram. + +Space Complexity: +The space complexity of this approach is O(1). +""" +# Function to check whether two strings are anagrams of each other +def are_anagrams(string1, string2): + + # If two strings have different size we return False as they cannot be anagrams of each other + if (len(string1) != len(string2)): + return False + # Variable to store the Xor Value + xor_value = 0 + for i in range(len(string1)): + xor_value = xor_value ^ ord(string1[i]) + xor_value = xor_value ^ ord(string2[i]) + + if(xor_value==0): + return True + else: + return False + +# Code To test The Function +string1 = "thestringsareanagrams" +string2 = "arethestringsanagrams" +if(are_anagrams(string1, string2)): + print("The two strings are anagram of each other") +else: + print("The two strings are not anagram of each other") + + 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/check_permutations.py b/data_structures/strings/check_permutations.py new file mode 100644 index 00000000..83ecd6e9 --- /dev/null +++ b/data_structures/strings/check_permutations.py @@ -0,0 +1,56 @@ +""" +Question: +Check Permutation: Given two strings, write a method to decide if +one is a permutation of the other. +Source: Cracking the Code Interview 6th Edition Question 1.2 + +Time Complexity: +Every letter must be looped which means O(n) time complexity. Then, +we must check if each letter is in the dictionary which is another +O(n) time complexity. Overall, the total time complexity is O(n^2). +""" + +letter_counts = {} + + +def check_permutations(word1, word2): + # Case 1: Not matching length + if len(word1) != len(word2): + return False + # Case 2: Both strings have a length of zero + if len(word1) == 0 and len(word2) == 0: + return True + # Case 3: One Letter Strings + if len(word1) == 1 and len(word2) == 1: + return word1[0] == word2[0] + # Case 4: Length greater than 1 for both strings and lengths are equal + else: + populate_letter_count(word1) + # Loop through each letter (looping is an O(n) operation) + for letter in word2: + # Check if it the letter is in the dictionary (checking is O(n) operation) + if letter_counts.get(letter) is not None: + curr_count = letter_counts.get(letter) + if curr_count == 1: + letter_counts.pop(letter) + else: + letter_counts[letter] = curr_count - 1 + else: + return False + return True + + +def populate_letter_count(word1): + # Loop through each letter (looping is an O(n) operation) + for letter in word1: + # Check if it the letter is in the dictionary (checking is O(n) operation) + if letter_counts.get(letter) is None: + letter_counts[letter] = 1 + else: + curr_count = letter_counts.get(letter) + 1 + letter_counts[letter] = curr_count + +######################################################### +# additional solution by sorting the letters of the words +def check_permutation2(word1, word2): + sorted(word1) == sorted(word2) diff --git a/data_structures/strings/finding_all_substrings.py b/data_structures/strings/finding_all_substrings.py new file mode 100644 index 00000000..acbae381 --- /dev/null +++ b/data_structures/strings/finding_all_substrings.py @@ -0,0 +1,26 @@ +''' +Question : Print and Count all the substrings of a given string. +Sample Input : hello +Sample Output : ['h', 'he', 'hel', 'hell', 'hello', 'e', 'el', 'ell', 'ello', 'l', 'll', 'llo', 'l', 'lo', 'o'] +15 +Source : Hackerrank''' + +def finding_substrings(string): + + # Get all substrings of string and store them in an empty list + list =[] + count=0 + for i in range(len(string)): + for j in range(i+1,(len(string)+1)): + list.append(string[i:j]) + + count=count+1 + + # printing result + print(list) + print(count) + +#driver code +string = input("enter string: ") +finding_substrings(string) + diff --git a/data_structures/strings/index.md b/data_structures/strings/index.md new file mode 100644 index 00000000..df56b743 --- /dev/null +++ b/data_structures/strings/index.md @@ -0,0 +1,7 @@ +# Index of strings + +* [Check Permutations](check_permutations.py) +* [Are Anagrams](are_anagrams.py) +* [Adjacent Vowel Pairs](adjacent_vowel_pairs.py) +* [Toggle String](toggle_string.py) +* [Is Rotate String](rotate_string.py) diff --git a/data_structures/strings/one_away.py b/data_structures/strings/one_away.py new file mode 100644 index 00000000..4ea42273 --- /dev/null +++ b/data_structures/strings/one_away.py @@ -0,0 +1,62 @@ +""" +Question: +One Away: There are three types of edits that can be performed on +strings: insert a character, remove a character, or replace a +character. Given two strings, write a function to check if they +are one edit (or zero edits) away. + +Example: +pale, ple -> true +pales, pale -> true +pale, bale -> true +pale, bake -> false + +Source: Cracking the Code Interview 6th Edition Question 1.5 + +Time Complexity: +We are going through both strings at the same time and stopping when +more than one letter is different, which means O(n) time complexity +on the while loop. No extra space is required. +""" + +def is_one_away(str1, str2): + edit_counter = 0 + i = 0 # str1 index + j = 0 # str2 index + + # Size difference must be less than 1 + if abs(len(str1) - len(str2)) > 1: + return False + + # Compare strings while counting edits + # If letters differ, update counter and compare next letter + # In this case, if strings have different sizes increment only index of the longest + # Otherwise increment both indexes + while i < len(str1) and j < len(str2): + if str1[i] != str2[j]: + # Only one edit is allowed + if edit_counter > 0: + return False + edit_counter += 1 + + if len(str1) > len(str2): + i += 1 + continue + elif len(str1) < len(str2): + j += 1 + continue + i += 1 + j += 1 + + # If one string finished before the other, we will certainly + # have one more edit to consider (adding the last letter), so + # we must check if the edit counter is still empty + if (i < len(str1) or j < len(str2)) and edit_counter > 0: + return False + + return True + +# Driver code +str1 = input("Enter first string: ") +str2 = input("Enter second string: ") +print(is_one_away(str1, str2)) 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/roman_to_integer.py b/data_structures/strings/roman_to_integer.py new file mode 100644 index 00000000..e9e830ec --- /dev/null +++ b/data_structures/strings/roman_to_integer.py @@ -0,0 +1,40 @@ +''' +Question: +Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M and their values are 1,5,10,50,100,500,1000 respectively.Given a roman numeral,convert it to an integer.Input is guaranteed to be within the range from 1 to 3999. + +Example 1: +Input : 'XI' +Output : 11 + +Example 2: +Input : 'CCCXL' +Output : 340 + +Source: Interview question. + +Time Complexity : O(n) +Space Complexity : O(1) + +''' + +# Function to convert a roman numeral to an integer +def roman_to_integer(input): + romans={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000} + sum=0 + for i in range(input): + # Getting the value of the symbol + value=romans[input[i]] + # Comparing if the next value is bigger or smaller than the current value + if i+1value: + # If it is big, the value is subtracted from the sum + sum-=value + else: + sum+=value + return sum + +# Main code +print("Roman to integer conversion of") +input=input.upper() +roman_to_integer(input) + 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/toggle_string.py b/data_structures/strings/toggle_string.py new file mode 100644 index 00000000..a7292c31 --- /dev/null +++ b/data_structures/strings/toggle_string.py @@ -0,0 +1,33 @@ +""" +Question +You have been given a String S consisting of uppercase and lowercase English alphabets. You need to change the case of each alphabet +in this String. That is, all the uppercase letters should be converted to lowercase and all the lowercase letters should be converted +to uppercase. You need to then print the resultant String to output. +Source Hackerearth + +SAMPLE INPUT +abcdE +SAMPLE OUTPUT +ABCDe +""" + +def toggle_string_1(string): + return string.swapcase() + +def toggle_string_2(string): + toggle_string='' + for s in string: + if s.isupper(): + toggle_string+=s.lower() + elif s.islower(): + toggle_string+=s.upper() + else: + toggle_string+=s + return toggle_string + +string=input() + +# method 1 +print(toggle_string_1(string)) +# method 2 +print(toggle_string_2(string)) 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/data_structures/union_find/uf.py b/data_structures/union_find/uf.py new file mode 100644 index 00000000..d1f52da3 --- /dev/null +++ b/data_structures/union_find/uf.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +# 0-based indexing is used in this +# n is given as an input, which is the number of elements in the set + +class Union_find: + ''' + Union-Find data structure along with required functions. Takes 'n' (integer) as input + which is the total number of nodes in the structure. + ''' + def __init__ (self, n): + self.ar = [None] * n + for i in range(n): + self.ar[i] = i + self.size = [1] * n + self.n = n + + def root (self, x): + ''' + Function used to find root of a node "x" + Input: node number (integer between 0 and n-1) + Output: node number (integer between 0 and n-1) + ''' + while self.ar[x] != x: + self.ar[x] = self.ar[self.ar[x]] + x = self.ar[x] + return x + + def union (self, a, b): + ''' + Function to perform union of the nodes 'a' and 'b' + Input: node number, node number (integers between 0 and n-1) + Output: none + ''' + print("Union", a, "and", b) + roa = self.root(a) + rob = self.root(b) + if self.size[roa] < self.size[rob]: + self.ar[roa] = self.ar[rob] + self.size[rob] += self.size[roa] + else: + self.ar[rob] = self.ar[roa] + self.size[roa] += self.size[rob] + + def find (self, a, b): + ''' + Function to check if 'a' and 'b' are connected + Input: node number, node number (integers between 0 and n-1) + Output: True or False + ''' + if self.root (a) == self.root (b): + return True + else: + return False + + pass + +if __name__ == "__main__": + uf = Union_find (6) + uf.union (1, 2) + print("2 and 3 connected?", uf.find(2, 3)) + uf.union (1, 3) + uf.union (4, 5) + print("2 and 4 connected?", uf.find(2, 4)) + uf.union (3, 5) + print("2 and 4 connected?", uf.find(2, 4)) + 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