64. 最小路径和
public int minPathSum(int[][] grid) {
/**
严格位置依赖的DP+空间压缩技巧
整个大过程就是从上到小不停更新一维数组
其中更新一维数组的小过程就是从左到右
*/
int n = grid[0].length;
int m = grid.length;
// 用一个一维数组动态更新来记录想象中的二维缓存表
int[] dp = new int[n];
// 记录想象表的第0行,就是由grip表第0行从左往右计算得到
dp[0]=grid[0][0];
for(int j= 1; j<n; j++){
dp[j]=dp[j-1]+grid[0][j];
}
// 从上到小的大过程
for(int i = 1; i<m;i++){
// 0位置的更新
dp[0]+=grid[i][0];
// 从左到右的小过程
for(int j=1; j<n;j++){
// 当前位置的值取决于左边和上边的最小值
dp[j]=grid[i][j]+Math.min(dp[j-1],dp[j]);
}
}
return dp[n-1];
}
- 时间复杂度:O(m*n)
1143. 最长公共子序列
public int minPathSum(int[][] grid) {
/**
严格位置依赖的DP+空间压缩技巧
整个大过程就是从上到小不停更新一维数组
其中更新一维数组的小过程就是从左到右
*/
int n = grid[0].length;
int m = grid.length;
// 用一个一维数组动态更新来记录想象中的二维缓存表
int[] dp = new int[n];
// 记录想象表的第0行,就是由grip表第0行从左往右计算得到
dp[0]=grid[0][0];
for(int j= 1; j<n; j++){
dp[j]=dp[j-1]+grid[0][j];
}
// 从上到小的大过程
for(int i = 1; i<m;i++){
// 0位置的更新
dp[0]+=grid[i][0];
// 从左到右的小过程
for(int j=1; j<n;j++){
// 当前位置的值取决于左边和上边的最小值
dp[j]=grid[i][j]+Math.min(dp[j-1],dp[j]);
}
}
return dp[n-1];
}
- 时间复杂度:O(m*n)
516. 最长回文子序列
思路一:原始串与逆序串的最长公共子序列,就是原串的最长回文子序列
思路二:区间dp,从区间的首尾来尝试,整个过程是从下往上,从左往右,要得到的是右上角。分三种情况:1.对角线:区间长度为1;2.副对角线,区间长度为2;3.其余位置,又分为区间端点值相等否两种情况讨论。
1)当区间值不等(s[L] != s[R])时,取[L,R-1] 和 [L+1, R] 区间的最大值
2)当区间值相等(s[L] == s[R])时,2+[L+1, R+1]
public int longestPalindromeSubseq(String s) {
char[] str = s.toCharArray();
int n =str.length;
int[] dp=new int[n];
// 区间dp
for(int l=n-1, leftDown=0,backUp;l>=0;l--){
dp[l]=1;
if(l+1<n){
leftDown = dp[l+1];
dp[l+1]= str[l]==str[l+1] ? 2 : 1;
}
for(int r=l+2; r<n; r++){
backUp=dp[r];
if(str[l]==str[r]){
dp[r]=2+leftDown;
}else{
dp[r]=Math.max(dp[r],dp[r-1]);
}
leftDown=backUp;
}
}
return dp[n-1];
}
329. 矩阵中最长的递增子序列
该题无法整理出严格位置依赖,其路径取决于上下左右中小的一个。不过题意严格递增,不会出现走回头路的情况,只需要挂个dp表记录即可。
public int longestIncreasingPath(int[][] matrix) {
int n = matrix.length;
int m = matrix[0].length;
int[][] dp=new int[n][m];
int ans=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
ans=Math.max(ans,f(matrix,i,j,dp));
}
}
return ans;
}
public int f(int[][] matrix, int i, int j, int[][] dp){
// 只要更新过就不为0,因为至少包含本身,至少为1
if(dp[i][j]!=0){
return dp[i][j];
}
int next=0;
if(i>0 && matrix[i][j]<matrix[i-1][j]){
next=Math.max(next,f(matrix,i-1,j,dp));
}
if(i+1<matrix.length && matrix[i][j]<matrix[i+1][j]){
next=Math.max(next,f(matrix,i+1,j,dp));
}
if(j>0 && matrix[i][j]<matrix[i][j-1]){
next=Math.max(next,f(matrix,i,j-1,dp));
}
if(j+1<matrix[0].length && matrix[i][j]<matrix[i][j+1]){
next=Math.max(next,f(matrix,i,j+1,dp));
}
// 注意加上自身,长度+1
dp[i][j]=next+1;
return next+1;
}
97. 交错字符串
public boolean isInterleave(String s1, String s2, String s3) {
// 当s1和s2的长度和都不等于s3的长度时,一定不是交错字符串
if(s1.length() + s2.length() != s3.length()){
return false;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
char[] str3 = s3.toCharArray();
int n = str1.length;
int m = str2.length;
// s1[前缀长度为i]和s2[前缀长度为j],能否交错组成s3[前缀长度为i+j]
boolean[] dp = new boolean[m+1];
dp[0] = true;
// 严格位置依赖, 依赖于左和上方格子
for(int j = 1; j<=m; j++){
// 对应于想象的二维表第0行,左
if(str2[j-1]!=str3[j-1]){
break;
}
dp[j]=true;
}
for(int i = 1;i<=n; i++){
// 对应于想象的二维表第0列,当有一个不满足时,后面的全是false
dp[0] = str1[i-1]== str3[i-1] && dp[0];
for(int j=1;j<=m;j++){
// 左和上
dp[j]=(str1[i-1]==str3[i+j-1] && dp[j])
||(str2[j-1]==str3[i+j-1] && dp[j-1]);
}
}
return dp[m];
}
- 时间复杂度O(m*n)
72. 编辑距离
public int minDistance(String word1, String word2) {
char[] s1 = word1.toCharArray();
char[] s2 = word2.toCharArray();
int n =s1.length;
int m=s2.length;
// s1[前i个]变成s2[前j个]的最少操作
// 空间压缩,依赖于左上角、左边、上边的值
int[] dp = new int[m+1];
// 对应于想象表的第0行,插入*i
for(int j = 1; j<= m ; j++){
dp[j]=j;
}
for(int i = 1,leftUp,backUp; i<=n;i++){
dp[0]=i;
leftUp=(i-1);
for(int j=1; j<=m;j++){
backUp=dp[j];
if(s1[i-1]==s2[j-1]){
dp[j]=leftUp;
}else{
dp[j]=Math.min(Math.min(dp[j-1]+1,dp[j]+1),leftUp+1);
}
leftUp=backUp;
}
}
return dp[m];
}
- 时间复杂度O(m*n)
115. 不同的子序列
public int numDistinct(String s, String t) {
// 已结尾字符讨论,要得到的是右下角
// dp[i][j]:s[i个字符]的子序列有几个等于t[j个]
char[] s1 = s.toCharArray();
char[] s2 = t.toCharArray();
int n = s1.length;
int m = s2.length;
int[] dp = new int[m+1];
dp[0]=1;
for(int i=1; i<=n;i++){
// 取决于左上角和上边的值,所以,从右往左
for(int j=m; j>=1; j--){
// 两种情况,相等时,等于左上角
// 不等时,等于上边的值,因此不用更新
if(s1[i-1]==s2[j-1]){
dp[j]+=dp[j-1];
}
}
}
return dp[m];
}
- 时间复杂度O(m*n)