经典动态规划1

经典的DP题,面试的时候直接用DP写就好了,不用从暴力递归,到数据记录,再到枚举糙动态规划,

DP 是递归时候 发现的用空间换时间的方法, 比如LCS:要求左上,左边,上边,再做决策,然后这三者都是有重复计算的,所以考虑用DP把结果存下来,下次重复利用


用DP的时候,先明确一点:dp[i][j]表示的是什么!然后想怎么根据之前的计算决策当前!(决策的方案可以先从最简单的情况入手(比如输入只有1行。。。),推导出规律,然后看能不能应用到一般情况)


1. 最长递增子序列

对于一个数字序列,请设计一个复杂度为O(nlogn)的算法,返回该序列的最长上升子序列的长度,这里的子序列定义为这样一个序列U1,U2...,其中Ui < Ui+1,且A[Ui] < A[Ui+1]。

给定一个数字序列A及序列的长度n,请返回最长上升子序列的长度。

测试样例:
[2,1,4,3,1,5,6],7
返回:4
import java.util.*;

/*
 * 递归是可以解得
 * 那就可以考虑DP来优化
 */
public class AscentSequence {
    public int findLongest(int[] A, int n) {
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int rst = 1;
        
        for(int i=1; i<n; i++)
        	for(int j=0; j<i; j++)
        		if(A[i] > A[j])
        			dp[i] = Math.max(dp[i], dp[j]+1);
        
        for(int i : dp)	rst = Math.max(rst, i);
        return rst;
    }
}


2.最长公共子序列

对于两个字符串,请设计一个高效算法,求他们的最长公共子序列的长度,这里的最长公共子序列定义为有两个序列U1,U2,U3...Un和V1,V2,V3...Vn,其中Ui&ltUi+1,Vi&ltVi+1。且A[Ui] == B[Vi]。

给定两个字符串AB,同时给定两个串的长度nm,请返回最长公共子序列的长度。保证两串长度均小于等于300。

测试样例:
"1A2C3D4B56",10,"B1D23CA45B6A",12
返回:6
public class LCS {
    public int findLCS(String A, int n, String B, int m) {
    	char[] s1 = A.toCharArray(), s2 = B.toCharArray();
        int[][] dp = new int[s1.length+1][s2.length+1];
        
        for(int i=1; i<=s1.length; i++) {
        	for(int j=1; j<=s2.length; j++) {
        		if(s1[i-1] == s2[j-1])
        			dp[i][j] = dp[i-1][j-1] + 1;
        		else
        			dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
        	}
        }
        
        return dp[s1.length][s2.length];
    }
}
如果还要求路径,从右下角方向推回去就行了:如果dp[i][j]比dp[i-1][j]和dp[i][j-1]都大,那说明dp[i][j]是由dp[i-1][j-1]推过来的,所以i,j位置对应的字符一定就出现在LCS中,就要往左上走,反之就可以往左走或者往上走

3最长公共子串

对于两个字符串,请设计一个时间复杂度为O(m*n)的算法(这里的m和n为两串的长度),求出两串的最长公共子串的长度。这里的最长公共子串的定义为两个序列U1,U2,..Un和V1,V2,...Vn,其中Ui + 1 == Ui+1,Vi + 1 == Vi+1,同时Ui == Vi。

给定两个字符串AB,同时给定两串的长度nm

测试样例:
"1AB2345CD",9,"12345EF",7
返回:4
public class LongestSubstring {
    public int findLongest(String A, int n, String B, int m) {
    	char[] s1 = A.toCharArray(), s2 = B.toCharArray();
        int[][] dp = new int[s1.length+1][s2.length+1];
        int rst = 0;
        
        for(int i=1; i<=s1.length; i++) {
        	for(int j=1; j<=s2.length; j++) {
        		if(s1[i-1] == s2[j-1]) {
        			dp[i][j] = dp[i-1][j-1] + 1;
        			rst = Math.max(rst, dp[i][j]);
        		}
        	}
        }
        
        
        
        return rst;
    }
}

用while循环模拟对应关系,用一个变量对应而不是构建矩阵:可以做到O(1)复杂度



4最小编辑代价

对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。

给定两个字符串AB,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。保证两串长度均小于等于300,且三种代价值均小于等于100。

测试样例:
"abc",3,"adc",3,5,3,100
返回:8
/*
 * 直接DP,不要先算步数
 * 但也是对dp[i][j]有三种决策方案
 */
public class MinCost {
    public int findMinCost(String A, int n, String B, int m, int c0, int c1, int c2) {
    	char[] s1 = A.toCharArray(), s2 = B.toCharArray();
        int[][] dp = new int[s1.length+1][s2.length+1];
        
        // 这里需要注意初始化的问题,dp[0][i]对应的不是0
        for(int i=0; i<s2.length+1; i++)
        	dp[0][i] = i * c0;
        for(int i=0; i<s1.length+1; i++)
        	dp[i][0] = i * c1;
        
        for(int i=1; i<=s1.length; i++) {
        	for(int j=1; j<=s2.length; j++) {
        		if(s1[i-1] == s2[j-1]) {
        			dp[i][j] = dp[i-1][j-1];
        		} else {
        			// 用A的最后一位update为B的最后一位
        			int t1 = dp[i-1][j-1] + c2;
        			
        			// 在A最后面插入一位成为B的最后一位
        			int t2 = dp[i][j-1] + c0;
        			
        			// 在A最后面删除一位,露出来的最后一位成为B的最后一位
        			int t3 = dp[i-1][j] + c1;
        			
        			dp[i][j] = Math.min(t1, Math.min(t2, t3));
        		}
        	}
        }
        
        return dp[s1.length][s2.length];
    }
}
这个题还可以用滚动数组优化空间


5字符串交错组成

对于三个字符串A,B,C。我们称C由A和B交错组成当且仅当C包含且仅包含A,B中所有字符,且对应的顺序不改变。请编写一个高效算法,判断C串是否由A和B交错组成。

给定三个字符串A,BC,及他们的长度。请返回一个bool值,代表C是否由A和B交错组成。保证三个串的长度均小于等于100。

测试样例:
"ABC",3,"12C",3,"A12BCC",6
返回:true
/*
 *	这些题都是可以写出递归解法的 
 *	接下来可以考虑怎么把递归的解法写成DP的方式
 */

public class Mixture {
    public boolean chkMixture(String A, int n, String B, int m, String C, int v) {
        if(n + m != v)	return false;
        char[] s1 = A.toCharArray(), s2 = B.toCharArray(), s3 = C.toCharArray();
        return check(s1, 0, s2, 0, s3, 0);
    }

	private boolean check(char[] s1, int i, char[] s2, int j, char[] s3, int k) {
		if(k == s3.length)	return true;
		if(i<s1.length && s1[i] == s3[k] && check(s1, i+1, s2, j, s3, k+1))	return true;
		if(j<s2.length && s2[j] == s3[k] && check(s1, i, s2, j+1, s3, k+1))	return true;
		return false;
	}
	
	public boolean chkMixture(String A, int n, String B, int m, String C, int v) {
		if(n + m != v)	return false;
		char[] s1 = A.toCharArray(), s2 = B.toCharArray(), s3 = C.toCharArray();
		boolean[][] dp = new boolean[n+1][m+1];
		dp[0][0] = true;
        for(int i=1; i<=v; i++)
        	for(int j=0; j<=i-1 && j<=n; j++)
       			if(i-1-j<=m && dp[j][i-1-j]) {
       				if(j<n && s1[j]==s3[i-1])
       					dp[j+1][i-1-j] = true;
       				
       				// 与上面不是if-else的关系,两者可能都是可以的
       				if(i-1-j<m && s2[i-1-j]==s3[i-1])
       					dp[j][i-j] = true;
       			}
        
        return dp[n][m];
    }
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值