First, the question is asking for the maximum or minimum of something. Second, we have to make decisions that may depend on previously made decisions, which is very typical of a problem involving subsequences.

Top 5 DP Patterns

https://www.youtube.com/watch?v=mBNrRy2_hVs&list=WL

  • Fib
  • 0/1 Knapsack
  • Unbounded Knapsack
  • Longest Common Subsequence
  • Pallendromes

Fib DP

unordered_map<int, int> memo;

int fib(int n) {
    if (memo.find(n) != memo.end())
        return memo[n];
    if (n==0) return 0;
    if (n==1) return 1;
    int f = fib(n - 1) + fib(n - 2);
    memo[n] = f;
    return f;
}

Grid Traveler

m-1 and n-1 because target is at 1,1 and 0 or 0 is out of bounds.

https://leetcode.com/problems/unique-paths/

int memo[101][101];

int helper(int m, int n) {
    if (memo[m][n] != -1) return memo[m][n];
    if (m==1 && n==1) return 1;
    if (m==0 || n==0) return 0;

    int r = helper(m - 1, n) + helper(m, n - 1);
    memo[m][n] = r;
    return r;
}

int uniquePaths(int m, int n) {
    for (int i=0; i<101; i++)
        for (int j=0; j<101;j++)
            memo[i][j] = -1;
    return helper(m, n);
}

Climbing Stairs (steps)

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

array<int, 45> memo;

int climb_Stairs(int i, int n)  {
    if (i > n) {
        return 0;
    }
    if (i == n) {
        return 1;
    }
    if (memo[i] > 0) {
        return memo[i];
    }
    memo[i] = climb_Stairs(i + 1, n) + climb_Stairs(i + 2, n);
    return memo[i];
}

int climbStairs(int n) {
    return climb_Stairs(0, n);
}

Can Sum

var memo = {}

canSum = (targetSum, numbers) => {
  if (targetSum in memo) return memo[targetSum];
  if (targetSum === 0) return true;
  if (targetSum < 0) return false;

  for (let num of numbers) {
    const rem = targetSum - num;
    if (canSum(rem, numbers) === true) {
      memo[targetSum] = true;
      return true;
    }
  }
  memo[targetSum] = false;
  return false;
}

How Sum

var memo = {};

howSum = (targetSum, numbers) => {
    if (targetSum in memo) return memo[targetSum];
    if (targetSum === 0) return [];
    if (targetSum < 0) return false;

    for (let num of numbers) {
        const rem = targetSum - num;
        const remResult = howSum(rem, numbers);

        if (remResult != null) {
            memo[targetSum] = [...remResult, num];
            return memo[targetSum];
        }
    }
    memo[targetSum] = null;
    return null;
}

Coin Change

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

int min_count = INT_MAX;

void helper(vector<int>& coins, int amount, int depth) {
    if (amount == 0) {
        min_count = min(depth, min_count);
    };

    if (amount < 0) return;

    for (auto& c : coins) {
        int a = amount - c;
        helper(coins, a, depth + 1);
    }
}

int coinChange(vector<int>& coins, int amount) {
    helper(coins, amount, 0);
    return min_count == INT_MAX ? -1 : min_count;
}

map<int, int> memo;

int helper(vector<int>& coins, int amount, int depth) {
    if (amount == 0) {
        return 0;
    };

    if (amount < 0) {
        return -1;
    }

    if (memo.find(amount) != memo.end()) {
        return memo[amount];
    }

    int mn = INT_MAX;

    for (auto& c : coins) {
        int r = helper(coins, amount - c, depth + 1);
        if (r >= 0 && r < mn) {
            mn = 1 + r;
        }
    }

    memo[amount] = mn == INT_MAX ? -1 : mn;
    return memo[amount];
}

int coinChange(vector<int>& coins, int amount) {
    if (amount < 1) return 0;
    return helper(coins, amount, 0);
}

Decode Ways

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

vector<string> grammar;
map<string, int> memo;

bool isPrefix(string& a, string& b) {
    auto res = std::mismatch(a.begin(), a.end(), b.begin());
    return res.first == a.end();
}

int decode(string s) {
    if (s.length() == 0) {
        return 0;
    }
    if (memo.find(s) != memo.end()) {
        return memo[s];
    }

    int count = 0;
    for (auto& p : grammar) {
        if (isPrefix(p, s)) {
            if (s.length() == p.length()) {
                count++;
            }
            else {
                count += decode(s.substr(p.length()));
            }
        }
    }

    memo[s] = count;
    return count;
}

int numDecodings(string s) {
    for (int i = 1; i <= 26; i++) {
        grammar.push_back(to_string(i));
    }
    return decode(s);
}

Longest Increasing Subsequence

DP, size of nums, init to 1. The minimum LIS from all values is 1. Quadratic loop i,j+1 max. Return longest from DP. https://leetcode.com/problems/longest-increasing-subsequence/

[10,9,2,5,3,7,101,18]

int lengthOfLIS(vector<int>& nums) {
    vector<int> dp(nums.size());
    for (int i = 0; i < nums.size(); i++)
        dp[i] = 1;

    for (int i = nums.size()-1; i >=0 ; i--) {
        for (int j = i+1; j < nums.size(); j++) {
            if (nums[i] < nums[j]) {
                dp[i] = std::max(dp[i], dp[j] + 1);
            }
        }
    }

    int longest = 0;
    for (int i = 0; i < nums.size(); i++) {
        longest = std::max(longest, dp[i]);
    }
    return longest;
}

Longest Common Subsequence

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

int lcs(string X, string Y, int m, int n) {
    if (m == 0 || n == 0)
        return 0;

    if (X[m - 1] == Y[n - 1])
        return 1 + lcs(X, Y, m - 1, n - 1);
    else
        return max(lcs(X, Y, m, n - 1),
                   lcs(X, Y, m - 1, n));
}

int lcs(string X, string Y, int m, int n, int dp[][maximum]) {
    // base case
    if (m == 0 || n == 0)
        return 0;

    // if the same state has already been
    // computed
    if (dp[m - 1][n - 1] != -1)
        return dp[m - 1][n - 1];

    // if equal, then we store the value of the
    // function call
    if (X[m - 1] == Y[n - 1]) {
        // store it in arr to avoid further repetitive
        // work in future function calls
        dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);
        return dp[m - 1][n - 1];
    }
    else {
        // store it in arr to avoid further repetitive
        // work in future function calls
        dp[m - 1][n - 1] = max(lcs(X, Y, m, n - 1, dp),
                               lcs(X, Y, m - 1, n, dp));
        return dp[m - 1][n - 1];
    }
}

Combination Sum IV

https://leetcode.com/problems/combination-sum-iv

class Solution {
    private HashMap<Integer, Integer> memo;

    public int combinationSum4(int[] nums, int target) {
        // minor optimization
        // Arrays.sort(nums);
        memo = new HashMap<>();
        return combs(nums, target);
    }

    private int combs(int[] nums, int remain) {
        if (remain == 0)
            return 1;
        if (memo.containsKey(remain))
            return memo.get(remain);

        int result = 0;
        for (int num : nums) {
            if (remain - num >= 0)
                result += combs(nums, remain - num);
            // minor optimizaton, early stopping
            // else
            //     break;
        }
        memo.put(remain, result);
        return result;
    }
}

Word Break

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

unordered_map<string, bool> memo;

bool wordBreak(string s, vector<string>& wordDict) {
    if (s == "")
        return true;

    if (memo.find(s) != memo.end()) {
        return memo[s];
    }

    bool ok = false;

    for (auto& word : wordDict) {
        string c = s.substr(0, word.length());

        if (c == word) {
           ok |= wordBreak(s.substr(word.length(), s.length()), wordDict);
        }
    }

    memo[s] = ok;
    return ok;
}

Regex Matching

// Brute Force
bool dfs(string& s, string& p, int i, int j) {
    // Both pointers moved beyond s and b, we have a match
    if (i >= s.length() && j >= p.length())
        return true;
    // no match
    if (j >= p.length())
        return false;

    // if i in bounds and char match or '.' match set flag. 
    bool match = i < s.length() && (s[i] == p[j] || p[j] == '.');
    bool has_star = j + 1 < p.length() && p[j + 1] == '*';

    if (has_star) {
        return dfs(s, p, i, j + 2) ||        // use *
            (match && dfs(s, p, i + 1, j));  // dont use *
    }
    if (match) {
        return dfs(s, p, i + 1, j + 1);
    }

    return false;
}

bool isMatch(string s, string p) {
    return dfs(s, p, 0, 0);
}

unordered_map<string, bool> memo;

bool check_memo(string& key) {
    return memo.find(key) != memo.end();
}

bool dfs(string& s, string& p, int i, int j) {
    string key = to_string(i) + "x" + to_string(j);

    if (check_memo(key))
        return memo[key];

    // Both pointers moved beyond s and b, we have a match
    if (i >= s.length() && j >= p.length())
        return true;
    // no match
    if (j >= p.length())
        return false;

    // if i in bounds and char match or '.' match set flag. 
    bool match = i < s.length() && (s[i] == p[j] || p[j] == '.');
    bool has_star = j+1 < p.length() && p[j + 1] == '*';

    if (has_star) {
        memo[key] = dfs(s, p, i, j + 2) ||       // use *
            (match && dfs(s, p, i + 1, j));      // dont use *
        return memo[key];
    }
    if (match) {
        memo[key] = dfs(s, p, i + 1, j + 1);
        return memo[key];
    }

    memo[key] = false;
    return false;
}

bool isMatch(string s, string p) {
    return dfs(s, p, 0, 0);
}

Remove Invalid Parenthesis

BFS not DFS. BFS makes much more sense.

bool isValid(string expr) {
    int count = 0;
    for (auto& ch : expr) {
        if (ch != '(' && ch != ')') {
            continue;
        }
        if (ch == '(') {
            count++;
        }
        if (ch == ')') {
            count--;
        }
        if (count < 0) {
            return false;
        }
    }
    return count == 0;
}
#include <deque>
#include <unordered_set>

vector<string> removeInvalidParentheses(string s) {
    if (s.size() == 0) {
        return { "" };
    }

    vector<string> output;

    std::unordered_set<string> visited;
    std::deque<string> q;
    q.push_back(s);
    bool found = false;

    while (q.size() != 0) {
        string expr = q.front();
        q.pop_front();
        if (isValid(expr)) {
            output.push_back(expr);
            found = true;
        }

        if (found) {
            continue;
        }

        for (int i = 0; i < expr.size(); i++) {
            if (expr[i] != '(' && expr[i] != ')') {
                continue;
            }

            string candidate = expr.substr(0, i) + expr.substr(i + 1, expr.size());
            if (visited.find(candidate) == visited.end()) {
                q.push_back(candidate);
                visited.insert(candidate);
            }
        }
    }

    return output;
}

---
)())() // A
(())() // B
()))()
()()()
()()()
()()))
()())(
--- // Of A try all combinations, none are valid so go to B
())()
)))()
)()()
)()()
)()))
)())(
B is valid return

Word Break II

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

class Solution {
    protected Set<String> wordSet;
    protected HashMap<String, List<List<String>>> memo;

    public List<String> wordBreak(String s, List<String> wordDict) {
        wordSet = new HashSet<>();
        for (String word : wordDict) {
            wordSet.add(word);
        }
        memo = new HashMap<String, List<List<String>>>();

        _wordBreak_topdown(s);

        // chain up words together
        List<String> ret = new ArrayList<String>();
        for (List<String> words : memo.get(s)) {
            StringBuffer sentence = new StringBuffer();
            for (String word : words) {
                sentence.insert(0, word);
                sentence.insert(0, " ");
            }
            ret.add(sentence.toString().strip());
        }

        return ret;
    }

    protected List<List<String>> _wordBreak_topdown(String s) {
        if (s.equals("")) {
            List<List<String>> solutions = new ArrayList<List<String>>();
            solutions.add(new ArrayList<String>());
            return solutions;
        }

        if (memo.containsKey(s)) {
            return memo.get(s);
        } else {
            List<List<String>> solutions = new ArrayList<List<String>>();
            memo.put(s, solutions);
        }

        for (int endIndex = 1; endIndex <= s.length(); ++endIndex) {
            String word = s.substring(0, endIndex);
            if (wordSet.contains(word)) {
                List<List<String>> subsentences = _wordBreak_topdown(s.substring(endIndex));
                for (List<String> subsentence : subsentences) {
                    List<String> newSentence = new ArrayList<String>(subsentence);
                    newSentence.add(word);
                    memo.get(s).add(newSentence);
                }
            }
        }
        return memo.get(s);
    }
}

House Robber

https://www.youtube.com/watch?v=73r3KWiEvyk

class Solution:
    def rob(self, nums: List[int]) -> int:
        rob1, rob2 = 0, 0
        for n in nums:
            temp = max(n+rob1, rob2)
            rob1 = rob2
            rob2 = temp
        return rob2