package com.interview.leetcode.dp;
/**
* Created_By: stefanie
* Date: 14-11-25
* Time: 下午8:37
*/
public class TwoSequenceDP {
/**
* Given two strings, find the longest comment subsequence (LCS).
* Your code should return the length of LCS.
*/
static class LongestCommonSubsequence{
// state[i][j] = the length of LCS with i chars in S1 and j chars in S2
// initialize:
// state[0][j] = 0 for j = 0 to S2.length().
// state[i][0] = 0 for i = 0 to S1.length();
// function:
// state[i][j] = state[i-1][j-1] + 1 if S1[i] == S2[j]
// state[i][j] = max{state[i-1][j], state[i][j-1]} if S1[i] != S2[j]
// Result: state[S1.length()][S2.length()]
public int longest(String A, String B) {
if(A == null || B == null) return 0;
int[][] len = new int[A.length() + 1][B.length() + 1];
len[0][0] = 0;
for(int i = 1; i <= A.length(); i++) len[i][0] = 0;
for(int j = 1; j <= B.length(); j++) len[0][j] = 0;
for(int i = 1; i <= A.length(); i++){
for(int j = 1; j <= B.length(); j++){
if(A.charAt(i - 1) == B.charAt(j - 1)){
len[i][j] = len[i - 1][j - 1] + 1;
} else {
len[i][j] = Math.max(len[i - 1][j], len[i][j - 1]);
}
}
}
return len[A.length()][B.length()];
}
}
/**
* Given two strings, find the longest common substring.
* Return the length of it.
*/
static class LongestCommonSubstring{
public int longestCommonSubstring(String A, String B) {
if(A == null || B == null) return 0;
int[][] len = new int[A.length() + 1][B.length() + 1];
len[0][0] = 0;
for(int i = 1; i <= A.length(); i++) len[i][0] = 0;
for(int j = 1; j <= B.length(); j++) len[0][j] = 0;
int max = 0;
for(int i = 1; i <= A.length(); i++){
for(int j = 1; j <= B.length(); j++){
if(A.charAt(i - 1) == B.charAt(j - 1)){
len[i][j] = len[i - 1][j - 1] + 1;
max = Math.max(max, len[i][j]);
} else {
len[i][j] = 0; //different from LCS
}
}
}
return max; //not len[A.length()][B.length()]
}
}
/**
* Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.
* You have the following 3 operations permitted on a word:
* a) Insert a character
* b) Delete a character
* c) Replace a character
*/
static class EditDistance {
// State: dis[i][j] is the edit distance of a.substring(0,i) and b.substring(0,j);
// Transfer: dis[i][j] == min of following
// dis[i - 1][j - 1] if(a.charAt(i-1) == b.charAt(j-1)) //equals
// dis[i - 1][j - 1] + 1 if(a.charAt(i-1) != b.charAt(j-1)) //modify
// dis[i - 1][j] or dis[i][j - 1]
// Init: dis[0][0] = 0
// dis[0][i] = i and dis[i][0] = i
// Answer: dis[a.length()][b.length()]
public static int minDistance(String a, String b) {
int n = a.length();
int m = b.length();
int[][] dis = new int[n + 1][m + 1];
for(int i = 0; i <= n; i++) dis[i][0] = i;
for(int j = 0; j <= m; j++) dis[0][j] = j;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
dis[i][j] = (a.charAt(i-1) == b.charAt(j-1))? dis[i-1][j-1] : dis[i-1][j-1] + 1;
int smaller = Math.min(dis[i-1][j], dis[i][j-1]) + 1;
dis[i][j] = Math.min(dis[i][j], smaller);
}
}
return dis[n][m];
}
}
/**
* Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.
* For example,
* Given:
* s1 = "aabcc", s2 = "dbbca",
* When s3 = "aadbbcbcac", return true.
* When s3 = "aadbbbaccc", return false.
*
*/
static class InterleavingString {
//State: matchChar[i][j]: i chars from s1, j chars from s2 is interleaving of i + j chars from s3
//Transfer:
// matchChar[i][j] == true when
// s1.charAt(i-1) == s3.charAt(i + j - 1) && matchChar[i-1][j]
// or s2.charAt(j-1) == s3.charAt(i + j - 1) && matchChar[i][j-1]
//Init: matchChar[i][0] = true when s1.charAt(i - 1) == s3.charAt(i - 1)
// matchChar[0][i] = true when s2.charAt(i - 1) == s3.charAt(i - 1)
//Result: matchChar[n][m]
public static boolean isInterleave(String s1, String s2, String s3) {
if(s1 == null || s2 == null || s3 == null || s1.length() + s2.length() != s3.length()) return false;
int n = s1.length();
int m = s2.length();
boolean[][] match = new boolean[n + 1][m + 1];
match[0][0] = true;
for(int i = 1; i <= n; i++) match[i][0] = s1.charAt(i - 1) == s3.charAt(i - 1);
for(int j = 1; j <= m; j++) match[0][j] = s2.charAt(j - 1) == s3.charAt(j - 1);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
match[i][j] = (match[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1))
||(match[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1));
}
}
return match[n][m];
}
}
/**
* Given a string S and a string T, count the number of distinct subsequences of T in S.
* A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters
* without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).
* Here is an example: S = "rabbbit", T = "rabbit" Return 3.
*
*/
static class DistinctSubsequence {
//State: num[i][j]: the subsequence num of S.substring(0, i) and T.substring(0, j);
//Transfer:
// 1. if S.charAt(i - 1) != T.charAt(j - 1) num[i][j] = num[i - 1][j]
// 2. if S.charAt(i - 1) == T.charAt(j - 1) num[i][j] = num[i - 1][j] + num[i - 1][j - 1]
//Init: num[0][*] = 1 and num[0][*] = 0
//Ans: num[n][m]
public int numDistinct(String S, String T) {
int n = S.length();
int m = T.length();
int[][] num = new int[n + 1][m + 1];
for(int i = 0; i <= n; i++) num[i][0] = 1;
for(int j = 1; j <= m; j++) num[0][j] = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
num[i][j] = num[i-1][j];
if(S.charAt(i - 1) == T.charAt(j - 1)) num[i][j] += num[i-1][j-1];
}
}
return num[n][m];
}
}
/**
* Given a string s, find out the min cut needed to partition it to palindrome
*/
static class PalindromePartition {
// minCut[i]: the min cuts with i chars in s
// initialization: minCut[0] = 0; minCut[1] = 0;
// function: minCut[i] = min{minCut[j] + 1, s[0...j] can be partitioned into palindromes and s[j..i] is a palindrome}
// result: minCut[s.length()]
public int minCut(String s){
if(s == null || s.length() == 1) return 0;
boolean[][] isPalindrome = calculate(s);
int[] minCut = new int[s.length()];
minCut[0] = 0;
for(int i = 1; i < s.length(); i++){
minCut[i] = minCut[i-1] + 1;
if(isPalindrome[0][i]) {
minCut[i] = 0;
continue;
}
for(int j = 1; j < i; j++){
if(isPalindrome[j][i]) minCut[i] = Math.min(minCut[i], minCut[j - 1] + 1);
}
}
return minCut[s.length() - 1];
}
// palindrome[i][j]: if substring(i, j + 1) is palindrome
// initialize: palindrome[i][i] = true 0<= i < S.length
// function: palindrome[i][i+len] = (len == 1? true : palindrome[i+1][i+len-1]) && s.charAt(i) == s.charAt(i + len);
// for loop on len, and i
public boolean[][] calculate(String s){ //substring(i, j + 1) is palindrome
boolean[][] palindrome = new boolean[s.length()][s.length()];
for(int i = 0; i < s.length(); i++) palindrome[i][i] = true;
for(int len = 1; len < s.length(); len++){
for(int i = 0; i+len < s.length(); i++){
palindrome[i][i+len] = (len == 1? true : palindrome[i+1][i+len-1]) && s.charAt(i) == s.charAt(i + len);
}
}
return palindrome;
}
}
/**
* Implement regular expression matching with support for '.' and '*'.
* '.' Matches any single character.
* '*' Matches zero or more of the preceding element.
*/
static class RegularExpressionMatching{
// State: matchChar[i][j] s.substring(0, i) matched p.substring(0, j);
// Transfer: matchChar[i][j] = true if
// matchChar[i-1][j-1] && matchChar(i, j);
// if j is '*' && matchChar[i][j-2] //a* doesn't matchChar any char in s //0
// if j is '*' && matchChar(i, j - 1) && (matchChar[i-1][j] || matchChar[i][j-1])
// matchChar[i-1][j]: "a*" matchChar 'a' //1
// matchChar[i][j-1]: "a*" matchChar 'aa~' //>1
// Init: matchChar[0][0] == true
// matchChar[0][j] == true when if j is '*' && matchChar[0][j-2]
// Result: matchChar[m][n]
public static boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] match = new boolean[m+1][n+1];
match[0][0] = true;
for(int j = 1; j <= n; j++){
match[0][j] = (p.charAt(j - 1) == '*') && (j - 2 >= 0) && match[0][j-2];
}
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
match[i][j] = (match[i-1][j-1] && match(s, p, i, j))
|| (p.charAt(j - 1) == '*' && match(s, p, i, j - 1) && (match[i-1][j] || match[i][j-1]))
|| (p.charAt(j - 1) == '*' && j - 2 >= 0 && match[i][j-2]);
}
}
return match[m][n];
}
public static boolean match(String s, String p, int i, int j){
return (p.charAt(j - 1) == '.') || (p.charAt(j - 1) == s.charAt(i - 1));
}
}
/**
* Given n distinct positive integers, integer k (k <= n) and a number target.
* Find k numbers where sum is target. Calculate how many solutions there are?
*/
static class KSum {
public int find(int A[],int k,int target) {
return 0;
}
}
/**
* N factories in one road, the distance between each of them to the west end of the road is D[N].
* Need pick M factories as supplier, to make the sum distance between the other factories to these M factories shortest.
* Solution:
* Assumption:
* 1. if need create 1 supplier between M-th and N-th factory, it should be the median factory to achieve shortest dist.
* 2. assume A(i, j) is the shortest dist of factory(0-i) setup j supplier,
* and B(m, n) is the shortest dist between M-th and N-th factory setup 1 supplier.
* we could get the following formula:
* A(i,j) = Min { A(t,j-1) + B(t+1,i) } 1<=t<i, t>=j-1
*/
static class PickFactory {
//
//sols[i][j][0] is the shortest dist of factory(0-i) setup j supplier
//sols[i][j][k] is if k-th factory is selected in the solution of shortest dist of factory(0-i) setup j supplier
//initialize: sols[i][1][0] = distance(0, i)
// sols[i][1][1] = dist[i/2];
// if need create 1 supplier between M-th and N-th factory, it should be the median factory to achieve shortest dist.
//function: sols[i][j][0] = min {sols[t][j-1][0] + distance(t + 1,i)} 1<=t<i, t>=j-1
// if is min copy the solution sols[t][j-1][*] to sols[i][j][*] and sols[i][j][j] = dist[(t + 1 + i) >> 1];
// here j only depends on j - 1, so reduce the sols[i][j][*] to sols[i][*]
//result: sols[n-1][m]
public static int[] pick(int[] dist, int m) {
int[][] sols = new int[dist.length][m + 1];
for (int p = 0; p < dist.length; p++) {
sols[p][0] = distance(dist, 0, p);
sols[p][1] = dist[p >> 1];
}
for (int j = 2; j <= m; j++) {
for (int i = 0; i < dist.length; i++) {
if (i + 1 >= j) { //could get one more supplier
int min = Integer.MAX_VALUE; //A(i,j) = Min { A(t,j-1) + B(t+1,i) } 1<=t<i, t>=j-1
for (int t = i - 1; t >= 0; t--) {
if (t + 1 >= j - 1) { //could get one more supplier
int curDis = sols[t][0] + distance(dist, t + 1, i);
if (min > curDis) {
min = curDis;
for (int k = 1; k <= j - 1; k++) {
sols[i][k] = sols[t][k]; //copy the old solution
}
sols[i][j] = dist[(t + 1 + i) >> 1];
}
}
}
sols[i][0] = min;
}
}
}
return sols[dist.length - 1];
}
/**
* the shortest dist from mth - nth factories if set 1 supplier.
* the supplier get shortest dist should be the the mid of the factories.
*/
private static int distance(int[] dist, int left, int right) {
int mid = (left + right) >> 1;
int dis = 0;
for (int i = left; i <= right; i++) {
int dif = dist[i] - dist[mid];
dis += ((dif > 0) ? dif : -1 * dif);
}
return dis;
}
}
/**
* There is M matrix, A1 A2 .. AM, write code to find the smallest cost ways to make these M matrix could multiply.
* (A1 * (A2 * A3)) or ((A1 * A2) * A3) give the same answer, but may cause different of computing effect when A1 is a very small matrix
* And A1*A1 could multiply when dimensionality is the same, so d[N] save the dimensions, Ai's dimension is di-1 and di
*/
static class MatrixMultiply{
//times[i][j] is the multiply times needed of A[i] multiply till A[j]
//initialize: times[i][i] = 0;
//function: times[s][t] = min{times[s][k] + times[k + 1][t] + dim[s - 1] * dim[k] * dim[t]} s <= k < t
// loop on the len and s. len in [2,N), s in [1, N - l + 1) t = s + l - 1
//result: times[1][N - 1]
public static int count(int[] dim) {
int N = dim.length;
int[][] times = new int[N][N];
for (int i = 1; i < N; i++) times[i][i] = 0;
for (int l = 2; l < N; l++) { // l is the length of matrix chain
for (int i = 1; i < N - l + 1; i++) { // i is the start of the chain
int j = i + l - 1; // j is the end of the chain
times[i][j] = Integer.MAX_VALUE;
for (int k = i; k < j; k++) {
int ten = times[i][k] + times[k + 1][j] + dim[i - 1] * dim[k] * dim[j];
if (ten < times[i][j]) {
times[i][j] = ten;
}
}
}
}
return times[1][N - 1];
}
}
/**
* Given an int array, find the sub arrays sum is equals or closest smaller to a given K
*/
static class SubArraysSumClosestToK {
//sums[i][k], is the closest sum to k in subarray 0-i
//initialize: sums[0][*] = 0
//function: sums[i][k] = sums[i-1][k] when k < array[i]
// sums[i][k] = Math.max(sums[i-1][k], sums[i-1][k-array[i]] + array[i]);
//return sums[array.length-1][K]
// back-tracing the mark
public static boolean[] find(int[] array, int K){
int len = array.length;
boolean[] mark = new boolean[len];
//if K equals or larger than sum, return all the set
int total = 0;
for (int i = 0; i < len; i++) total += array[i];
if(total <= K) {
for(int i = 0; i < len; i++) mark[i] = true;
return mark;
}
//opt[i][k] saves 0~i element sum closest to k.
int[][] sums = new int[len][K + 1];
for(int i = 0; i <= K; i++) sums[0][i] = 0;
for (int i = 1; i < len; i++) {
for(int k = 0; k < K + 1; k++){
if(k >= array[i]){ //i-th element is smaller than j
//find a more close solution
sums[i][k] = Math.max(sums[i-1][k], sums[i-1][k-array[i]] + array[i]);
} else
sums[i][k] = sums[i-1][k];
}
}
//backtrace the solution
int k = K;
int i = len - 1;
while(i >= 0 && k > 0){
//when not the first and opt[i][j] > opt[i-1][j] means i-th element is selected.
//when is the first element, if j = array[i], means i-th element is selected
if(( i > 0 && sums[i][k] > sums[i-1][k]) || (i == 0 && k == array[i])){
mark[i] = true;
k -= array[i];
}
i--;
}
return mark;
}
}
/**
* There are n coins in a line. (Assume n is even). Two players take turns to take a coin from one of the ends
* of the line until there are no more coins left. The player with the larger amount of money wins.
* 1. Would you rather go first or second? Does it matter?
* 2. Assume that you go first, describe an algorithm to compute the maximum amount of money you can win.
*/
static class CoinsInALine {
//money[i][j = the max money I can get in the coin sequence num[i] ~ num[j]
//initialize: money[0][*] == 0 money[*][0] = 0;
//function: money[i][j] = max of
// num[i] + min(money[i+2][j], money[i+1][j-1])
// num[j] + min(money[i+1][j-1], money[i][j-2])
// same as palindrome, need loop on len and start point
//result: money[0][num.length-1]
public int maxMoney(int[] num) {
int N = num.length;
int[][] money = new int[N][N];
int a, b, c;
for (int len = 1; len < N; len++) {
for (int i = 0, j = len; i < N && j < N; i++, j++) {
a = ((i + 2 <= N - 1) ? money[i + 2][j] : 0);
b = ((i + 1 <= N - 1 && j - 1 >= 0) ? money[i + 1][j - 1] : 0);
c = ((j - 2 >= 0) ? money[i][j - 2] : 0);
money[i][j] = Math.max(num[i] + Math.min(a, b), num[j] + Math.min(b, c));
}
}
return money[0][N - 1];
}
}
}