Dynamic Programming Flashcards

1
Q
  1. Best Time to Buy and Sell Stock III
    Solved
    Hard
    Topics
    Companies

You are given an array prices where prices[i] is the price of a given stock on the ith day.

Find the maximum profit you can achieve. You may complete at most two transactions.

Note: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).

Example 1:

Input: prices = [3,3,5,0,0,3,1,4]
Output: 6
Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3.

Example 2:

Input: prices = [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again.

Example 3:

Input: prices = [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

Constraints:

1 <= prices.length <= 105
0 <= prices[i] <= 105

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/

A

Calculate max profit at every step from the back. I.e imagine going from i to n for each i (keep track of max seen so far instead of min in the first approach)

Calculate max profit at every step from front and from back
# Then sum them up, return the max.

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        trades = [0] * len(prices)
        buy = 0
        sell = 0
        r = 1
        while r < len(prices):
            profit = prices[r] - prices[buy]
            trades[r] = max(profit, trades[r-1])
            if prices[r] < prices[buy]:
                buy = r
            r += 1
        r = len(prices) - 2
        sell = r+1
        profit = 0
        while r >= 0:
            profit = max(profit, prices[sell] - prices[r])
            trades[r] = trades[r] + profit
            if prices[r] > prices[sell]:
                sell = r
            r-=1
        return max(trades)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q
  1. Best Time to Buy and Sell Stock with Cooldown
    Solved
    Medium
    Topics
    Companies

You are given an array prices where prices[i] is the price of a given stock on the ith day.

Find the maximum profit you can achieve. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times) with the following restrictions:

After you sell your stock, you cannot buy stock on the next day (i.e., cooldown one day).

Note: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).

Example 1:

Input: prices = [1,2,3,0,2]
Output: 3
Explanation: transactions = [buy, sell, cooldown, buy, sell]

Example 2:

Input: prices = [1]
Output: 0

Constraints:

1 <= prices.length <= 5000
0 <= prices[i] <= 1000

O(n), O(n) Expected.

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/

A

Memoization: O(n), O(n)
~~~
from functools import cache
class Solution:
def maxProfit(self, prices: List[int]) -> int:

    @cache
    def stock(i, buying):
        if i >= len(prices):
            return 0
        # do nothing.
        cooldown = stock(i+1, buying)
        if buying:
            # buy
            action = stock(i+1, not buying) - prices[i]
        else:
            #sell
            action = stock(i+2, not buying) + prices[i]
        return max(action, cooldown)
    
    return stock(0, True) ~~~

State machine O(n), O(n)
~~~
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# States:
# Held: Holding stock after buying, or just continue holding it from before.
# Sold: Sold the stock here after holding it.
# Cooldown: You either sold the stock before this, or you just keep on being in cooldown
# sold = held[i-1] + prices[i]
# held = max(held[i-1], cooldown[i-1] - prices[i])
# cooldown = max(cooldown[i-1], sold[i-1])
cooldown = 0
held = float(‘-inf’) # impossible to be in held at 0 since you haven’t bought yet.
sold = float(‘-inf’)
for i in range(len(prices)):
sold, cooldown, held = held + prices[i], max(cooldown, sold), max(held, cooldown - prices[i])
return max(sold, cooldown)
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q
  1. Climbing Stairs
    Solved
    Easy
    Topics
    Companies Amazon, FB
    Hint

You are climbing a staircase. It takes n steps to reach the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Example 1:

Input: n = 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: n = 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

Constraints:

1 <= n <= 45

https://leetcode.com/problems/climbing-stairs/description/

A

class Solution(object):
def climbStairs(self, n):
“””
:type n: int
:rtype: int
“””
if n < 3:
return n
step_1 = 1
step_2 = 2
for idx in range(2, n):
step_1, step_2 = step_2, (step_1 + step_2)
return step_2

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q
  1. Min Cost Climbing Stairs
    Solved
    Easy
    Topics
    Companies
    Hint

You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.

You can either start from the step with index 0, or the step with index 1.

Return the minimum cost to reach the top of the floor.

Example 1:

Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.

Example 2:

Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.

Constraints:

2 <= cost.length <= 1000
0 <= cost[i] <= 999

https://leetcode.com/problems/min-cost-climbing-stairs/description/

A

Trick here is to set the cost of reaching step 0 and 1 to 0 as we can start there without paying any cost
# When we need to leave the position we need to pay the cost, so we calculate cost to reach n-1 and n-2 + their respective costs to leave that spot.

class Solution(object):
def minCostClimbingStairs(self, cost):
“””
:type cost: List[int]
:rtype: int
“””
steps = [0] * (len(cost) + 1)
for n in range(2, len(cost) + 1):
steps[n] = min(steps[n-1] + cost[n-1], steps[n-2] + cost[n-2])
return steps[-1]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
  1. House Robber
    Solved
    Medium
    Topics
    Companies

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.

Example 2:

Input: nums = [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

Constraints:

1 <= nums.length <= 100
0 <= nums[i] <= 400 https://leetcode.com/problems/house-robber/description/
A

Either pick previous house, or amount of this house + the max so far till n-2
# init the 0 to nums 0 and 1 to max of nums 0 or 1 because if we only have 2 houses we can chose to pick the more loaded one.
class Solution(object):
def rob(self, nums):
“””
:type nums: List[int]
:rtype: int
“””
if len(nums) < 2:
return nums[0]
money = [0] * len(nums)
money[0] = nums[0]
money[1] = max(nums[1], nums[0])
for n in range(2, len(money)):
money[n] = max(money[n-1], money[n-2] + nums[n])
return money[-1]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q
  1. House Robber II
    Solved
    Medium
    Topics
    Companies
    Hint

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: nums = [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses.

Example 2:

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.

Example 3:

Input: nums = [1,2,3]
Output: 3

Constraints:

1 <= nums.length <= 100
0 <= nums[i] <= 1000

https://leetcode.com/problems/house-robber-ii/

A

Run house robber 198 on the list twice first, without 1st element and then without last element, return their max.

class Solution(object):
def rob(self, nums):
“””
:type nums: List[int]
:rtype: int
“””
if len(nums) < 2:
return nums[0]

    def solve(input):
        if len(input) < 2:
            return input[0]
        amount = [0] * len(input)
        amount[0] = input[0]
        amount[1] = max(input[1], input[0])
        for i in range(2, len(amount)):
            amount[i] = max(amount[i-1], amount[i-2] + input[i])
        return amount[-1]
    return max(solve(nums[:-1]), solve(nums[1:]))
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q
  1. Longest Palindromic Substring

Medium
Topics
Companies Amazon, Google

Given a string s, return the longest
palindromic
substring
in s.

Example 1:

Input: s = “babad”
Output: “bab”
Explanation: “aba” is also a valid answer.

Example 2:

Input: s = “cbbd”
Output: “bb”

Constraints:

1 <= s.length <= 1000
s consist of only digits and English letters.

https://leetcode.com/problems/longest-palindromic-substring/description/

A

2D array, mark true if palindrome exists from that point outwards.

Check from center outwards

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        ans = (0,0)
        ans_len = 1
        def palin(i,j):
            while i > 0 and j < n-1 and s[i-1] == s[j+1]:
                i -= 1
                j += 1
            return (i, j)
        
        for i in range(0, n-1):
            # odd
            (a,b) = palin(i,i)
            if (b-a+1) > ans_len:
                ans_len = b-a+1
                ans = (a,b)
            if s[i] == s[i+1]:
                (c,d) = palin(i,i+1)
                if (d-c+1) > ans_len:
                    ans_len = d-c+1
                    ans = (c,d)
        return s[ans[0]:ans[1]+1]

Dynamic Programming
# start with diff = 2 and go upwards#
eg. compare 0 and 1 if they are equlal check the previous element from 0 and 1 if they are palindrome
eg abllb at diff = 3 , ll is true (2,3) and i = 1
s[i] = b , s[i+diff] =b
dp[i+1][i-diff-1] is true (L,L is a palindrome)

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        dp = [[False] * n for i in range(n)]
        ans = (0,0)

        for i in range(n):
            dp[i][i] = True
        for i in range(n-1):
            if s[i] == s[i+1]:
                dp[i][i+1] = True
                ans = (i, i+1)
        for diff in range(2, n):
            for i in range(0, n-diff):
                j = i + diff
                if s[i] == s[j] and dp[i+1][j-1]:
                    dp[i][j] = True
                    ans = (i, j)
        return s[ans[0]:ans[1] + 1]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q
  1. Palindromic Substrings
    Solved
    Medium
    Topics
    Companies
    Hint

Given a string s, return the number of palindromic substrings in it.

A string is a palindrome when it reads the same backward as forward.

A substring is a contiguous sequence of characters within the string.

Example 1:

Input: s = “abc”
Output: 3
Explanation: Three palindromic strings: “a”, “b”, “c”.

Example 2:

Input: s = “aaa”
Output: 6
Explanation: Six palindromic strings: “a”, “a”, “a”, “aa”, “aa”, “aaa”.

Constraints:

1 <= s.length <= 1000
s consists of lowercase English letters.

https://leetcode.com/problems/palindromic-substrings/description/

A

class Solution(object):
def countSubstrings(self, s):
“””
:type s: str
:rtype: int
“””
n = len(s)
palins = 0

    def palin(i,j):
        palins = 1
        while (i > 0 and j < len(s) - 1) and s[i-1] == s[j+1]:
            i -= 1
            j += 1
            palins += 1
        return palins
    
    for start in range(0, n-1):
        # odd
        odd = palin(start, start)
        palins += odd
        if s[start] == s[start+1]:
            even = palin(start, start+1)
            palins += even
    return palins + 1

using the same DP as Q5

class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
dp = [[False] * n for i in range(n)]
palins = 0

    for i in range(n):
        dp[i][i] = True
        palins += 1
    for i in range(n-1):
        if s[i] == s[i+1]:
            palins += 1
            dp[i][i+1] = True
    
    for diff in range(2, n):
        for i in range(0, n-diff):
            j = i+diff
            if s[i] == s[j] and dp[i+1][j-1]:
                dp[i][j] = True
                palins += 1
    return palins
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q
  1. Decode Ways
    Solved
    Medium
    Topics
    Companies

A message containing letters from A-Z can be encoded into numbers using the following mapping:

‘A’ -> “1”
‘B’ -> “2”

‘Z’ -> “26”

To decode an encoded message, all the digits must be grouped then mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, “11106” can be mapped into:

"AAJF" with the grouping (1 1 10 6)
"KJF" with the grouping (11 10 6)

Note that the grouping (1 11 06) is invalid because “06” cannot be mapped into ‘F’ since “6” is different from “06”.

Given a string s containing only digits, return the number of ways to decode it.

The test cases are generated so that the answer fits in a 32-bit integer.

Example 1:

Input: s = “12”
Output: 2
Explanation: “12” could be decoded as “AB” (1 2) or “L” (12).

Example 2:

Input: s = “226”
Output: 3
Explanation: “226” could be decoded as “BZ” (2 26), “VF” (22 6), or “BBF” (2 2 6).

Example 3:

Input: s = “06”
Output: 0
Explanation: “06” cannot be mapped to “F” because of the leading zero (“6” is different from “06”).

Constraints:

1 <= s.length <= 100
s contains only digits and may contain leading zero(s).

https://leetcode.com/problems/decode-ways/description/

A

Start from the end,
11106
dp will look somewhat like
[0, 0, 0, 0, 0, 1, 0]
The second last element will be the ways to arrange the last element.
For every element we move back, we check
a) If its 0 then we mark its place as zero (n1)
b) if it, and its next element combined make a number between 10-26, if so we take the n+1 and n+2 as the value at that otherwise just n+1’s value.
This is because if eg. number was 52834,
for element 2 we have 2 choices either pick 2 and dp at 8 or 28 + dp at 3 since 28 is invalid we just use the DP at 8.
Here n1 = n+1
n2 = n+2 to save space
otherwise you can create an array of len(n+1)
with element at n = 1.

class Solution:
    def numDecodings(self, s: str) -> int:
        # DP bottom up
        n1 = 1
        n2 = 0
        n = len(s)
        digits = set(["0","1","2","3","4","5","6"])
        res = 0
        for i in range(n-1, -1, -1):
            if s[i] == "0":
                n1,n2 = 0, n1
                continue
            if (i < n-1 and (s[i] == '1' or (s[i] == '2' and s[i+1] in digits))):
                n1, n2 = n1 + n2, n1
            else:
                n1, n2 = n1, n1
        return n1

Other solution goes from front to back.
~~~
class Solution:
def numDecodings(self, s: str) -> int:
# Array to store the subproblem results
dp = [0 for _ in range(len(s) + 1)]

    dp[0] = 1
    # Ways to decode a string of size 1 is 1. Unless the string is '0'.
    # '0' doesn't have a single digit decode.
    dp[1] = 0 if s[0] == "0" else 1

    for i in range(2, len(dp)):

        # Check if successful single digit decode is possible.
        if s[i - 1] != "0":
            dp[i] = dp[i - 1]

        # Check if successful two digit decode is possible.
        two_digit = int(s[i - 2 : i])
        if two_digit >= 10 and two_digit <= 26:
            dp[i] += dp[i - 2]

    return dp[len(s)] ~~~ Same as above but O(1) memory  ~~~ class Solution(object):
def numDecodings(self, s):
    """
    :type s: str
    :rtype: int
    """
    f0 = 1
    f1 = 0 if s[0] == "0" else 1
    for i in range(2, len(s)+1):
        temp = 0
        if s[i-1] != "0":
            temp = f1
        if 10 <= int(s[i-2:i]) < 27:
            temp += f0
        f0,f1 = f1, temp
    return f1  ```		

Neetcode Solution
~~~
class Solution:
def numDecodings(self, s: str) -> int:
dp = {len(s):1}
def backtrack(i):
if i in dp:
return dp[i]

        if s[i]=='0':
            return 0

        if i==len(s)-1:
            return 1

        res = backtrack(i+1)
        if int(s[i:i+2])<27:
            res+=backtrack(i+2)
        dp[i]=res
        return res
    return backtrack(0) ~~~

Neetcode without recursion

class Solution:
    def numDecodings(self, s: str) -> int:

        dp = { len(s) : 1}

        for i in range(len(s) -1 , -1, -1):
            if s[i] == '0':
                dp[i] = 0
            else:
                dp[i] = dp[i + 1] 
            
            if i + 1 < len(s) and (s[i] == '1' or (s[i] == '2' and s[i + 1] in  "0123456")):
                dp[i] = dp[i] + dp[i + 2]
        
        return dp[0]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q
  1. Coin Change
    Solved
    Medium
    Topics
    Companies

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

Return 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.

You may assume that you have an infinite number of each kind of coin.

Example 1:

Input: coins = [1,2,5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
Output: -1

Example 3:

Input: coins = [1], amount = 0
Output: 0

Constraints:

1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104

https://leetcode.com/problems/coin-change/description/

A

class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
coins = sorted(coins)
dp = [0] * (amount + 1)
for i in range(1, amount+1):
c = 0
mins = float(‘inf’)
while c < len(coins) and coins[c] <= i:
mins = min(mins, dp[i-coins[c]] + 1)
c += 1
dp[i] = mins
return -1 if dp[amount] == float(‘inf’) else dp[amount]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q
  1. Maximum Product Subarray
    Solved
    Medium
    Topics
    Companies

Given an integer array nums, find a
subarray
that has the largest product, and return the product.

The test cases are generated so that the answer will fit in a 32-bit integer.

Example 1:

Input: nums = [2,3,-2,4]
Output: 6
Explanation: [2,3] has the largest product 6.

Example 2:

Input: nums = [-2,0,-1]
Output: 0
Explanation: The result cannot be 2, because [-2,-1] is not a subarray.

Constraints:

1 <= nums.length <= 2 * 104
-10 <= nums[i] <= 10
The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.

https://leetcode.com/problems/maximum-product-subarray/description/

A

The key here is to keep track of min so far and max so far.
# reason being, min can be flipped to max with a negative number in the future.

class Solution:
def maxProduct(self, nums: List[int]) -> int:
max_so_far = nums[0]
min_so_far = nums[0]
max_total = nums[0]
for i in range(1, len(nums)):
cur = nums[i]
max_so_far, min_so_far = max(cur, cur * max_so_far, cur * min_so_far), min(cur, cur * max_so_far, cur * min_so_far)
max_total = max(max_so_far, max_total)
return max_total

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q
  1. Word Break
    Solved
    Medium
    Topics
    Companies

Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.

Note that the same word in the dictionary may be reused multiple times in the segmentation.

Example 1:

Input: s = “leetcode”, wordDict = [“leet”,”code”]
Output: true
Explanation: Return true because “leetcode” can be segmented as “leet code”.

Example 2:

Input: s = “applepenapple”, wordDict = [“apple”,”pen”]
Output: true
Explanation: Return true because “applepenapple” can be segmented as “apple pen apple”.
Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = “catsandog”, wordDict = [“cats”,”dog”,”sand”,”and”,”cat”]
Output: false

Constraints:

1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s and wordDict[i] consist of only lowercase English letters.
All the strings of wordDict are unique.

https://leetcode.com/problems/word-break/description/

A

bottom up, start from the end and work way up. Similar to longest word without using banned characters.

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n = len(s)
        dp = [False] * (n+1)
        dp[-1] = True
        wordDict = set(wordDict)
        for i in range(n-1, -1, -1):
            for j in range(i, n+1):
                if dp[j] and s[i:j] in wordDict:
                    dp[i] = True
        return dp[0]  
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n = len(s)
        dp = [False] * (n+1)
        dp[-1] = True
        wordDict = set(wordDict)
        for i in range(n-1, -1, -1):
            for j in range(n, i, -1):
                if dp[j] and s[i:j] in wordDict:
                    dp[i] = True
        return dp[0]

Using Trie

from collections import defaultdict
class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        Trie = lambda: defaultdict(Trie)
        trie = Trie()
        for word in wordDict:
            cur = trie
            for c in word:
                cur = cur[c]
            cur["$"] = True
        
        n = len(s)
        dp = [False] * (n+1)
        dp[n] = True
        for i in range(n-1, -1, -1):
            cur = trie
            for j in range(i, n):
                if s[j] not in cur:
                    break
                cur = cur[s[j]]
                if cur["$"] == True and dp[j+1] == True:
                    dp[i] = True
                    break
        return dp[0]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q
  1. Word Break II
    Hard
    Companies: Facebook 2024

Given a string s and a dictionary of strings wordDict, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences in any order.

Note that the same word in the dictionary may be reused multiple times in the segmentation.

Example 1:

Input: s = “catsanddog”, wordDict = [“cat”,”cats”,”and”,”sand”,”dog”]
Output: [“cats and dog”,”cat sand dog”]

Example 2:

Input: s = “pineapplepenapple”, wordDict = [“apple”,”pen”,”applepen”,”pine”,”pineapple”]
Output: [“pine apple pen apple”,”pineapple pen apple”,”pine applepen apple”]
Explanation: Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = “catsandog”, wordDict = [“cats”,”dog”,”sand”,”and”,”cat”]
Output: []

Constraints:

1 <= s.length <= 20
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 10
s and wordDict[i] consist of only lowercase English letters.
All the strings of wordDict are unique.
Input is generated in a way that the length of the answer doesn't exceed 105.

https://leetcode.com/problems/word-break-ii/

A
from functools import cache, reduce
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
        output = []
        maxLen = reduce(lambda mlen,x: max(len(x), mlen), wordDict, 0)
        wordDict = set(wordDict)

        @cache
        def rec(i):
            if i == len(s):
                return [""]
            
            out = []
            for j in range(i+1, min(i+maxLen, len(s))+1):
                word = s[i:j]
                if word in wordDict:
                    ret = rec(j)
                    for subwords in ret:
                        if subwords:
                            out.append(" ".join([word, subwords]))
                        else:
                            out.append(word)
            return out
        return rec(0)

O(n2^n), 2^n

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q
  1. Longest Increasing Subsequence
    Solved
    Medium
    Topics
    Companies Amazon, Google

Given an integer array nums, return the length of the longest strictly increasing
subsequence
.

Example 1:

Input: nums = [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.

Example 2:

Input: nums = [0,1,0,3,2,3]
Output: 4

Example 3:

Input: nums = [7,7,7,7,7,7,7]
Output: 1

Constraints:

1 <= nums.length <= 2500
-104 <= nums[i] <= 104

Follow up: Can you come up with an algorithm that runs in O(n log(n)) time complexity?

https://leetcode.com/problems/longest-increasing-subsequence/description/

A

DP n^2 Solution
Start from the end and begin building the memory

class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
seq = [nums[0]]
n = len(nums)
for num in nums[1:]:
i = 0
while i < len(seq) and num > seq[i]:
i += 1
if i + 1 > len(seq):
seq.append(num)
else:
seq[i] = num
return len(seq)

N^2 solution no DP, Build the sequence likea monotonic increasing stack.
BUT instead of going from the top, start from the bottom, and if you see a value smaller than this value, replace it with the incming value.
THIS wont result in a correct sequence BUT will result in the correct count, as the size of sequence will only grow if we see value larger than largest. even if the middle valuesare incorrect, we know there are values smaller than the largest in the past.

eg.
9,10,2,100, 5
seq is 9, 10, 100
9
9, 10
2, 10
2, 10, 100
2, 5, 100 <– this is incorrect sequence BUT the length is still valid.
to get correct sequence you can’t use this method.
This method can be optimized by using binary search in the sequence to find insertion point.
thus making it nLogn

The nLog(n) Solution

class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
seq = [nums[0]]
n = len(nums)
for num in nums[1:]:
if num > seq[-1]:
seq.append(num)
else:
insertion_point = bisect.bisect_left(seq, num)
seq[insertion_point] = num
return len(seq)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q
  1. Partition Equal Subset Sum
    Solved
    Medium
    Topics
    Companies

Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise.

Example 1:

Input: nums = [1,5,11,5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: nums = [1,2,3,5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

Constraints:

1 <= nums.length <= 200
1 <= nums[i] <= 100

https://leetcode.com/problems/partition-equal-subset-sum/description/

A

Create a set of sums with the last value in the nums

Run through all values except last one from back to start
Then to the set add all the values with the sum of current value + values in set.

This is the same as building combinations for all n i.e from 1Cn to nCn.
eg 1345
1, 2, 3, 4
12, 13, 14, 23, 24, 34
123, 124, 134, 234
1234

class Solution(object):
def canPartition(self, nums):
“””
:type nums: List[int]
:rtype: bool
“””
sums = set()
sums.add(nums[-1])
n = len(nums)
total_sum = sum(nums)
if total_sum % 2:
return False
target = total_sum // 2
for i in range(n-2, -1, -1):
tset = sums.copy()
for n in tset:
sums.add(nums[i] + n)
if target in sums:
return True
return target in sums

DP – dont like this much but good to talk about in interview
class Solution:
def canPartition(self, nums: List[int]) -> bool:

    @lru_cache(maxsize=None)
    def dfs(nums, target, idx):
        if target == 0:
            return True
        if idx == 0 or target < 0:
            return False
        
        return dfs(nums, target - nums[idx-1], idx-1) or dfs(nums, target, idx-1)
    
    total_sum = sum(nums)
    if total_sum % 2:
        # Odd sum cant split evenly
        return False
    target = total_sum // 2
    n = len(nums)
    return dfs(tuple(nums), target, n-1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q
  1. Coin Change II
    Solved
    Medium
    Topics
    Companies

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

Return the number of combinations that make up that amount. If that amount of money cannot be made up by any combination of the coins, return 0.

You may assume that you have an infinite number of each kind of coin.

The answer is guaranteed to fit into a signed 32-bit integer.

Example 1:

Input: amount = 5, coins = [1,2,5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

Example 2:

Input: amount = 3, coins = [2]
Output: 0
Explanation: the amount of 3 cannot be made up just with coins of 2.

Example 3:

Input: amount = 10, coins = [10]
Output: 1

Constraints:

1 <= coins.length <= 300
1 <= coins[i] <= 5000
All the values of coins are unique.
0 <= amount <= 5000

https://leetcode.com/problems/coin-change-ii/description/

A

from functools import cache, lru_cache
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
@cache
def rec(idx, amt):
if idx >= len(coins) or amt < 0:
return 0
if amt == 0:
return 1
return rec(idx, amt - coins[idx]) + rec(idx + 1, amt)
ans = rec(0, amount)
return ans

Same as above just using matrix
# dp[a][c] = dp[a][c+1] is not using this coin
# dp[a][c] += dp[a-coins[c]][c] using this coin, and finding how many ways the remaining amount can be reached using this coin again. ( Same as idx, amt - coins[idx]
.

class Solution(object):
def change(self, amount, coins):
“””
:type amount: int
:type coins: List[int]
:rtype: int
“””
dp = [[0] * (len(coins) + 1) for _ in range(amount + 1)]
dp[0] = [1] * (len(coins) + 1)

    for a in range(1, amount + 1):
        for c in range(len(coins) - 1, -1, -1):
            dp[a][c] = dp[a][c+1]
            if a-coins[c] >= 0:
                dp[a][c] += dp[a-coins[c]][c]
    return dp[amount][0]
17
Q
  1. Longest Common Subsequence
    Solved
    Medium
    Topics
    Companies
    Hint

Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

For example, "ace" is a subsequence of "abcde".

A common subsequence of two strings is a subsequence that is common to both strings.

Example 1:

Input: text1 = “abcde”, text2 = “ace”
Output: 3
Explanation: The longest common subsequence is “ace” and its length is 3.

Example 2:

Input: text1 = “abc”, text2 = “abc”
Output: 3
Explanation: The longest common subsequence is “abc” and its length is 3.

Example 3:

Input: text1 = “abc”, text2 = “def”
Output: 0
Explanation: There is no such common subsequence, so the result is 0.

Constraints:

1 <= text1.length, text2.length <= 1000
text1 and text2 consist of only lowercase English characters.

https://leetcode.com/problems/longest-common-subsequence/description/

A

Subproblems
Start at the end of both strings,
if the letters are equal, then you find the value of the previous subproblem (n+1, i+1) and increment 1 to it.
If not then you find if the max of subproblem of string a or subproblem string b

eg . ABCDE ACE
If you just take E from both, you put 1 in the DP Matrix location.

Now if you notice, no matter if you pick ABCDE or BCDE, CDE and compare it with the subproblem “E” from ACE the length will always be 1. Same can be said about ABCDE if you just take E and compare it with ACE.

from functools import cache

class Solution(object):
    def longestCommonSubsequence(self, text1, text2):
        """
        :type text1: str
        :type text2: str
        :rtype: int
        """
        @cache
        def rec(a, b):
            if a >= len(text1) or b >= len(text2):
                return 0
            if text1[a] == text2[b]:
                return 1 + rec(a+1, b+1)
            else:
                return max(rec(a + 1, b), rec(a, b+1))
        
        return rec(0, 0)

DP Solution
~~~
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
rows = len(text1)
cols = len(text2)
matrix = [[0 for c in range(cols+1)] for r in range(rows+1)]
for r in reversed(range(rows)):
for c in reversed(range(cols)):
col = c+1
row = r+1
if text1[r] == text2[c]:
matrix[r][c] = 1 + matrix[row][col]
else:
matrix[r][c] = max(matrix[row][c], matrix[r][col])
return matrix[0][0]
~~~

18
Q
  1. Target Sum
    Solved
    Medium
    Topics
    Companies

You are given an integer array nums and an integer target.

You want to build an expression out of nums by adding one of the symbols ‘+’ and ‘-‘ before each integer in nums and then concatenate all the integers.

For example, if nums = [2, 1], you can add a '+' before 2 and a '-' before 1 and concatenate them to build the expression "+2-1".

Return the number of different expressions that you can build, which evaluates to target.

Example 1:

Input: nums = [1,1,1,1,1], target = 3
Output: 5
Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

Example 2:

Input: nums = [1], target = 1
Output: 1

Constraints:

1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000

https://leetcode.com/problems/target-sum/

A
from functools import cache
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:      
        @cache
        def rec(idx, amt):
            if idx == len(nums):
                return 1 if amt == target else 0
            return rec(idx + 1, amt - nums[idx]) + rec(idx + 1, amt + nums[idx])
        
        return rec(0, 0)

O(n.t), O(n.t)

19
Q
  1. Longest Palindromic Subsequence
    Medium
    Companies Facebook 2024

Given a string s, find the longest palindromic subsequence’s length in s.

A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.

Example 1:

Input: s = “bbbab”
Output: 4
Explanation: One possible longest palindromic subsequence is “bbbb”.

Example 2:

Input: s = “cbbd”
Output: 2
Explanation: One possible longest palindromic subsequence is “bb”.

Constraints:

1 <= s.length <= 1000
s consists only of lowercase English letters.

O(n^2), O(n^2) follow up: do it in O(n) space.

https://leetcode.com/problems/longest-palindromic-subsequence/

A

Optimized DP O(n^2) time O(n) space
~~~
class Solution(object):
def longestPalindromeSubseq(self, s):
“””
:type s: str
:rtype: int
“””
n = len(s)
top = [0 for _ in range(n)]
cur = [0 for _ in range(n)]
for i in range(n-1, -1, -1):
cur[i] = 1
for j in range(i+1, n):
if s[i] == s[j]:
cur[j] = top[j-1] + 2
else:
cur[j] = max(cur[j-1], top[j])
top = cur[:]
return cur[-1]
~~~

Using Cache On^2 time memory
~~~
from functools import cache
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:

    @cache
    def rec(i,j):
        if i == len(s) or j < 0 or i > j:
            return 0
        if i == j:
            return 1
        if s[i] == s[j]:
            if j-i < 2:
                return 2
            return 2 + rec(i+1, j-1)
        else:
            return max(rec(i, j-1), rec(i+1, j))
    return rec(0, len(s)-1) ~~~
20
Q
  1. Valid Palindrome III
    Hard
    Facebook 2024

Given a string s and an integer k, return true if s is a k-palindrome.

A string is k-palindrome if it can be transformed into a palindrome by removing at most k characters from it.

Example 1:

Input: s = “abcdeca”, k = 2
Output: true
Explanation: Remove ‘b’ and ‘e’ characters.

Example 2:

Input: s = “abbababa”, k = 1
Output: true

Constraints:

1 <= s.length <= 1000
s consists of only lowercase English letters.
1 <= k <= s.length

O(n^2), O(n^2)

https://leetcode.com/problems/valid-palindrome-iii/description/

A

Find longest common palindromic subsequence and return len - n <= k

from functools import cache
class Solution:
    def isValidPalindrome(self, s: str, k: int) -> bool:
        n = len(s)
        dp = [0 for _ in range(n)]
        prev = dp[:]

        for i in range(n-1, -1, -1):
            dp[i] = 1
            for j in range(i+1, n):
                if s[i] == s[j]:
                    dp[j] = prev[j-1] + 2
                else:
                    dp[j] = max(prev[j], dp[j-1])
            prev = dp[:]
        return (n - dp[-1]) <= k
21
Q
  1. Longest Palindromic Subsequence II
    Solved
    Medium
    Topics
    Companies
    Hint
    A subsequence of a string s is considered a good palindromic subsequence if:

It is a subsequence of s.
It is a palindrome (has the same value if reversed).
It has an even length.
No two consecutive characters are equal, except the two middle ones.
For example, if s = “abcabcabb”, then “abba” is considered a good palindromic subsequence, while “bcb” (not even length) and “bbbb” (has equal consecutive characters) are not.

Given a string s, return the length of the longest good palindromic subsequence in s.

Example 1:

Input: s = “bbabab”
Output: 4
Explanation: The longest good palindromic subsequence of s is “baab”.
Example 2:

Input: s = “dcbccacdb”
Output: 4
Explanation: The longest good palindromic subsequence of s is “dccd”.

Constraints:

1 <= s.length <= 250
s consists of lowercase English letters.

(feel free to skip)

https://leetcode.com/problems/longest-palindromic-subsequence-ii/description/

A
class Solution(object):
    def longestPalindromeSubseq(self, s):
        """
        :type s: str
        :rtype: int
        """
        n = len(s)
        dp = [0] * n
        for i in range(n-1, -1,-1):
            max_valid = 0
            for j in range(i+1, n):
                if s[i] != s[j]:
                    # since they do not match, find if there was a palindrome at this point using the char at j, update max_valid
                    max_valid = max(max_valid, dp[j])
                else:
                    # ok so this matched, now if we have a max palindrome without using the current char, add its length
                    dp[j] = max_valid + 2
        return max(dp)

More intuitive

from functools import lru_cache

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        
        @lru_cache(maxsize=None)
        def rec(i, j, prev_match_char):
            if i >= j or i == len(s) or j < 0:
                return 0
            if s[i] == s[j] != prev_match_char:
                return 2 + rec(i+1, j-1, s[i])
            else:
                return max(rec(i+1, j, prev_match_char), rec(i, j-1, prev_match_char))
        return rec(0, len(s)-1, '')
22
Q
  1. Find the Index of the First Occurrence in a String
    Solved
    Easy
    Topics
    Companies
    Given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

Example 1:

Input: haystack = “sadbutsad”, needle = “sad”
Output: 0
Explanation: “sad” occurs at index 0 and 6.
The first occurrence is at index 0, so we return 0.
Example 2:

Input: haystack = “leetcode”, needle = “leeto”
Output: -1
Explanation: “leeto” did not occur in “leetcode”, so we return -1.

Constraints:

1 <= haystack.length, needle.length <= 104
haystack and needle consist of only lowercase English characters.

O(nxm), O(1) -> Follow up: can you do it in O(n+m) time? (KMP Algo)

https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

A

O(n^2) Solutions

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """

        h = len(haystack)
        n = len(needle)
        if h < n:
            return -1
        i,j = 0,0
        while i < (h-n+1):
            while j < n and i < h and needle[j] == haystack[i]:
                j+=1
                i+=1
            if j == n:
                return i-j
            i = (i-j+1)
            j=0
        return -1
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        m = len(needle)
        n = len(haystack)

        for window_start in range(n - m + 1):
            for i in range(m):
                if needle[i] != haystack[window_start + i]:
                    break
                if i == m - 1:
                    return window_start

        return -1

KMP (O(n))
~~~
class Solution:
def strStr(self, haystack: str, needle: str) -> int:

    def lcs(s):
        ret = [0] * len(s)
        lcs_idx = 0
        for i in range(1, len(s)):
            while lcs_idx != 0 and s[i] != s[lcs_idx]:
                lcs_idx = ret[lcs_idx-1]
                ret[i] = lcs_idx
            if s[lcs_idx] == s[i]:
                lcs_idx += 1
                ret[i] = lcs_idx
        return ret
    
    lcs_arr = lcs(needle)
    print(lcs_arr)

    hay_len = len(haystack)
    n_idx = 0

    for h_idx in range(hay_len):
        while n_idx != 0 and haystack[h_idx] != needle[n_idx]:
            n_idx = lcs_arr[n_idx-1]
        if haystack[h_idx] == needle[n_idx]:
            n_idx += 1
            if n_idx == len(needle):
                return h_idx-n_idx+1
    return -1

~~~