二维动态规划(上)

二维动态规划

64. 最小路径和

int min(int a, int b) {
    return a > b ? b : a;
}

// 从(0,0)到(i,j)的最小路径和,只能向右或向下移动
int recursive(int **grid, int i, int j) {
    if (i == 0 && j == 0) return grid[0][0];
    int up = 0x7fffffff;
    int left = 0x7fffffff;
    if (i - 1 >= 0) up = recursive(grid, i - 1, j);
    if (j - 1 >= 0) left = recursive(grid, i, j - 1);
    // 只能从上方或者左侧到当前位置,选则更小的路径和
    return grid[i][j] + min(up, left);
}

// 暴力超时
int minPathSum(int **grid, int gridSize, int *gridColSize) {
    return recursive(grid, gridSize - 1, (*gridColSize) - 1);
}
int min(int a, int b) {
    return a > b ? b : a;
}

int **dp;

// 从(0,0)到(i,j)的最小路径和,只能向右或向下移动
int recursive(int **grid, int i, int j) {
    // 之前计算过就返回
    if (dp[i][j] != -1) return dp[i][j];
    if (i == 0 && j == 0) {
        dp[i][j] = grid[0][0];
    } else {
        int up = 0x7fffffff;
        int left = 0x7fffffff;
        if (i - 1 >= 0) up = recursive(grid, i - 1, j);
        if (j - 1 >= 0) left = recursive(grid, i, j - 1);
        // 只能从上方或者左侧到当前位置,选则更小的路径和
        dp[i][j] = grid[i][j] + min(up, left);
    }
    return dp[i][j];
}

// 自顶向下记忆化搜索
int minPathSum(int **grid, int gridSize, int *gridColSize) {
    dp = (int **) malloc(sizeof(int *) * gridSize);
    for (int i = 0; i < gridSize; ++i) {
        dp[i] = (int *) malloc(sizeof(int) * (*gridColSize));
        memset(dp[i], -1, sizeof(int) * (*gridColSize));
    }
    return recursive(grid, gridSize - 1, (*gridColSize) - 1);
}
int min(int a, int b) {
    return a > b ? b : a;
}

// 自底向上+空间压缩
int minPathSum(int **grid, int gridSize, int *gridColSize) {
    int rowSize = gridSize;
    int columnSize = *gridColSize;
    // 一行一行保存最小路径和
    int dp[columnSize];
    dp[0] = grid[0][0];
    // 第一行只和左边的一个元素有关
    for (int i = 1; i < columnSize; ++i)
        dp[i] = dp[i - 1] + grid[0][i];

    for (int i = 1; i < rowSize; ++i) {
        // 每行的首元素只和上一行同位置元素有关
        dp[0] += grid[i][0];
        // 其他行的非首元素和当前行左边的元素和上一行的同位置元素有关
        for (int j = 1; j < columnSize; ++j)
            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];
    }

    return dp[columnSize - 1];
}

1143. 最长公共子序列

int max(int a, int b) {
    return a > b ? a : b;
}

// 返回0~i1和0~i2的最长公共子序列长度
int recursive(char *s1, char *s2, int i1, int i2) {
    if (i1 < 0 || i2 < 0) return 0;
    // 四种可能
    int p1 = recursive(s1, s2, i1 - 1, i2 - 1);
    int p2 = recursive(s1, s2, i1 - 1, i2);
    int p3 = recursive(s1, s2, i1, i2 - 1);
    int p4 = s1[i1] == s2[i2] ? (p1 + 1) : 0;
    return max(max(p1, p2), max(p3, p4));
}

// 暴力超时
int longestCommonSubsequence(char *text1, char *text2) {
    return recursive(text1, text2, strlen(text1) - 1, strlen(text2) - 1);
}
int max(int a, int b) {
    return a > b ? a : b;
}

// 返回前缀长为len1和len2的最长公共子序列长度
int recursive(char *s1, char *s2, int len1, int len2) {
    if (len1 == 0 || len2 == 0) return 0;
    if (s1[len1 - 1] == s2[len2 - 1])
        return recursive(s1, s2, len1 - 1, len2 - 1) + 1;
    // 优化:省去了recursive(s1, s2, len1 - 1, len2 - 1),因为范围包括在下面的两个范围内了
    return max(recursive(s1, s2, len1 - 1, len2), recursive(s1, s2, len1, len2 - 1));
}

// 暴力超时
int longestCommonSubsequence(char *text1, char *text2) {
    return recursive(text1, text2, strlen(text1), strlen(text2));
}

int max(int a, int b) {
    return a > b ? a : b;
}

int **dp;
int length1;
int length2;

// 返回前缀长为len1和len2的最长公共子序列长度
int recursive(char *s1, char *s2, int len1, int len2) {
    if (len1 == 0 || len2 == 0) return 0;
    if (dp[len1][len2] != -1) return dp[len1][len2];
    if (s1[len1 - 1] == s2[len2 - 1]) {
        dp[len1][len2] = recursive(s1, s2, len1 - 1, len2 - 1) + 1;
    } else {
        // 优化:省去了recursive(s1, s2, len1 - 1, len2 - 1),因为范围包括在下面的两个范围内了
        dp[len1][len2] = max(recursive(s1, s2, len1 - 1, len2), recursive(s1, s2, len1, len2 - 1));
    }
    return dp[len1][len2];
}

// 自顶向下记忆化搜索
int longestCommonSubsequence(char *text1, char *text2) {
    length1 = strlen(text1);
    length2 = strlen(text2);
    dp = (int **) malloc(sizeof(int *) * (length1 + 1));
    for (int i = 0; i <= length1; ++i) {
        dp[i] = (int *) malloc(sizeof(int) * (length2 + 1));
        memset(dp[i], -1, sizeof(int) * (length2 + 1));
    }

    return recursive(text1, text2, length1, length2);
}
int max(int a, int b) {
    return a > b ? a : b;
}

// 自底向上
int longestCommonSubsequence(char *text1, char *text2) {
    int length1 = strlen(text1);
    int length2 = strlen(text2);
    int dp[length1 + 1][length2 + 1];
    // 第一行和第一列都是0
    for (int i = 0; i <= length1; ++i) dp[i][0] = 0;
    for (int i = 0; i <= length2; ++i) dp[0][i] = 0;
    // 和左边,上边,左上角的元素有关
    for (int len1 = 1; len1 <= length1; ++len1) {
        for (int len2 = 1; len2 <= length2; ++len2) {
            if (text1[len1 - 1] == text2[len2 - 1])
                dp[len1][len2] = dp[len1 - 1][len2 - 1] + 1;
            else
                dp[len1][len2] = max(dp[len1 - 1][len2], dp[len1][len2 - 1]);
        }
    }

    return dp[length1][length2];
}
int max(int a, int b) {
    return a > b ? a : b;
}

// 空间压缩
int longestCommonSubsequence(char *text1, char *text2) {
    char *s1, *s2;
    // s2为较短
    if (strlen(text1) < strlen(text2)) {
        s1 = text2;
        s2 = text1;
    } else {
        s1 = text1;
        s2 = text2;
    }
    int length1 = strlen(s1);
    int length2 = strlen(s2);
    // 长度较短但滚动次数较多的数组,
    int dp[length2 + 1];
    // 第0行全0
    memset(dp, 0, sizeof(int) * (length2 + 1));
    for (int len1 = 1; len1 <= length1; ++len1) {
        // 左上角元素
        int leftUp = dp[0];
        // 修改前暂存当前元素
        int temp;
        for (int len2 = 1; len2 <= length2; ++len2) {
            temp = dp[len2];
            if (s1[len1 - 1] == s2[len2 - 1])
                // 与左上角有关
                dp[len2] = leftUp + 1;
            else
                // 与左边和上边的最大值有关
                dp[len2] = max(dp[len2 - 1], dp[len2]);
            leftUp = temp;
        }
    }
    return dp[length2];
}

516. 最长回文子序列

int max(int a, int b) {
    return a > b ? a : b;
}

// 返回下标left~right的最长回文子序列长度
int recursive(char *s, int left, int right) {
//    if (left > right) return 0;   
    // 只有一个元素
    if (left == right) return 1;
    // 只有两个元素
    if (left + 1 == right) return s[left] == s[right] ? 2 : 1;
    if (s[left] == s[right]) {
        return recursive(s, left + 1, right - 1) + 2;
    } else {
        return max(recursive(s, left, right - 1), recursive(s, left + 1, right));
    }
}

// 暴力超时
int longestPalindromeSubseq(char *s) {
    return recursive(s, 0, strlen(s) - 1);
}
int max(int a, int b) {
    return a > b ? a : b;
}

int **dp;
int len;

// 返回下标left~right的最长回文子序列长度
int recursive(char *s, int left, int right) {
    // 只有一个元素
    if (left == right) return 1;
    // 只有两个元素
    if (left + 1 == right) {
        dp[left][right] = s[left] == s[right] ? 2 : 1;
    }
    // 已经有记录就返回
    if (dp[left][right] != -1) return dp[left][right];
    if (s[left] == s[right]) {
        dp[left][right] = recursive(s, left + 1, right - 1) + 2;
    } else {
        dp[left][right] = max(recursive(s, left, right - 1), recursive(s, left + 1, right));
    }
    return dp[left][right];
}

// 自顶向下记忆化搜索
int longestPalindromeSubseq(char *s) {
    len = strlen(s);
    // dp[i][j]表示从i到j位置的最长回文子序列长度
    dp = (int **) malloc(sizeof(int *) * len);
    for (int i = 0; i < len; ++i) {
        dp[i] = (int *) malloc(sizeof(int) * len);
        memset(dp[i], -1, sizeof(int) * len);
    }
    // 主对角线为1,只需再填写上三角矩阵
    for (int i = 0; i < len; ++i) dp[i][i] = 1;

    return recursive(s, 0, len - 1);
}
int max(int a, int b) {
    return a > b ? a : b;
}

// 自底向上
int longestPalindromeSubseq(char *s) {
    int len = strlen(s);
    // dp[i][j]表示从i到j位置的最长回文子序列长度
    int dp[len][len];
    for (int left = len - 1; left >= 0; left--) {
        // 主对角线为1,只需再填写上三角矩阵
        dp[left][left] = 1;
        // 主对角线上方的一条斜线
        if (left + 1 < len)
            dp[left][left + 1] = s[left] == s[left + 1] ? 2 : 1;
        // 上三角的其他位置,之和左边、下边、左下角相关
        for (int right = left + 2; right < len; ++right) {
            if (s[left] == s[right])
                dp[left][right] = dp[left + 1][right - 1] + 2;
            else
                dp[left][right] = max(dp[left + 1][right], dp[left][right - 1]);
        }
    }

    return dp[0][len - 1];
}
int max(int a, int b) {
    return a > b ? a : b;
}

// 空间压缩
int longestPalindromeSubseq(char *s) {
    int len = strlen(s);
    int dp[len];
    // 左下角元素
    int leftDown;
    for (int left = len - 1; left >= 0; left--) {
        // dp[left][left]
        dp[left] = 1;
        // 主对角线上方的一条斜线
        if (left + 1 < len) {
            // 记录下左下角
            leftDown = dp[left + 1];
            // dp[left][left+1]
            dp[left + 1] = s[left] == s[left + 1] ? 2 : 1;
        }
        // 上三角的其他位置,之和左边、下边、左下角相关
        for (int right = left + 2; right < len; ++right) {
            int backup = dp[right];
            if (s[left] == s[right])
                dp[right] = leftDown + 2;
            else
                // 没更新前,dp[right]表示dp[left+1][right],dp[right-1]表示dp[left][right-1]
                dp[right] = max(dp[right], dp[right - 1]);
            leftDown = backup;
        }
    }

    return dp[len - 1];
}

节点数为n高度不大于m的二叉树个数

package class067;

// 节点数为n高度不大于m的二叉树个数
// 现在有n个节点,计算出有多少个不同结构的二叉树
// 满足节点个数为n且树的高度不超过m的方案
// 因为答案很大,所以答案需要模上1000000007后输出
// 测试链接 : https://www.nowcoder.com/practice/aaefe5896cce4204b276e213e725f3ea
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下所有代码,把主类名改成Main,可以直接通过

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

public class Code05_NodenHeightNotLargerThanm {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		while (in.nextToken() != StreamTokenizer.TT_EOF) {
			int n = (int) in.nval;
			in.nextToken();
			int m = (int) in.nval;
			out.println(compute3(n, m));
		}
		out.flush();
		out.close();
		br.close();
	}

	public static int MAXN = 51;

	public static int MOD = 1000000007;

	// 记忆化搜索
	public static long[][] dp1 = new long[MAXN][MAXN];

	static {
		for (int i = 0; i < MAXN; i++) {
			for (int j = 0; j < MAXN; j++) {
				dp1[i][j] = -1;
			}
		}
	}

	// 二叉树节点数为n
	// 高度不能超过m
	// 结构数返回
	// 记忆化搜索
	public static int compute1(int n, int m) {
		if (n == 0) {
			return 1;
		}
		// n > 0
		if (m == 0) {
			return 0;
		}
		if (dp1[n][m] != -1) {
			return (int) dp1[n][m];
		}
		long ans = 0;
		// n个点,头占掉1个
		for (int k = 0; k < n; k++) {
			// 一共n个节点,头节点已经占用了1个名额
			// 如果左树占用k个,那么右树就占用i-k-1个
			ans = (ans + ((long) compute1(k, m - 1) * compute1(n - k - 1, m - 1)) % MOD) % MOD;
		}
		dp1[n][m] = ans;
		return (int) ans;
	}

	// 严格位置依赖的动态规划
	public static long[][] dp2 = new long[MAXN][MAXN];

	public static int compute2(int n, int m) {
		for (int j = 0; j <= m; j++) {
			dp2[0][j] = 1;
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				dp2[i][j] = 0;
				for (int k = 0; k < i; k++) {
					// 一共i个节点,头节点已经占用了1个名额
					// 如果左树占用k个,那么右树就占用i-k-1个
					dp2[i][j] = (dp2[i][j] + dp2[k][j - 1] * dp2[i - k - 1][j - 1] % MOD) % MOD;
				}
			}
		}
		return (int) dp2[n][m];
	}

	// 空间压缩
	public static long[] dp3 = new long[MAXN];

	public static int compute3(int n, int m) {
		dp3[0] = 1;
		for (int i = 1; i <= n; i++) {
			dp3[i] = 0;
		}
		for (int j = 1; j <= m; j++) {
			// 根据依赖,一定要先枚举列
			for (int i = n; i >= 1; i--) {
				// 再枚举行,而且i不需要到达0,i>=1即可
				dp3[i] = 0;
				for (int k = 0; k < i; k++) {
					// 枚举
					dp3[i] = (dp3[i] + dp3[k] * dp3[i - k - 1] % MOD) % MOD;
				}
			}
		}
		return (int) dp3[n];
	}

}

329. 矩阵中的最长递增路径

int rowSize;
int columnSize;
int directions[4][2] = {{-1, 0},
                        {1,  0},
                        {0,  -1},
                        {0,  1}};

bool isPositionLegal(int i, int j) {
    return i >= 0 && i < rowSize && j >= 0 && j < columnSize;
}

// 从matrix[i, j]出发,能走出来的最长递增路径长度
int recursive(int **matrix, int i, int j, int pre) {
    if (pre >= matrix[i][j]) return 0;
    // 比pre更大(不会走回头路)
    int max = 0;
    for (int k = 0; k < 4; ++k) {
        int nextI = i + directions[k][0];
        int nextJ = j + directions[k][1];
        // 没越界
        if (isPositionLegal(nextI, nextJ)) {
            int temp = recursive(matrix, nextI, nextJ, matrix[i][j]);
            if (temp > max) max = temp;
        }
    }
    return max + 1;
}

// 暴力超时
int longestIncreasingPath(int **matrix, int matrixSize, int *matrixColSize) {
    rowSize = matrixSize;
    columnSize = *matrixColSize;
    int res = 0;
    for (int i = 0; i < rowSize; ++i) {
        for (int j = 0; j < columnSize; ++j) {
            int temp = recursive(matrix, i, j, -1);
            if (temp > res) res = temp;
        }
    }
    return res;
}
int rowSize;
int columnSize;
int directions[4][2] = {{-1, 0},
                        {1,  0},
                        {0,  -1},
                        {0,  1}};
int **dp;

bool isPositionLegal(int i, int j) {
    return i >= 0 && i < rowSize && j >= 0 && j < columnSize;
}

// 从matrix[i, j]出发,能走出来的最长递增路径长度
int recursive(int **matrix, int i, int j, int pre) {
    if (pre >= matrix[i][j]) return 0;
    if (dp[i][j] != -1) return dp[i][j];
    // 比pre更大(不会走回头路)
    int max = 0;
    for (int k = 0; k < 4; ++k) {
        int nextI = i + directions[k][0];
        int nextJ = j + directions[k][1];
        // 没越界
        if (isPositionLegal(nextI, nextJ)) {
            int temp = recursive(matrix, nextI, nextJ, matrix[i][j]);
            if (temp > max) max = temp;
        }
    }
    dp[i][j] = max + 1;
    return dp[i][j];
}

// 记忆化搜索
int longestIncreasingPath(int **matrix, int matrixSize, int *matrixColSize) {
    rowSize = matrixSize;
    columnSize = *matrixColSize;
    dp = (int **) malloc(sizeof(int *) * rowSize);
    for (int i = 0; i < rowSize; ++i) {
        dp[i] = (int *) malloc(sizeof(int) * columnSize);
        memset(dp[i], -1, sizeof(int) * columnSize);
    }
    int res = 0;
    for (int i = 0; i < rowSize; ++i) {
        for (int j = 0; j < columnSize; ++j) {
            int temp = recursive(matrix, i, j, -1);
            if (temp > res) res = temp;
        }
    }
    return res;
}
posted @ 2024-01-28 18:56  Sprinining  阅读(17)  评论(0编辑  收藏  举报