int Fabonacci(int n) {
if(n == 0 || n == 1) return 1;
return F(n-1) + F(n-2);
}
F(6)
F(5)
F(4)
F(4)
F(3)
F(3)
F(3)
F(2)
F(2)
F(1)
F(1)
F(1)
F(0)
F(1)
F(0)
F(1)
F(0)
F(1)
F(0)
F(2)
F(1)
F(1)
F(0)
F(2)
F(2)
int Fabonacci(int n) {
if(n <= 1) return 1;
int[] result = new int[n + 1];
result[0] = 1;
result[1] = 1;
for(int i = 2; i < n + 1; i ++) {
result[i] = result[i-1] + result[i-2];
}
return result[n];
}
我们利用数组来存储临时结果.
这个问题的最优子结构是什么?
int uniquePaths(int m, int n) {
if(m==1 || n==1) return 1;
return uniquePaths(m-1, n) + uniquePaths(m, n-1);
}
哪些地方是重复计算?
这个问题的最优子结构是什么?
最优子结构
a(i,j) = a(i-1,j) + a(i, j-1)
public int uniquePaths(int m, int n) {
int[][] a = new int[m][n];
for (int i = 0; i < m; i++) {
a[i][0] = 1;
}
for (int i = 0; i < n; i++) {
a[0][i] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
a[i][j] = a[i-1][j] + a[i][j-1];
}
}
return a[m-1][n-1];
}
1 | 3 | 4 | 2 |
---|---|---|---|
3 | 5 | 2 | 3 |
2 | 1 | 2 | 3 |
2 | 2 | 4 | 2 |
最优子结构?
1 | 3 | 4 | 2 |
---|---|---|---|
3 | 5 | 2 | 3 |
2 | 1 | 2 | 3 |
2 | 2 | 4 | 2 |
PathSum(m,n) = MIN(PathSum(m,n-1),PathSum(m-1,n)) + matrix(m,n)
public int minPathSum(int[][] grid) {
if(grid == null || grid.length==0)
return 0;
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for(int i=1; i<n; i++){
dp[0][i] = dp[0][i-1] + grid[0][i];
}
for(int j=1; j<m; j++){
dp[j][0] = dp[j-1][0] + grid[j][0];
}
for(int i=1; i<m; i++){
for(int j=1; j<n; j++){
if(dp[i-1][j] > dp[i][j-1]){
dp[i][j] = dp[i][j-1] + grid[i][j];
}else{
dp[i][j] = dp[i-1][j] + grid[i][j];
}
}
}
return dp[m-1][n-1];
}
如何改进?
我们可以用更少的空间去解决问题同时不影响时间复杂度
public static int minPathSum(int[][] grid) {
if(grid == null || grid.length==0)
return 0;
int m = grid.length;
int n = grid[0].length;
int[] newline = new int[n];
int[] oldline = new int[n];
oldline[0] = grid[0][0];
for(int i=1; i<n; i++){
oldline[i] = oldline[i-1] + grid[0][i];
}
for(int i=1; i<m; i++){
newline[0] = grid[i][0] + oldline[0];
for(int j=1; j<n; j++){
if(oldline[j] > newline[j-1]){
newline[j] = newline[j-1] + grid[i][j];
}else{
newline[j] = oldline[j] + grid[i][j];
}
}
oldline = newline;
}
return newline[n-1];
}
我们可以只用一个数组
有一个称重量为w磅的背包, 还有一些物品重量分别为w1, w2, ... wn. 每个物品的价值分别为s1,s2,...,sn. 请尝试选择物品使背包可以放下并且使的价值最大.
这个问题的最优子结构是什么?
w[i][j]: 表示前i个物品中, 在容量为j时的最大价值
我们应该比较哪两种情况?
w[i][j]: 表示前i个物品中, 在容量为j时的最大价值
当我们遍历i和j时, 我们应该尝试:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 3 | 3 | 8 | 11 | 11 | 11 | 11 |
3 | 0 | 3 | 3 | 8 | 11 | 11 | 11 | 12 |
4 | 0 | 3 | 3 | 8 | 11 | 11 | 11 | 12 |
w[i][j]: 表示前i个物品中, 在容量为j时的最大价值
例子: weights{1,3,4,5} values{3,8,4,7}
public int knapsack(int capacity, int[] weights, int[] values) {
int length = weights.length;
if (capacity == 0 || length == 0)
return 0;
int[][] w = new int[length + 1][capacity + 1];
for (int i = 1; i <= length; i++) {
int index = i - 1;
for (int j = 1; j <= capacity; j++) {
if (j < weights[index]) {
w[i][j] = w[i - 1][j];
} else if (w[i - 1][j - weights[index]] + values[index] > w[i - 1][j]) {
w[i][j] = w[i - 1][j - weights[index]] + values[index];
} else {
w[i][j] = w[i - 1][j];
}
}
}
return w[length][capacity];
}
这个问题用DFS或DFS都会超时(TLE)
这个问题和背包问题很相似
硬币种类和数量分别是问题的两个维度
我们应该如何更新结果呢?
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
int length = coins.length;
int[][] dp = new int[length][amount + 1];
for (int j = 0; j <= amount; j ++) {
if (j % coins[0] == 0) {
dp[0][j] = j / coins[0];
} else {
dp[0][j] = -1;
}
}
for (int i = 1; i < length; i ++) {
for (int j = 0; j <= amount; j ++) {
if (j < coins[i]) {
dp[i][j] = dp[i-1][j];
} else {
int temp = Integer.MAX_VALUE;
for (int k = 0; k <= j / coins[i]; k ++) {
int remaining = j - coins[i] * k;
if (dp[i-1][remaining] != -1 && dp[i-1][remaining] + k < temp) {
temp = dp[i-1][remaining] + k;
}
}
dp[i][j] = temp < Integer.MAX_VALUE ? temp : -1;
}
}
}
return dp[length - 1][amount];
}
3, 1, 4, 5, 7, 6, 8, 2
1, 4, 5, 6, 8 (或 1, 4, 5, 7, 8)
这个问题的最优子结构是什么呢?
我们为不同的长度i, 存储lis[i]作为当前长度的最长递增子序列?
我们用 sequence[i]来存储当前长度i的最长递增子序列
public int longestIncreasingSubsequence(int[] nums) {
if(nums.length == 0){
return 0;
}
int[] lis = new int[nums.length];
int max = 0;
for (int i = 0; i < nums.length; i++){
int localMax = 0;
for (int j = 0; j < i; j++){
if (lis[j] > localMax && nums[j] <= nums[i]){
localMax = lis[j];
}
}
lis[i] = localMax + 1;
max = Math.max(max, lis[i]);
}
return max;
}
1, 3, 5, 2, 8, 4, 7, 6, 0, 9, 10
1 -> 0 |
1,3 -> 1,2 |
1,3,5 -> 1,3,4 |
1,3,5,8 -> 1,3,5,7 -> 1,3,5,6 |
1,3,5,6,9 |
1,3,5,6,9,10 |
public int longestIncreasingSubsequence(int[] nums) {
if(nums.length == 0){
return 0;
}
int len = 0;
int[] tails = new int[nums.length];
tails[0] = nums[0];
for(int i = 1; i < nums.length; i++){
if(nums[i] < tails[0]){
tails[0] = nums[i];
} else if (nums[i] >= tails[len]){
tails[++len] = nums[i];
} else {
tails[binarySearch(tails, 0, len, nums[i])] = nums[i];
}
}
return len + 1;
}
private int binarySearch(int[] tails, int min, int max, int target){
while(min < max){
int mid = min + (max - min) / 2;
if(tails[mid] == target){
return mid;
}
else if(tails[mid] < target){
min = mid + 1;
}
else max = mid;
}
return min;
}
例如: abcfbc abfcab
返回 4 (abcb)
这个问题的最优子结构是什么?
maxCommon(i,j): 表示String A(0,i) 和 String B(0,j)的最长公共子序列
最后需要得到的是 maxCommon(stringA.length, stringB.length)
maxCommon(i,j)和maxCommon(i-1,j-1)之间的关系是什么?
If(A[i-1] = B[j-1]) ?
If(A[i-1] != B[j-1])?
maxCommon(i,j)和maxCommon(i-1,j-1)之间的关系是什么?
If(A[i-1] = B[j-1]) ?
If(A[i-1] != B[j-1])?
maxCommon(i,j) = maxCommon(i-1,j-1) + 1
maxCommon(i,j) = max(maxCommon(i-1,j), maxCommon(i,j-1))
public static int longestCommonString(String a, String b) {
int m = a.length();
int n = b.length();
int[][] maxCommon = new int[m+1][n+1];
//for(int i = 0; i <= m; i ++) {
// maxCommon[i][0] = 0;
//}
//for(int j = 0; j <= n; j ++) {
// maxCommon[0][j] = 0;
//}
for(int i = 1; i <= m; i ++) {
for(int j = 1; j <= n; j ++) {
if(a.charAt(i-1) == b.charAt(j-1)) {
maxCommon[i][j] = maxCommon[i-1][j-1] + 1;
}
else {
maxCommon[i][j] = Math.max(maxCommon[i][j-1], maxCommon[i-1][j]);
}
}
}
return maxCommon[m][n];
}
Matrix A m*n
Matrix B n*p
C = A * B 需要 m*n*p 次计算
A 100 * 10, B 10 * 100, C 100 * 5
D = A * B * C
如果计算(AB)C, 需要100*10*100 + 100*100*5 = 150000次计算
如果计算A(BC), need 100*10*5 + 10*100*5 = 10000次计算
给定A1,A2,.....,An共n个矩阵, 找出总的计算次数最少的方式
input: 一个数组P共有n+1个数字p0, p1...pn
其中A1 = p0*p1, An = Pn-1*Pn
这个问题的最优子结构是什么?
对于矩阵乘法A1*...*An, 如果我们在Ak出分开:
计算会变为(A1*...Ak)(Ak+1*...An)
T(1,n) = T(1,k) + T(k+1,n) + p0*pk*pn
所以如果T(1,n)是最好的, 那么T(1,k)和T(k+1,n)必须各自是最好的.
我们如何得到T(1,k)?
我们需要从链式长度为1的乘法一直到链式长度为n的乘法, 不断地更新得到结果
public static int MatrixChain(int[] p)
{
int n = p.length;
n --;
int[][] m = new int[n][n];
for(int i = 0; i < n; i++)
m[i][i] = 0;
for(int r = 2; r <= n; r++)
{
for(int i = 0; i < n - r + 1; i ++)
{
int j = i + r - 1;
m[i][j] = m[i + 1][j] + p[i] * p[i+1] * p[j + 1];
for(int k = i + 1; k < j; k++)
{
int t = m[i][k] + m[k + 1][j] + p[i] * p[k+1] * p[j+1];
if( t < m[i][j])
m[i][j] = t;
}
}
}
return m[0][n-1];
}
链的长度
链的开始