arrow-left

All pages
gitbookPowered by GitBook
1 of 2

Loading...

Loading...

List-Of-Solutions-To-Common-Interview-Questions

hashtag
Abstract Data Structures

hashtag
practice

BFSarrow-up-right

  • Palendromearrow-up-right

  • Untitledarrow-up-right

  • Algorithmsarrow-up-right

  • Dictionaryarrow-up-right

  • Array Practicearrow-up-right

  • Binary Searcharrow-up-right

  • Binary Treearrow-up-right

    • Binary Tree Explainedarrow-up-right

    • Find the maximum path sum between two leaves of a binary treearrow-up-right

  • Binary Search Treearrow-up-right

    • BST Explainedarrow-up-right

    • BST Insertarrow-up-right

  • Exoticarrow-up-right

    • Tirearrow-up-right

    • Dynamic Programmingarrow-up-right

  • Graphsarrow-up-right

    • Overflow Practice Problemsarrow-up-right

    • Graphs Explainedarrow-up-right

  • Hash Tablearrow-up-right

    • Hashmap or Hash tablesarrow-up-right

    • Hash Table and HashMap in Pythonarrow-up-right

  • Heaparrow-up-right

    • Heap Examplesarrow-up-right

  • Stringarrow-up-right

  • Maparrow-up-right

    • Examplesarrow-up-right

  • Queuearrow-up-right

    • Queue Continued...arrow-up-right

    • Queue Sandboxarrow-up-right

  • Treearrow-up-right

    • In Order Traversalarrow-up-right

    • Tree Equal ?arrow-up-right

  • Recursionarrow-up-right

    • Recursion Explainedarrow-up-right

      • Recursion Examplesarrow-up-right

  • Linked Listarrow-up-right

    • Linked List Backgroundarrow-up-right

    • Double Linked Listarrow-up-right

  • Setarrow-up-right

    • Setarrow-up-right

    • Set Intersection Unionarrow-up-right

  • Sortingarrow-up-right

    • In JavaScriptarrow-up-right

    • Merge Sortarrow-up-right

  • Stackarrow-up-right

    • Stack Continuedarrow-up-right

    • Stack Part 3arrow-up-right

  • Searchingarrow-up-right

    • Binary Searcharrow-up-right

    • Searching & Sorting Computational Complexity (JS)arrow-up-right

  • CGA-Sprint Preparrow-up-right

  • Supplemental Practice:arrow-up-right

    • JavaScript Algorithmsarrow-up-right

    • Industry Standard Algorithmsarrow-up-right

  • Algorithmsarrow-up-right
    Dijkstra's algorithmarrow-up-right
    Calculate a Factorial With Python - Iterative and Recursivearrow-up-right
    DFSarrow-up-right
    Data Structures Overviewarrow-up-right
    General Data Structures Notesarrow-up-right
    DS-Explained-Simplearrow-up-right
    Abstract Data Structures:arrow-up-right
    Arrayarrow-up-right
    Extra-Arrayarrow-up-right
    GCA Sprint Prep:arrow-up-right
    Practice Problemsarrow-up-right
    Code Signal CGA Sprint Resourcesarrow-up-right
    
    """
    Climbing Staircase
    
    There exists a staircase with N steps, and you can climb up either X different steps at a time.
    Given N, write a function that returns the number of unique ways you can climb the staircase.
    The order of the steps matters.
    
    Input: steps = [1, 2], height = 4
    Output: 5
    Output explanation:
    1, 1, 1, 1
    2, 1, 1
    1, 2, 1
    1, 1, 2
    2, 2
    
    =========================================
    Dynamic Programing solution.
        Time Complexity:    O(N*S)
        Space Complexity:   O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def climbing_staircase(steps, height):
        dp = [0 for i in range(height)]
    
        # add all steps into dp
        for s in steps:
            if s <= height:
                dp[s - 1] = 1
    
        # for each position look how you can arrive there
        for i in range(height):
            for s in steps:
                if i - s >= 0:
                    dp[i] += dp[i - s]
    
        return dp[height - 1]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 5
    print(climbing_staircase([1, 2], 4))
    
    # Test 2
    # Correct result => 3
    print(climbing_staircase([1, 3, 5], 4))
    
    """
    Coin Change
    
    You are given coins of different denominations and a total amount of money amount.
    Write a function to compute the fewest number of coins that you need to make up that amount.
    If that amount of money cannot be made up by any combination of the coins, return -1.
    
    Input: coins = [1, 2, 5], amount = 11
    Output: 3
    
    Input: coins = [2], amount = 3
    Output: -1
    
    =========================================
    Dynamic programming solution 1
        Time Complexity:    O(A*C)  , A = amount, C = coins
        Space Complexity:   O(A)
    Dynamic programming solution 2 (don't need the whole array, just use modulo to iterate through the partial array)
        Time Complexity:    O(A*C)  , A = amount, C = coins
        Space Complexity:   O(maxCoin)
    """
    
    
    ##############
    # Solution 1 #
    ##############
    
    
    def coin_change_1(coins, amount):
        if amount == 0:
            return 0
        if len(coins) == 0:
            return -1
    
        max_value = amount + 1  # use this instead of math.inf
        dp = [max_value for i in range(max_value)]
        dp[0] = 0
    
        for i in range(1, max_value):
            for c in coins:
                if c <= i:
                    # search on previous positions for min coins needed
                    dp[i] = min(dp[i], dp[i - c] + 1)
    
        if dp[amount] == max_value:
            return -1
        return dp[amount]
    
    
    ##############
    # Solution 2 #
    ##############
    
    
    def coin_change_2(coins, amount):
        if amount == 0:
            return 0
        if len(coins) == 0:
            return -1
    
        max_value = amount + 1
        max_coin = min(max_value, max(coins) + 1)
        dp = [max_value for i in range(max_coin)]
        dp[0] = 0
    
        for i in range(1, max_value):
            i_mod = i % max_coin
            dp[i_mod] = max_value  # reset current position
    
            for c in coins:
                if c <= i:
                    # search on previous positions for min coins needed
                    dp[i_mod] = min(dp[i_mod], dp[(i - c) % max_coin] + 1)
    
        if dp[amount % max_coin] == max_value:
            return -1
        return dp[amount % max_coin]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 3
    coins = [1, 2, 5]
    amount = 11
    print(coin_change_1(coins, amount))
    print(coin_change_2(coins, amount))
    
    # Test 2
    # Correct result => -1
    coins = [2]
    amount = 3
    print(coin_change_1(coins, amount))
    print(coin_change_2(coins, amount))
    
    """
    Count IP Addresses
    
    An IP Address (IPv4) consists of 4 numbers which are all between 0 and 255.
    In this problem however, we are dealing with 'Extended IP Addresses' which consist of K such numbers.
    Given a string S containing only digits and a number K,
    your task is to count how many valid 'Extended IP Addresses' can be formed.
    An Extended IP Address is valid if:
    * it consists of exactly K numbers
    * each numbers is between 0 and 255, inclusive
    * a number cannot have leading zeroes
    
    Input: '1234567', 3
    Output: 1
    Output explanation: Valid IP addresses: '123.45.67'.
    
    Input: '100111', 3
    Output: 1
    Output explanation: Valid IP addresses: '100.1.11', '100.11.1', '10.0.111'.
    
    Input: '345678', 2
    Output: 0
    Output explanation: It is not possible to form a valid IP Address with two numbers.
    
    =========================================
    1D Dynamic programming solution.
        Time Complexity:    O(N*K)
        Space Complexity:   O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def count_ip_addresses(S, K):
        n = len(S)
        if n == 0:
            return 0
        if n < K:
            return 0
    
        dp = [0] * (n + 1)
        dp[0] = 1
    
        for i in range(K):
            # if you want to save just little calculations you can use min(3*(i+1), n) instead of n
            for j in range(n, i, -1):
                # reset the value
                dp[j] = 0
    
                # use iteration to check all 3 possible numbers (x, xx, xxx), instead of writing 3 IFs
                for e in range(max(i, j - 3), j):
                    if is_valid(S[e:j]):
                        dp[j] += dp[e]
    
        return dp[n]
    
    
    def is_valid(S):
        if (len(S) > 1) and (S[0] == "0"):
            return False
        return int(S) <= 255
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 1
    print(count_ip_addresses("1234567", 3))
    
    # Test 2
    # Correct result => 3
    print(count_ip_addresses("100111", 3))
    
    # Test 3
    # Correct result => 0
    print(count_ip_addresses("345678", 2))
    
    """
    Create Palindrome (Minimum Insertions to Form a Palindrome)
    
    Given a string, find the palindrome that can be made by inserting the fewest number of characters as possible anywhere in the word.
    If there is more than one palindrome of minimum length that can be made, return the lexicographically earliest one (the first one alphabetically).
    
    Input: 'race'
    Output: 'ecarace'
    Output explanation: Since we can add three letters to it (which is the smallest amount to make a palindrome).
                        There are seven other palindromes that can be made from "race" by adding three letters, but "ecarace" comes first alphabetically.
    
    Input: 'google'
    Output: 'elgoogle'
    
    Input: 'abcda'
    Output: 'adcbcda'
    Output explanation: Number of insertions required is 2 - aDCbcda (between the first and second character).
    
    Input: 'adefgfdcba'
    Output: 'abcdefgfedcba'
    Output explanation: Number of insertions required is 3 i.e. aBCdefgfEdcba.
    
    =========================================
    Recursive count how many insertions are needed, very slow and inefficient.
        Time Complexity:    O(2^N)
        Space Complexity:   O(N^2)  , for each function call a new string is created (and the recursion can have depth of max N calls)
    Dynamic programming. Count intersections looking in 3 direction in the dp table (diagonally left-up or min(left, up)).
        Time Complexity:    O(N^2)
        Space Complexity:   O(N^2)
    """
    
    
    ##############
    # Solution 1 #
    ##############
    
    
    def create_palindrome_1(word):
        n = len(word)
    
        # base cases
        if n == 1:
            return word
        if n == 2:
            if word[0] != word[1]:
                word += word[0]  # make a palindrom
            return word
    
        # check if the first and last chars are same
        if word[0] == word[-1]:
            # add first and last chars
            return word[0] + create_palindrome_1(word[1:-1]) + word[-1]
    
        # if not remove the first and after that the last char
        # and find which result has less chars
        first = create_palindrome_1(word[1:])
        first = word[0] + first + word[0]  # add first char twice
    
        last = create_palindrome_1(word[:-1])
        last = word[-1] + last + word[-1]  # add last char twice
    
        if len(first) < len(last):
            return first
        return last
    
    
    ##############
    # Solution 2 #
    ##############
    
    import math
    
    
    def create_palindrome_2(word):
        n = len(word)
        dp = [[0 for j in range(n)] for i in range(n)]
    
        # run dp
        for gap in range(1, n):
            left = 0
            for right in range(gap, n):
                if word[left] == word[right]:
                    dp[left][right] = dp[left + 1][right - 1]
                else:
                    dp[left][right] = min(dp[left][right - 1], dp[left + 1][right]) + 1
                left += 1
    
        # build the palindrome using the dp table
        return build_palindrome(word, dp, 0, n - 1)
    
    
    def build_palindrome(word, dp, left, right):
        # similar like the first solution, but without exponentialy branching
        # this is linear time, we already know the inserting values
        if left > right:
            return ""
        if left == right:
            return word[left]
    
        if word[left] == word[right]:
            return word[left] + build_palindrome(word, dp, left + 1, right - 1) + word[left]
    
        if dp[left + 1][right] < dp[left][right - 1]:
            return word[left] + build_palindrome(word, dp, left + 1, right) + word[left]
    
        return word[right] + build_palindrome(word, dp, left, right - 1) + word[right]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 'ecarace'
    word = "race"
    print(create_palindrome_1(word))
    print(create_palindrome_2(word))
    
    # Test 2
    # Correct result => 'elgoogle'
    word = "google"
    print(create_palindrome_1(word))
    print(create_palindrome_2(word))
    
    # Test 3
    # Correct result => 'adcbcda'
    word = "abcda"
    print(create_palindrome_1(word))
    print(create_palindrome_2(word))
    
    # Test 4
    # Correct result => 'abcdefgfedcba'
    word = "adefgfdcba"
    print(create_palindrome_1(word))
    print(create_palindrome_2(word))
    
    """
    Interleaving Strings
    
    Given are three strings A, B and C.
    C is said to be interleaving of A and B, if:
    - it contains all characters of A and B, and
    - order of all characters from A and B is preserved in C
    Your task is to count in how many ways C can be formed by interleaving of A and B.
    
    Input: A='xy', B= 'xz', C: 'xxyz'
    Output: 2
    Output explanation: 
        1) Take 'x' from A, then 'x' from B, then 'y' from A and at the end 'z' from B.
        2) Take 'x' from B, then 'x' from A, then 'y' from A and at the end 'z' from B.
    
    =========================================
    2D Dynamic programming solution.
        Time Complexity:    O(N*M)
        Space Complexity:   O(N*M)
    1D Dynamic programming solution. Only the last two rows from the whole matrix are used, but that could be represented using only 1 row.
        Time Complexity:    O(N*M)
        Space Complexity:   O(M)
    """
    
    ##############
    # Solution 1 #
    ##############
    
    
    def interleaving_strings_1(A, B, C):
        nA, nB, nC = len(A), len(B), len(C)
        if nA + nB != nC:
            return 0
    
        dp = [[0 for j in range(nB + 1)] for i in range(nA + 1)]
    
        # starting values
        dp[0][0] = 1
    
        for i in range(1, nA + 1):
            if A[i - 1] == C[i - 1]:
                # short form of if A[i - 1] == C[i - 1] and dp[i - 1][0] == 1
                # dp[i][0] and dp[0][1] can be only 0 or 1
                dp[i][0] = dp[i - 1][0]
    
        for i in range(1, nB + 1):
            if B[i - 1] == C[i - 1]:
                dp[0][i] = dp[0][i - 1]
    
        # run dp
        for i in range(1, nA + 1):
            for j in range(1, nB + 1):
                if A[i - 1] == C[i + j - 1]:
                    # look for the dp value from the previous position
                    dp[i][j] += dp[i - 1][j]
                if B[j - 1] == C[i + j - 1]:
                    # look for the dp value from the previous position
                    dp[i][j] += dp[i][j - 1]
    
        return dp[nA][nB]
    
    
    ##############
    # Solution 2 #
    ##############
    
    
    def interleaving_strings_2(A, B, C):
        nA, nB, nC = len(A), len(B), len(C)
        if nA + nB != nC:
            return 0
    
        dp = [0 for j in range(nB + 1)]
    
        # starting values
        dp[0] = 1
    
        for i in range(1, nB + 1):
            if B[i - 1] == C[i - 1]:
                dp[i] = dp[i - 1]
    
        # run dp
        for i in range(1, nA + 1):
            if A[i - 1] != C[i - 1]:
                # reset the value
                dp[0] = 0
    
            for j in range(1, nB + 1):
                if A[i - 1] != C[i + j - 1]:
                    # reset the value
                    dp[j] = 0
                if B[j - 1] == C[i + j - 1]:
                    dp[j] += dp[j - 1]
    
        return dp[nB]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 2
    a, b, c = "xy", "xz", "xxyz"
    print(interleaving_strings_1(a, b, c))
    print(interleaving_strings_2(a, b, c))
    
    """
    Jump Game 2
    
    Given an array of non-negative integers, you are initially positioned at the first index of the array.
    Each element in the array represents your maximum jump length at that position.
    Your goal is to reach the last index in the minimum number of jumps.
    
    Input: XXX
    Output: XXX
    Output explanation: XXX
    
    =========================================
    Classical 1D Dynamic Programming solution.
        Time Complexity:    O(N)    , maybe looks like O(N^2) but that's not possible
        Space Complexity:   O(N)
    If you analyze the previous solution, you'll see that you don't need the whole DP array.
        Time Complexity:    O(N)
        Space Complexity:   O(1)
    """
    
    
    ##############
    # Solution 1 #
    ##############
    
    
    def min_jumps_1(nums):
        n = len(nums)
        if n <= 1:
            return 0
    
        dp = [-1] * n
        dp[0] = 0
    
        for i in range(n):
            this_jump = i + nums[i]
            jumps = dp[i] + 1
    
            if this_jump >= n - 1:
                return jumps
    
            # starging from back, go reverse and
            # change all -1 values and break when first positive is found
            for j in range(this_jump, i, -1):
                if dp[j] != -1:
                    break
                dp[j] = jumps
    
    
    ##############
    # Solution 2 #
    ##############
    
    
    def min_jumps_2(nums):
        n = len(nums)
        if n <= 1:
            return 0
    
        jumps = 0
        max_jump = 0
        new_max_jump = 0
    
        for i in range(n):
            if max_jump < i:
                max_jump = new_max_jump
                jumps += 1
    
            this_jump = i + nums[i]
            if this_jump >= n - 1:
                return jumps + 1
    
            new_max_jump = max(new_max_jump, this_jump)
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 2
    nums = [2, 3, 1, 1, 4]
    print(min_jumps_1(nums))
    print(min_jumps_2(nums))
    
    """
    Longest Common Subsequence
    
    Given 2 strings, find the longest common subseqence - https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
    NOT Longest Common Substring, this is a different problem.
    Substring is a string composed ONLY of neighboring chars, subsequence could contain non-neighboring chars.
    
    Input: 'ABAZDC', 'BACBAD'
    Output: 'ABAD'
    
    Input: 'I'm meto', 'I am Meto'
    Output: 'Im eto'
    
    =========================================
    Dynamic programming solution.
    Find more details here: https://www.geeksforgeeks.org/printing-longest-common-subsequence/
        Time Complexity:    O(N * M)
        Space Complexity:   O(N * M)    , can be O(M) see longest_common_substring.py solution (but you'll need to save subsequences)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def longest_common_subsequence(str1, str2):
        n, m = len(str1), len(str2)
        # create dp matrix
        dp = [[0 for j in range(m + 1)] for i in range(n + 1)]
    
        # run dp
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                # checks only in 3 directions in the table
                # in short: to the current position dp could come from those 3 previous positions
                #   ^  ^
                #    \ |
                #   <- O
                if str1[i - 1] == str2[j - 1]:
                    # from this position dp could come only if there is a match in the previous chars
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    # dp could come from these positions only if there is no much
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    
        # find the subseqence/string
        letters = dp[n][m]
        # use an array for storing the chars because string manipulation operations are not time and space efficient
        result = ["" for i in range(letters)]
        i = n
        j = m
    
        while (i != 0) and (j != 0):
            # use the inverse logic from upper code (filling the dp table)
            if str1[i - 1] == str2[j - 1]:
                letters -= 1
                result[letters] = str1[i - 1]
                j -= 1
                i -= 1
            elif dp[i - 1][j] < dp[i][j - 1]:
                j -= 1
            else:
                i -= 1
    
        return "".join(result)
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 'ABAD'
    print(longest_common_subsequence("ABAZDC", "BACBAD"))
    
    # Test 2
    # Correct result => 'Im eto'
    print(longest_common_subsequence("I'm meto", "I am Meto"))
    
    """
    Longest Common Substring
    
    Given two strings X and Y, find the of longest common substring.
    
    Input: 'GeeksforGeeks', 'GeeksQuiz'
    Output: 'Geeks'
    
    =========================================
    Dynamic Programming Solution.
        Time Complexity:    O(N * M)
        Space Complexity:   O(M)
    * For this problem exists a faster solution, using Suffix tree, Time Complexity O(N + M).
    """
    
    
    ############
    # Solution #
    ############
    
    
    def longest_common_substring(str1, str2):
        n, m = len(str1), len(str2)
        # instead of creating a whole dp table, use only 2 rows (current and previous row)
        curr = [0 for j in range(m + 1)]
        prev = []
        max_length = 0
        max_idx = 0
    
        for i in range(1, n + 1):
            # save the previous row and create the current row
            prev = curr
            curr = [0 for j in range(m + 1)]
    
            for j in range(1, m + 1):
                if str1[i - 1] == str2[j - 1]:
                    # search only for matching chars
                    curr[j] = prev[j - 1] + 1
    
                    if curr[j] > max_length:
                        # save the last matching index of the first string
                        max_length = curr[j]
                        max_idx = i
    
        return str1[max_idx - max_length : max_idx]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => BABC
    print(longest_common_substring("ABABC", "BABCA"))
    
    # Test 2
    # Correct result => Geeks
    print(longest_common_substring("GeeksforGeeks", "GeeksQuiz"))
    
    # Test 3
    # Correct result => abcd
    print(longest_common_substring("abcdxyz", "xyzabcd"))
    
    # Test 4
    # Correct result => abcdez
    print(longest_common_substring("zxabcdezy", "yzabcdezx"))
    
    """
    Longest Increasing Subsequence (LIS)
    
    Find the longest increasing subsequence.
    (subsequence doesn't mean that all elements need to be neighboring in the original array).
    
    Sample input: [1, 4, 2, 0, 3, 1]
    Sample output: [1, 2, 3]
    or output the length
    Sample output: 3
    
    =========================================
    Dynamic programming (classical) solution.
        Time Complexity:    O(N^2)
        Space Complexity:   O(N)
    Dynamic programing in combination with binary search.
    Explanation in details: https://www.geeksforgeeks.org/longest-monotonically-increasing-subsequence-size-n-log-n/
        Time Complexity:    O(N * logN)
        Space Complexity:   O(N^2)      , if you need only the length of the LIS, extra space complexity will be O(N)
    """
    
    
    ##############
    # Solution 1 #
    ##############
    
    
    def longest_increasing_subsequence_1(nums):
        n = len(nums)
        if n == 0:
            return 0
        dp = [1 for i in range(n)]
        max_val = 1
    
        # run dp
        for i in range(n):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
                    max_val = max(max_val, dp[i])
    
        # find the values (there could be more combinations/solutions)
        current_val = max_val
        result = [0 for i in range(current_val)]
        # start from the back and look for the biggest value in dp list
        for i in range(n - 1, -1, -1):
            if (dp[i] == current_val) and (
                (len(result) == current_val) or (result[current_val] > nums[i])
            ):
                current_val -= 1
                result[current_val] = nums[i]
    
        return result
    
    
    ##############
    # Solution 2 #
    ##############
    
    
    def longest_increasing_subsequence_2(nums):
        n = len(nums)
        if n == 0:
            return 0
        # the last dp array result in longest increasing subsequence
        dp = []
    
        for i in range(n):
            idx = binary_search(dp, nums[i])
            k = len(dp)
    
            if idx == k:
                # bigger element than the current wasn't found
                arr = []
                if k != 0:
                    arr = [i for i in dp[-1]]  # make a copy
                arr.append(nums[i])
    
                dp.append(arr)
            elif dp[idx][-1] > nums[i]:
                # smaller element was found, replace it
                dp[idx][-1] = nums[i]
    
        return dp[-1]
    
    
    def binary_search(dp, target):
        l = 0
        r = len(dp) - 1
    
        while l <= r:
            mid = l + (r - l) // 2
            if dp[mid][-1] == target:
                return mid
            elif dp[mid][-1] < target:
                l = mid + 1
            else:
                r = mid - 1
    
        return l
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => [2, 3, 7, 18] - one of the possible combinations
    arr = [10, 9, 2, 5, 3, 7, 101, 18]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    # Test 2
    # Correct result => [1, 2, 3]
    arr = [1, 2, 3]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    # Test 3
    # Correct result => [1, 2, 5, 7, 12] - one of the possible combinations
    arr = [10, 1, 3, 8, 2, 0, 5, 7, 12, 3]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    # Test 4
    # Correct result => [1, 2, 3, 4, 5, 6]
    arr = [12, 1, 11, 2, 10, 3, 9, 4, 8, 5, 7, 6]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    # Test 5
    # Correct result => [1, 2, 3]
    arr = [1, 4, 2, 0, 3, 1]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    # Test 6
    # Correct result => [3] - one of the possible combinations
    arr = [7, 5, 5, 5, 5, 5, 3]
    print(longest_increasing_subsequence_1(arr))
    print(longest_increasing_subsequence_2(arr))
    
    """
    Max Profit With K Transactions
    
    You are given an array of integers representing the prices of a single stock on various days
    (each index in the array represents a different day).
    You are also given an integer k, which represents the number of transactions you are allowed to make.
    One transaction consists of buying the stock on a given day and selling it on another, later day.
    Write a function that returns the maximum profit that you can make buying and selling the stock,
    given k transactions. Note that you can only hold 1 share of the stock at a time; in other words,
    you cannot buy more than 1 share of the stock on any given day, and you cannot buy a share of the
    stock if you are still holding another share.
    In a day, you can first sell a share and buy another after that.
    
    Input: [5, 11, 3, 50, 60, 90], 2
    Output: 93
    Output explanation: Buy 5, Sell 11; Buy 3, Sell 90
    
    =========================================
    Optimized dynamic programming solution.
    For this solution you'll need only the current and previous rows.
    The original (not optimized) DP formula is: MAX(dp[t][d-1], price[d] + MAX(dp[t-1][x] - price[x])),
    but this is O(K * N^2) Time Complexity, and O(N * K) space complexity.
        Time Complexity:    O(N * К)
        Space Complexity:   O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    import math
    
    
    def max_profit_with_k_transactions(prices, k):
        days = len(prices)
        if days < 2:
            # not enough days for a transaction
            return 0
    
        # transaction = buy + sell (2 separate days)
        # in a day you can sell and after that buy a share
        # (according to this, can't exists more transactions than the number of the prices/days)
        k = min(k, days)
        # create space optimized dp matrix
        dp = [[0 for j in range(days)] for i in range(2)]
    
        for t in range(k):
            max_prev = -math.inf
    
            # compute which row is previous and which is the current one
            prev_idx = (t - 1) % 2
            curr_idx = t % 2
    
            # the values in dp table for these days will be same
            # just ignore them, don't update them (because those combinations were tried)
            past_days = t
            # only save the last one
            dp[curr_idx][past_days] = dp[prev_idx][past_days]
    
            for d in range(past_days + 1, days):
                # first try to buy with the current price
                max_prev = max(max_prev, dp[prev_idx][d - 1] - prices[d - 1])
                # after that try to sell with the current price
                dp[curr_idx][d] = max(dp[curr_idx][d - 1], max_prev + prices[d])
    
        # return the last value from the last transaction
        return dp[(k - 1) % 2][-1]
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 9
    print(max_profit_with_k_transactions([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10))
    
    # Test 2
    # Correct result => 93
    print(max_profit_with_k_transactions([5, 11, 3, 50, 60, 90], 2))
    
    """
    Maximum subarray sum
    
    The subarray must be contiguous.
    
    Sample input: [-2, -3, 4, -1, -2, 1, 5, -3]
    Sample output: 7
    Output explanation: [4, -1, -2, 1, 5]
    
    =========================================
    Need only one iteration, in each step add the current element to the current sum.
    When the sum is less than 0, reset the sum to 0 and continue with adding. (we care only about non-negative sums)
    After each addition, check if the current sum is greater than the max sum. (Called Kadane's algorithm)
        Time Complexity:    O(N)
        Space Complexity:   O(1)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def max_subarray_sum(a):
        curr_sum = 0
        max_sum = 0
    
        for val in a:
            # extend the current sum with the curren value;
            # reset it to 0 if it is smaller than 0, we care only about non-negative sums
            curr_sum = max(0, curr_sum + val)
    
            # check if this is the max sum
            max_sum = max(max_sum, curr_sum)
    
        return max_sum
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 7
    print(max_subarray_sum([-2, -3, 4, -1, -2, 1, 5, -3]))
    
    # Test 2
    # Correct result => 5
    print(max_subarray_sum([1, -2, 2, -2, 3, -2, 4, -5]))
    
    # Test 3
    # Correct result => 7
    print(max_subarray_sum([-2, -5, 6, -2, -3, 1, 5, -6]))
    
    # Test 4
    # Correct result => 0
    print(max_subarray_sum([-6, -1]))
    
    """
    Min Cost Coloring
    
    A builder is looking to build a row of N houses that can be of K different colors.
    He has a goal of minimizing cost while ensuring that no two neighboring houses are of the same color.
    Given an N by K matrix where the nth row and kth column represents the cost to build the
    nth house with kth color, return the minimum cost which achieves this goal.
    
    =========================================
    Dynamic programming, for each house search for the cheapest combination of the previous houses.
    But don't search the whole array with combinations (colors), save only the smallest 2
    (in this case we're sure that the previous house doesn't have the same color).
        Time Complexity:    O(N * K)
        Space Complexity:   O(1)
    """
    
    ############
    # Solution #
    ############
    
    import math
    
    
    def min_cost_coloring(dp):
        # no need from a new dp matrix, you can use the input matrix
        n = len(dp)
        if n == 0:
            return 0
        m = len(dp[0])
        if m < 2:
            return -1
    
        # save only the smallest 2 costs instead of searching the whole previous array
        prev_min = [(0, -1), (0, -1)]
    
        for i in range(n):
            curr_min = [(math.inf, -1), (math.inf, -1)]
    
            for j in range(m):
                # find result with different color
                if j != prev_min[0][1]:
                    dp[i][j] += prev_min[0][0]
                else:
                    dp[i][j] += prev_min[1][0]
    
                # save the current result if smaller than the current 2
                if curr_min[0][0] > dp[i][j]:
                    curr_min[1] = curr_min[0]
                    curr_min[0] = (dp[i][j], j)
                elif curr_min[1][0] > dp[i][j]:
                    curr_min[1] = (dp[i][j], j)
    
            prev_min = curr_min
    
        # return the min cost of the last house
        return min(dp[n - 1])
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 5
    print(
        min_cost_coloring(
            [[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [3, 2, 1, 4, 5], [3, 2, 1, 4, 3]]
        )
    )
    
    # Test 2
    # Correct result => 6
    print(
        min_cost_coloring(
            [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
        )
    )
    
    """
    Number of Decodings
    
    Given the mapping a=1, b=2, ... , z=26, and an encoded message, count the number of ways it can be decoded.
    For example, the message "111" would give 3, since it could be decoded as "aaa", "ka" and "ak".
    All of the messages are decodable!
    
    =========================================
    The easiest solution is Brute-Force (building a tree and making all combinations),
    and in the worst case there will be Fibbionaci(N) combinations, so the worst Time Complexity will be O(Fib(N))
    
    Dynamic programming solution. Similar to number_of_smses.py.
        Time Complexity:    O(N)
        Space Complexity:   O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def num_decodings(code):
        n = len(code)
        dp = [0 for i in range(n)]
    
        if n == 0:
            return 0
        dp[0] = 1
        if n == 1:
            return dp[0]
        dp[1] = (code[1] != "0") + is_valid(code[0:2])
    
        for i in range(2, n):
            if code[i] != "0":
                # looking for how many combinations are there till now if this is a single digit
                dp[i] += dp[i - 1]
            if is_valid(code[i - 1 : i + 1]):
                # looking for how many combinations are there till now if this is a number of 2 digits
                dp[i] += dp[i - 2]
    
        return dp[n - 1]
    
    
    def is_valid(code):
        k = int(code)
        return (k < 27) and (k > 9)
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 5
    print(num_decodings("12151"))
    
    # Test 2
    # Correct result => 5
    print(num_decodings("1111"))
    
    # Test 3
    # Correct result => 3
    print(num_decodings("111"))
    
    # Test 4
    # Correct result => 1
    print(num_decodings("1010"))
    
    # Test 5
    # Correct result => 4
    print(num_decodings("2626"))
    
    # Test 6
    # Correct result => 1
    print(num_decodings("1"))
    
    # Test 7
    # Correct result => 2
    print(num_decodings("11"))
    
    # Test 8
    # Correct result => 3
    print(num_decodings("111"))
    
    # Test 9
    # Correct result => 5
    print(num_decodings("1111"))
    
    # Test 10
    # Correct result => 8
    print(num_decodings("11111"))
    
    # Test 11
    # Correct result => 13
    print(num_decodings("111111"))
    
    # Test 12
    # Correct result => 21
    print(num_decodings("1111111"))
    
    # Test 13
    # Correct result => 34
    print(num_decodings("11111111"))
    
    """
    Number of SMSes
    
    Given the number sequence that is being typed in order to write and SMS message, return the count
    of all the possible messages that can be constructed.
    
     1    2    3
         abc  def
    
     4    5    6
    ghi  jkl  mno
    
     7    8    9
    pqrs tuv wxyz
    
    The blank space character is constructed with a '0'.
    
    Input: '222'
    Output: 4
    Output explanation: '222' could mean: 'c', 'ab','ba' or 'aaa'. That makes 4 possible messages.
    
    =========================================
    Dynamic programming solution. Similar to number_of_decodings.py.
        Time Complexity:    O(N)
        Space Complexity:   O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def num_smses(sequence):
        n = len(sequence)
        dp = [0] * n
    
        # dp starting values, check all 4 possible starting combinations
        for i in range(min(4, n)):
            if is_valid(sequence[0 : i + 1]):
                dp[i] = 1
    
        # run dp
        for i in range(1, n):
            # check all 4 possible combinations (x, xx, xxx, xxxx)
            for j in range(min(4, i)):
                if is_valid(sequence[i - j : i + 1]):
                    dp[i] += dp[i - j - 1]
    
        return dp[n - 1]
    
    
    def is_valid(sequence):
        ch = sequence[0]
    
        for c in sequence:
            if c != ch:
                return False
    
        if sequence == "0":
            return True
    
        if ((ch >= "2" and ch <= "6") or ch == "8") and (len(sequence) < 4):
            return True
    
        if (ch == "7") or (ch == "9"):
            return True
    
        return False
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 4
    print(num_smses("222"))
    
    # Test 2
    # Correct result => 14
    print(num_smses("2202222"))
    
    # Test 3
    # Correct result => 274
    print(num_smses("2222222222"))
    
    """
    Ordered Digits
    
    We are given a number and we need to transform to a new number where all its digits are ordered in a non descending order.
    We are allowed to increase or decrease a digit by 1, and each of those actions counts as one operation.
    We are also allowed to over/underflow a number meaning from '9' we can change to '0' and also from '0' to '9', also costing only one operation.
    One same digit can be changed multiple times.
    Find the minimum number of operations we need to do do to create a new number with its ordered digits.
    
    Input: 301
    Output: 3
    Output explanation: 301 -> 201 -> 101 -> 111, in this case 3 operations are required to get an ordered number.
    
    Input: 901
    Output: 1
    Output explanation: 901 -> 001, in this case 1 operation is required to get an ordered number.
    
    Input: 5982
    Output: 4
    Output explanation: 5982 -> 5981 -> 5980 -> 5989 -> 5999, in this case 4 operations are required to get an ordered number.
    
    =========================================
    Dynamic programming solution. For each position, calculate the cost of transformation to each possible digit (0-9).
    And take the minimum value from the previous position (but smaller than the current digit).
        Time Complexity:    O(N)    , O(N*10) = O(N), N = number of digits
        Space Complexity:   O(N)    , same O(N*2) = O(N)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def ordered_digits(number):
        n = len(number)
        dp = [[0 for j in range(10)] for i in range(2)]
    
        for i in range(n):
            min_prev = float("inf")
            for j in range(10):
                # find the min value from the previous digit and add it to the current value
                min_prev = min(min_prev, dp[(i - 1) % 2][j])
                # compute diff between the current digit and wanted digit
                diff = abs(j - int(number[i]))
                dp[i % 2][j] = min(diff, 10 - diff) + min_prev
    
        # min value from the last digit
        return min(dp[(n - 1) % 2])
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 3
    print(ordered_digits("301"))
    
    # Test 2
    # Correct result => 1
    print(ordered_digits("901"))
    
    # Test 3
    # Correct result => 4
    print(ordered_digits("5982"))
    
    """
    Split Coins
    
    You have a number of coins with various amounts.
    You need to split the coins in two groups so that the difference between those groups in minimal.
    
    Input: [1, 1, 1, 3, 5, 10, 18]
    Output: 1
    Output explanation: First group 1, 3, 5, 10 (or 1, 1, 3, 5, 10) and second group 1, 1, 18 (or 1, 18).
    
    =========================================
    Simple dynamic programming solution. Find the closest sum to the half of the sum of all coins.
        Time Complexity:    O(C*HS)     , C = number of coins, HS = half of the sum of all coins
        Space Complexity:   O(HS)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def split_coins(coins):
        if len(coins) == 0:
            return -1
    
        full_sum = sum(coins)
        half_sum = full_sum // 2 + 1
    
        dp = [False] * half_sum
        dp[0] = True
    
        for c in coins:
            for i in range(half_sum - 1, -1, -1):
                if (i >= c) and dp[i - c]:
                    # if you want to find coins, save the coin here dp[i] = c
                    dp[i] = True
    
        for i in range(half_sum - 1, -1, -1):
            if dp[i]:
                # if you want to print coins, while i>0: print(dp[i]) i -= dp[i]
                return full_sum - 2 * i
    
        # not possible
        return -1
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 1
    print(split_coins([1, 1, 1, 3, 5, 10, 18]))
    
    """
    Sum of non-adjacent numbers
    
    Given a list of integers, write a function that returns the largest sum of non-adjacent numbers.
    Numbers can be 0 or negative.
    
    Input: [2, 4, 6, 2, 5]
    Output: 13
    Output explanation: We pick 2, 6, and 5.
    
    Input: [5, 1, 1, 5]
    Output: 10
    Output explanation: We pick 5 and 5.
    
    =========================================
    Dynamic programming solution, but don't need the whole DP array, only the last 3 sums (DPs) are needed.
        Time Complexity:    O(N)
        Space Complexity:   O(1)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def sum_non_adjacent(arr):
        n = len(arr)
        # from the dp matrix you only need the last 3 sums
        sums = [0, 0, 0]
    
        # TODO: refactor these if-elses, those are to skip using of DP matrix
        if n == 0:
            return 0
    
        # if negative or zero, the sum will be 0
        sums[0] = max(arr[0], 0)
    
        if n == 1:
            return sums[0]
    
        sums[1] = arr[1]
        # if the second number is negative or zero, then jump it
        if sums[1] <= 0:
            sums[1] = sums[0]
    
        if n == 2:
            return max(sums[0], sums[1])
    
        sums[2] = arr[2]
        # if the third number is negative or zero, then jump it
        if sums[2] <= 0:
            sums[2] = max(sums[0], sums[1])
        else:
            sums[2] += sums[0]
    
        # THE SOLUTION
        for i in range(3, n):
            temp = 0
    
            if arr[i] > 0:
                # take this number, because it's positive and the sum will be bigger
                temp = max(sums[0], sums[1]) + arr[i]
            else:
                # don't take this number, because the sum will be same or smaller
                temp = max(sums)
    
            # remove the first sum
            sums = sums[1:] + [temp]
    
        # return the max sum
        return max(sums)
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 13
    print(sum_non_adjacent([2, 4, 6, 2, 5]))
    
    # Test 2
    # Correct result => 15
    print(sum_non_adjacent([2, 4, 2, 6, 2, -3, -2, 0, -3, 5]))
    
    # Test 3
    # Correct result => 10
    print(sum_non_adjacent([5, 1, 1, 5]))
    
    # Test 4
    # Correct result => 10
    print(sum_non_adjacent([5, 1, -1, 1, 5]))
    
    """
    Transform Number Ascending Digits
    
    Given a number and we need to transform to a new number where all its digits are ordered in a non descending order.
    All digits can be increased, decreased, over/underflow are allowed.
    Find the minimum number of operations we need to do to create a new number with its ordered digits.
    
    Input: '5982'
    Output: 4
    Output explanation: 5999, 1 operation to transform 8 to 9, 3 operations to transform 2 to 9.
    
    =========================================
    Dynamic programming solution.
        Time Complexity:    O(N)    , O(N * 10 * 10) = O(100 N) = O(N)
        Space Complexity:   O(1)    , O(10 * 10) = O(100) = O(1)
    """
    
    
    ############
    # Solution #
    ############
    
    
    def operations(number):
        n = len(number)
        diff = lambda i, j: abs(j - int(number[i]))
        # compute diff between the current digit and wanted digit, and fill the dp
        prev_dp = [min(diff(0, i), 10 - diff(0, i)) for i in range(10)]
    
        # go through all digits and see all possible combinations using dynamic programming
        for i in range(1, n):
            curr_dp = [min(diff(i, j), 10 - diff(i, j)) for j in range(10)]
            for j in range(10):
                # find the min value for the previous digit and add it to the current value
                curr_dp[j] += min(prev_dp[0 : j + 1])
            prev_dp = curr_dp
    
        # min value from the last digit
        min_dist = min(prev_dp)
    
        return min_dist
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => 1
    print(operations("901"))
    
    # Test 2
    # Correct result => 3
    print(operations("301"))
    
    # Test 3
    # Correct result => 4
    print(operations("5982"))
    
    """
    Word Break (Find the original words)
    
    Given a dictionary of words and a string made up of those words (no spaces), return the original sentence in a list.
    If there is more than one possible reconstruction, return solution with less words.
    If there is no possible reconstruction, then return null.
    
    Input: sentence = 'thequickbrownfox', words = ['quick', 'brown', 'the', 'fox']
    Output: ['the', 'quick', 'brown', 'fox']
    
    Input: sentence = 'bedbathandbeyond', words = ['bed', 'bath', 'bedbath', 'and', 'beyond']
    Output: ['bedbath', 'and', 'beyond'] (['bed', 'bath', 'and', 'beyond] has more words)
    
    =========================================
    Optimized dynamic programming solution (more simpler solutions can be found here https://www.geeksforgeeks.org/word-break-problem-dp-32/)
        Time Complexity:    O(N*M)  , N = number of chars in the sentence, M = max word length
        Space Complexity:   O(N+W)  , W = number of words
    Bonus solution: Backtracking, iterate the sentence construct a substring and check if that substring exist in the set of words.
    If the end is reached but the last word doesn't exist in the words, go back 1 word from the result (backtracking).
    * But this solution doesn't give the result with the smallest number of words (gives the first found result)
        Time Complexity:    O(?)    , (worst case, about O(W! * N), for example sentence='aaaaaac', words=['a','aa','aaa','aaaa','aaaaa', 'aaaaaa'])
        Space Complexity:   O(W)
    """
    
    
    ############
    # Solution #
    ############
    
    import math
    
    
    def word_break(sentence, words):
        n, w = len(sentence), len(words)
        if (n == 0) or (w == 0):
            return None
    
        dw = [-1 for i in range(n + 1)]
        dp = [math.inf for i in range(n + 1)]
        dp[0] = 0
        matched_indices = [0]
    
        dic = {}  # save all words in dictionary for faster searching
        max_word = 0  # length of the max word
        for i in range(w):
            dic[words[i]] = i
            max_word = max(max_word, len(words[i]))
    
        for i in range(1, n + 1):
            matched = False
            # start from the back of the matched_indices list (from the bigger numbers)
            for j in range(len(matched_indices) - 1, -1, -1):
                matched_index = matched_indices[j]
                # break this loop if the subsentence created with this matched index is bigger than the biggest word
                if matched_index < i - max_word:
                    break
    
                subsentence = sentence[matched_index:i]
                # save this result if this subsentence exist in the words and number of words that forms sentence is smaller
                if (subsentence in dic) and (dp[matched_index] + 1 < dp[i]):
                    dp[i] = dp[matched_index] + 1
                    dw[i] = dic[subsentence]
                    matched = True
    
            if matched:
                matched_indices.append(i)
    
        # the sentence can't be composed from the given words
        if dp[n] == math.inf:
            return None
    
        # find the words that compose this sentence
        result = ["" for i in range(dp[n])]
        i = n
        j = dp[n] - 1
        while i > 0:
            result[j] = words[dw[i]]
            i -= len(words[dw[i]])
            j -= 1
    
        return result
    
    
    #########################
    # Solution Backtracking #
    #########################
    
    from collections import deque
    
    
    def word_break_backtracking(sentence, words):
        all_words = set()
    
        # create a set from all words
        for i in range(len(words)):
            all_words.add(words[i])
    
        n = len(sentence)
        i = 0
        subsentence = ""
        result = deque()
    
        # go letter by letter and save the new letter in subsentence
        while (i < n) or (len(subsentence) != 0):
            # if there are no left letters in the sentence, then this combination is not valid
            # remove the last word from the results and continue from that word
            if i == n:
                i -= len(subsentence)
                # if there are no words in the result, then this string is not composed only from the given words
                if len(result) == 0:
                    return None
                subsentence = result[-1]
                result.pop()
    
            # add the new letter into subsentence and remove it from the sentence
            subsentence += sentence[i]
            i += 1
    
            # check if the new word exist in the set
            if subsentence in all_words:
                result.append(subsentence)
                subsentence = ""
    
        return list(result)
    
    
    ###########
    # Testing #
    ###########
    
    # Test 1
    # Correct result => ['the', 'quick', 'brown', 'fox']
    print(word_break("thequickbrownfox", ["quick", "brown", "the", "fox"]))
    
    # Test 2
    # Correct result => ['bedbath', 'and', 'beyond']
    print(word_break("bedbathandbeyond", ["bed", "bath", "bedbath", "and", "beyond"]))
    
    # Test 3
    # Correct result => ['bedbath', 'andbeyond']
    print(
        word_break(
            "bedbathandbeyond",
            ["bed", "and", "bath", "bedbath", "bathand", "beyond", "andbeyond"],
        )
    )
    
    # Test 4
    # Correct result => None ('beyo' doesn't exist)
    print(word_break("bedbathandbeyo", ["bed", "bath", "bedbath", "bathand", "beyond"]))
    
    # Test 5
    # Correct result => ['314', '15926535897', '9323', '8462643383279']
    print(
        word_break(
            "3141592653589793238462643383279",
            ["314", "49", "9001", "15926535897", "14", "9323", "8462643383279", "4", "793"],
        )
    )
    
    # Test 6
    # Correct result => ['i', 'like', 'like', 'i', 'mango', 'i', 'i', 'i']
    print(
        word_break(
            "ilikelikeimangoiii",
            [
                "mobile",
                "samsung",
                "sam",
                "sung",
                "man",
                "mango",
                "icecream",
                "and",
                "go",
                "i",
                "like",
                "ice",
                "cream",
            ],
        )
    )
    
    ```

    Searching and Generating Graphsarrow-up-right

    Earliest Ancestorarrow-up-right
    _Mini Graph-Projectsarrow-up-right
    # Social Grapharrow-up-right
    number of 1 islandsarrow-up-right
    Graph FAQarrow-up-right
    Graph DFSarrow-up-right
    Connected Componentsarrow-up-right
    Randomnessarrow-up-right
    Graph BFSarrow-up-right
    Topological Sortarrow-up-right
    Dequeuearrow-up-right
    Ternary-search-treesarrow-up-right
    Red_Black Treearrow-up-right
    Tree Mirror:arrow-up-right
    Tree Traversalarrow-up-right
    List Examplearrow-up-right
    Examples (LL) continuedarrow-up-right
    List Operationsarrow-up-right
    Disjoint Setarrow-up-right
    Iterative Sortingarrow-up-right
    Recursive Sortingarrow-up-right
    Graph Topological Sortarrow-up-right
    SelectionSortarrow-up-right
    Quick Sortarrow-up-right
    Merge Sortarrow-up-right
    Insertion Sortarrow-up-right
    Interview Practice Resourcesarrow-up-right
    Write a Program to Find the Maximum Depth or Height of a Treearrow-up-right
    Random Examplesarrow-up-right
    Promptsarrow-up-right
    JS_BASICSarrow-up-right

    Algo-Resources

    hashtag
    Abstract Data Structures

    • Algorithmsarrow-up-right

    hashtag
    practice

    hashtag
    Data Structures & Algorithms Resource List Part 1

    Guess the author of the following quotes:

    hashtag
    Data Structures & Algorithms Resource List Part 1

    Guess the author of the following quotes:

    Talk is cheap. Show me the code.

    Software is like sex: it’s better when it’s free.

    Microsoft isn’t evil, they just make really crappy operating systems.

    ### Update:

    Here’s some more:

    hashtag
    Algorithms:

    •  — Solved algorithms and data structures problems in many languages.

    • () ()

    ### Data Structures:

    BFSarrow-up-right
  • Palendromearrow-up-right

  • Untitledarrow-up-right

  • Algorithmsarrow-up-right

  • Dictionaryarrow-up-right

  • Array Practicearrow-up-right

  • Binary Searcharrow-up-right

  • Binary Treearrow-up-right

    • Binary Tree Explainedarrow-up-right

    • Find the maximum path sum between two leaves of a binary treearrow-up-right

  • Binary Search Treearrow-up-right

    • BST Explainedarrow-up-right

    • BST Insertarrow-up-right

  • Exoticarrow-up-right

    • Tirearrow-up-right

    • Dynamic Programmingarrow-up-right

  • Graphsarrow-up-right

    • Overflow Practice Problemsarrow-up-right

    • Graphs Explainedarrow-up-right

  • Hash Tablearrow-up-right

    • Hashmap or Hash tablesarrow-up-right

    • Hash Table and HashMap in Pythonarrow-up-right

  • Heaparrow-up-right

    • Heap Examplesarrow-up-right

  • Stringarrow-up-right

  • Maparrow-up-right

    • Examplesarrow-up-right

  • Queuearrow-up-right

    • Queue Continued...arrow-up-right

    • Queue Sandboxarrow-up-right

  • Treearrow-up-right

    • In Order Traversalarrow-up-right

    • Tree Equal ?arrow-up-right

  • Recursionarrow-up-right

    • Recursion Explainedarrow-up-right

      • Recursion Examplesarrow-up-right

  • Linked Listarrow-up-right

    • Linked List Backgroundarrow-up-right

    • Double Linked Listarrow-up-right

  • Setarrow-up-right

    • Setarrow-up-right

    • Set Intersection Unionarrow-up-right

  • Sortingarrow-up-right

    • In JavaScriptarrow-up-right

    • Merge Sortarrow-up-right

  • Stackarrow-up-right

    • Stack Continuedarrow-up-right

    • Stack Part 3arrow-up-right

  • Searchingarrow-up-right

    • Binary Searcharrow-up-right

    • Searching & Sorting Computational Complexity (JS)arrow-up-right

  • CGA-Sprint Preparrow-up-right

  • Supplemental Practice:arrow-up-right

    • JavaScript Algorithmsarrow-up-right

    • Industry Standard Algorithmsarrow-up-right

  • Dynamic Programming Q&A — What is Optimal Substructurearrow-up-right
  • The Framework for Backtracking Algorithmarrow-up-right

  • Binary Search in Detail: I wrote a Poemarrow-up-right

  • The Sliding Window Techniquearrow-up-right

  • Difference Between Process and Thread in Linuxarrow-up-right

  • Some Good Online Practice Platformsarrow-up-right

  • Dynamic Programming in Detailsarrow-up-right

  • Dynamic Programming Q&A — What is Optimal Substructurearrow-up-right

  • Classic DP: Longest Common Subsequencearrow-up-right

  • Classic DP: Edit Distancearrow-up-right

  • Classic DP: Super Eggarrow-up-right

  • Classic DP: Super Egg (Advanced Solution)arrow-up-right

  • The Strategies of Subsequence Problemarrow-up-right

  • Classic DP: Game Problemsarrow-up-right

  • Greedy: Interval Schedulingarrow-up-right

  • KMP Algorithm In Detailarrow-up-right

  • A solution to all Buy Time to Buy and Sell Stock Problemsarrow-up-right

  • A solution to all House Robber Problemsarrow-up-right

  • 4 Keys Keyboardarrow-up-right

  • Regular Expressionarrow-up-right

  • Longest Increasing Subsequencearrow-up-right

  • The Framework for Learning Algorithms and intense problem solving exercisesarrow-up-right

  • Algs4: Recommended book for Learning Algorithms and Data Structuresarrow-up-right

  • Binary Heap and Priority Queuearrow-up-right

  • LRU Cache Strategy in Detailarrow-up-right

  • Collections of Binary Search Operationsarrow-up-right

  • Special Data Structure: Monotonic Stackarrow-up-right

  • Special Data Structure: Monotonic Stackarrow-up-right

  • Design Twitterarrow-up-right

  • Reverse Part of Linked List via Recursionarrow-up-right

  • Queue Implement Stack/Stack implement Queuearrow-up-right

  • My Way to Learn Algorithmarrow-up-right

  • The Framework of Backtracking Algorithmarrow-up-right

  • Binary Search in Detailarrow-up-right

  • Backtracking Solve Subset/Permutation/Combinationarrow-up-right

  • Diving into the technical parts of Double Pointersarrow-up-right

  • Sliding Window Techniquearrow-up-right

  • The Core Concept of TwoSum Problemsarrow-up-right

  • Common Bit Manipulationsarrow-up-right

  • Breaking down a Complicated Problem: Implement a Calculatorarrow-up-right

  • Pancake Sorting Algorithmarrow-up-right

  • Prefix Sum: Intro and Conceptarrow-up-right

  • String Multiplicationarrow-up-right

  • FloodFill Algorithm in Detailarrow-up-right

  • Interval Scheduling: Interval Mergingarrow-up-right

  • Interval Scheduling: Intersections of Intervalsarrow-up-right

  • Russian Doll Envelopes Problemarrow-up-right

  • A collection of counter-intuitive Probability Problemsarrow-up-right

  • Shuffle Algorithmarrow-up-right

  • Recursion In Detailarrow-up-right

  • How to Implement LRU Cachearrow-up-right

  • How to Find Prime Number Efficientlyarrow-up-right

  • How to Calculate Minimium Edit Distancearrow-up-right

  • How to use Binary Searcharrow-up-right

  • How to efficiently solve Trapping Rain Water Problemarrow-up-right

  • How to Remove Duplicates From Sorted Arrayarrow-up-right

  • How to Find Longest Palindromic Substringarrow-up-right

  • How to Reverse Linked List in K Grouparrow-up-right

  • How to Check the Validation of Parenthesisarrow-up-right

  • How to Find Missing Elementarrow-up-right

  • How to Find Duplicates and Missing Elementsarrow-up-right

  • How to Check Palindromic LinkedListarrow-up-right

  • How to Pick Elements From an Infinite Arbitrary Sequencearrow-up-right

  • How to Schedule Seats for Studentsarrow-up-right

  • Union-Find Algorithm in Detailarrow-up-right

  • Union-Find Applicationarrow-up-right

  • Problems that can be solved in one linearrow-up-right

  • Find Subsequence With Binary Searcharrow-up-right

  • Difference Between Process and Thread in Linuxarrow-up-right

  • You Must Know About Linux Shellarrow-up-right

  • You Must Know About Cookie and Sessionarrow-up-right

  • Cryptology Algorithmarrow-up-right

  • Some Good Online Practice Platformsarrow-up-right

  • Top algos/DS to learnarrow-up-right

  • Some neat algorithmsarrow-up-right

  • Mathematical Proof of Algorithm Correctness and Efficiency (2019)arrow-up-right

  • Algorithm Visualizerarrow-up-right — Interactive online platform that visualizes algorithms from code.

  • Algorithms for Optimization bookarrow-up-right

  • Collaborative book on algorithmsarrow-up-right (Codearrow-up-right)

  • Algorithms in C by Robert Sedgewickarrow-up-right

  • Algorithm Design Manualarrow-up-right

  • MIT Introduction to Algorithms course (2011)arrow-up-right

  • How to implement an algorithm from a scientific paper (2012)arrow-up-right

  • Quadsortarrow-up-right — Stable non-recursive merge sort named quadsort.

  • System design algorithmsarrow-up-right — Algorithms you should know before system design.

  • Algorithms Design bookarrow-up-right

  • Think Complexityarrow-up-right

  • All Algorithms implemented in Rustarrow-up-right

  • Solutions to Introduction to Algorithms bookarrow-up-right (Codearrow-up-right)

  • Maze Algorithms (2011)arrow-up-right (HNarrow-up-right)

  • Algorithmic Design Paradigms bookarrow-up-right (Codearrow-up-right)

  • Words and buttons Online Blogarrow-up-right (Codearrow-up-right)

  • Algorithms animatedarrow-up-right

  • Cache Oblivious Algorithms (2020)arrow-up-right (HNarrow-up-right)

  • You could have invented fractional cascading (2012)arrow-up-right

  • Guide to learning algorithms through LeetCodearrow-up-right (Codearrow-up-right) (HNarrow-up-right)

  • How hard is unshuffling a string?arrow-up-right

  • Optimization Algorithms on Matrix Manifoldsarrow-up-right

  • Problem Solving with Algorithms and Data Structuresarrow-up-right (HNarrow-up-right) (PDFarrow-up-right)

  • Algorithms implemented in Pythonarrow-up-right

  • Algorithms implemented in JavaScriptarrow-up-right

  • Algorithms & Data Structures in Javaarrow-up-right

  • Wolfsortarrow-up-right — Stable adaptive hybrid radix / merge sort.

  • Evolutionary Computation Bestiaryarrow-up-right — Bestiary of evolutionary, swarm and other metaphor-based algorithms.

  • Elements of Programming bookarrow-up-right — Decomposing programs into a system of algorithmic components. (Reviewarrow-up-right) (HNarrow-up-right) (Lobstersarrow-up-right)

  • Competitive Programming Algorithmsarrow-up-right

  • CPP/Carrow-up-right — C/C++ algorithms/DS problems.

  • How to design an algorithm (2018)arrow-up-right

  • CSE 373 — Introduction to Algorithms, by Steven Skiena (2020)arrow-up-right

  • Computer Algorithms II course (2020)arrow-up-right

  • Improving Binary Search by Guessing (2019)arrow-up-right

  • The case for a learned sorting algorithm (2020)arrow-up-right (HNarrow-up-right)

  • Elementary Algorithmsarrow-up-right — Introduces elementary algorithms and data structures. Includes side-by-side comparisons of purely functional realization and their imperative counterpart.

  • Combinatorics Algorithms for Coding Interviews (2018)arrow-up-right

  • Algorithms written in different programming languagesarrow-up-right (Webarrow-up-right)

  • Solving the Sequence Alignment problem in Python (2020)arrow-up-right

  • The Sound of Sortingarrow-up-right — Visualization and “Audibilization” of Sorting Algorithms. (Webarrow-up-right)

  • Miniselect: Practical and Generic Selection Algorithms (2020)arrow-up-right

  • The Slowest Quicksort (2019)arrow-up-right

  • Functional Algorithm Design (2020)arrow-up-right

  • Algorithms To Live By — Book Notesarrow-up-right

  • Numerical Algorithms (2015)arrow-up-right

  • Using approximate nearest neighbor search in real world applications (2020)arrow-up-right

  • In search of the fastest concurrent Union-Find algorithm (2019)arrow-up-right

  • Computer Science 521 Advanced Algorithm Designarrow-up-right

  • Phil’s Data Structure Zooarrow-up-right
  • The Periodic Table of Data Structuresarrow-up-right (HNarrow-up-right)

  • Data Structure Visualizationsarrow-up-right (HNarrow-up-right)

  • Data structures to name-drop when you want to sound smart in an interviewarrow-up-right

  • On lists, cache, algorithms, and microarchitecture (2019)arrow-up-right

  • Topics in Advanced Data Structures (2019)arrow-up-right (HNarrow-up-right)

  • CS166 Advanced DS Course (2019)arrow-up-right

  • Advanced Data Structures (2017)arrow-up-right (HNarrow-up-right)

  • Write a hash table in Carrow-up-right

  • Python Data Structures and Algorithmsarrow-up-right

  • HAMTs from Scratch (2018)arrow-up-right

  • JavaScript Data Structures and Algorithmsarrow-up-right

  • Implementing a Key-Value Store seriesarrow-up-right

  • Open Data Structuresarrow-up-right — Provide a high-quality open content data structures textbook that is both mathematically rigorous and provides complete implementations. (Codearrow-up-right)

  • A new analysis of the false positive rate of a Bloom filter (2009)arrow-up-right

  • Ideal Hash Treesarrow-up-right

  • RRB-Trees: Efficient Immutable Vectorsarrow-up-right

  • Some data structures and algorithms written in OCamlarrow-up-right

  • Let’s Invent B(+)-Treesarrow-up-right (HNarrow-up-right)

  • Annaarrow-up-right — Low-latency, cloud-native KVS.

  • Persistent data structures thanks to recursive type aliases (2019)arrow-up-right

  • Log-Structured Merge-Trees (2020)arrow-up-right

  • Bloom Filters for the Perplexed (2017)arrow-up-right

  • Understanding Bloom Filters (2020)arrow-up-right

  • Dense vs. Sparse Indexes (2020)arrow-up-right

  • Data Structures and Algorithms Problemsarrow-up-right

  • Data Structures & Algorithms I Actually Used Working at Tech Companies (2020)arrow-up-right (Lobstersarrow-up-right) (HNarrow-up-right)

  • Let’s implement a Bloom Filter (2020)arrow-up-right (HNarrow-up-right)

  • Data Structures Part 1: Bulk Data (2019)arrow-up-right (Lobstersarrow-up-right)

  • Data Structures Explainedarrow-up-right

  • Introduction to Cache-Oblivious Data Structures (2018)arrow-up-right

  • The Daily Coding newsletterarrow-up-right — Master JavaScript and Data Structures.

  • Lectures Note for Data Structures and Algorithms (2019)arrow-up-right

  • Mechanically Deriving Binary Tree Iterators with Continuation Defunctionalization (2020)arrow-up-right

  • Segment Tree data structurearrow-up-right

  • Structure of a binary state tree (2020)arrow-up-right

  • Introductory data structures and algorithmsarrow-up-right

  • Applying Textbook Data Structures for Real Life Wins (2020)arrow-up-right (HNarrow-up-right)

  • Michael Scott — Nonblocking data structures lectures (2020)arrow-up-right — Nonblocking concurrent data structures are an increasingly valuable tool for shared-memory parallel programming.

  • Scalarrow-up-right — High-performance multicore-scalable data structures and benchmarks. (Webarrow-up-right)

  • Hyperbolic embedding implementationsarrow-up-right

  • Morphisms of Computational Constructsarrow-up-right — Visual catalogue + story of morphisms displayed across computational structures.

  • What is key-value store? (build-your-own-x) (2020)arrow-up-right

  • Lesser Known but Useful Data Structuresarrow-up-right

  • Using Bloom filters to efficiently synchronize hash graphs (2020)arrow-up-right

  • Bloom Filters by Examplearrow-up-right (Codearrow-up-right)

  • Binary Decision Diagramsarrow-up-right (HNarrow-up-right)

  • 3 Steps to Designing Better Data Structures (2020)arrow-up-right

  • Sparse Matrices (2019)arrow-up-right (HNarrow-up-right)

  • Algorithms & Data Structures in C++arrow-up-right

  • Fancy Tree Traversals (2019)arrow-up-right

  • The Robson Tree Traversal (2019)arrow-up-right

  • Data structures and program structuresarrow-up-right

  • cdbarrow-up-right — Fast, reliable, simple package for creating and reading constant databases.

  • PGM-indexarrow-up-right — Learned indexes that match B-tree performance with 83x less space. (HNarrow-up-right) (Codearrow-up-right)

  • Structural and pure attributesarrow-up-right

  • Cache-Tries: O(1) Concurrent Lock-Free Hash Tries (2018)arrow-up-right

  • Dijkstra's algorithmarrow-up-right
    Calculate a Factorial With Python - Iterative and Recursivearrow-up-right
    DFSarrow-up-right
    Data Structures Overviewarrow-up-right
    General Data Structures Notesarrow-up-right
    DS-Explained-Simplearrow-up-right
    Abstract Data Structures:arrow-up-right
    Arrayarrow-up-right
    Extra-Arrayarrow-up-right
    GCA Sprint Prep:arrow-up-right
    Practice Problemsarrow-up-right
    Code Signal CGA Sprint Resourcesarrow-up-right
    arrow-up-right
    The Framework for Learning Algorithms and intense problem solving exercisesarrow-up-right
    Algs4: Recommended book for Learning Algorithms and Data Structuresarrow-up-right
    An analysis of Dynamic Programmingarrow-up-right
    100 days of algorithmsarrow-up-right
    Algorithmsarrow-up-right
    Algorithms by Jeff Ericksonarrow-up-right
    Codearrow-up-right
    HNarrow-up-right
    arrow-up-right
    Data Structures and Algorithms implementation in Goarrow-up-right
    Which algorithms/data structures should I “recognize” and know by name?arrow-up-right
    Dictionary of Algorithms and Data Structuresarrow-up-right

    Earliest Ancestorarrow-up-right
    _Mini Graph-Projectsarrow-up-right
    # Social Grapharrow-up-right
    number of 1 islandsarrow-up-right
    Graph FAQarrow-up-right
    Graph DFSarrow-up-right
    Connected Componentsarrow-up-right
    Randomnessarrow-up-right
    Graph BFSarrow-up-right
    Topological Sortarrow-up-right
    Dequeuearrow-up-right
    Ternary-search-treesarrow-up-right
    Red_Black Treearrow-up-right
    Tree Mirror:arrow-up-right
    Tree Traversalarrow-up-right
    List Examplearrow-up-right
    Examples (LL) continuedarrow-up-right
    List Operationsarrow-up-right
    Disjoint Setarrow-up-right
    Iterative Sortingarrow-up-right
    Recursive Sortingarrow-up-right
    Graph Topological Sortarrow-up-right
    SelectionSortarrow-up-right
    Quick Sortarrow-up-right
    Merge Sortarrow-up-right
    Insertion Sortarrow-up-right
    Interview Practice Resourcesarrow-up-right
    Write a Program to Find the Maximum Depth or Height of a Treearrow-up-right
    Random Examplesarrow-up-right
    Promptsarrow-up-right
    JS_BASICSarrow-up-right
    Searching and Generating Graphsarrow-up-right