1)动态规划中有道题,是查找两个字符串a,b中的最长公共子串;设一个dp[i][j]数组,字符串a以第i个字符结尾,b中以第j个字符结尾时的公共子串长度。如果直接按照如下编码,也能通过测试用例,但其实会有一些问题;运行完下面方法之后不退出线程,会发现dp数组有一些地方为0,按道理说只要存在公共子字符串,就不能为0,如abcxy和abwxy,如果按照上边对dp数组的定义,就会有
dp[1][1]=1,表示字符串a和a中公共子字符串长度;
dp[2][2]=2,表示字符串ab和ab中公共子字符串长度;
dp[3][3]=2,表示字符串abc和abw中公共子字符串长度;
dp[4][4]=2,表示字符串abcx和abwx中公共子字符串长度;
dp[5][5]=2,表示字符串abcxy和abwxy中公共子字符串长度;
但按照下边程序运行后,dp[3][3]=0,dp[4][4]=1;这个就与上边根据dp定义得出的结论不同了,明明字符串abc和abw中的公共子字符串是ab,长度为2,但是程序得出1呢?
经过我的调试分析,发现其实dp的定义中有句话需要明确下,就是这句话:字符串a以第i个字符结尾,b中以第j个字符结尾;这里的以某某字符结尾,具体理解应该是该字符也属于公共子字符串的一部分,如果不属于那么前边的就不能进行计数,意思是需要重新计数,因为要求出最长的公共子字符串,所以遇到断开处,需要重新将该点处的dp[i][j]置为0,重新计数;如abc和abw,c和w不同,所以程序会置dp[3][3]=0;当下个字母都为x,则dp[4][4]=dp[3][3]+1;如果不重置,那么dp[4][4]就变成了了2+1,等于3了,但是abcx和abwx的公共子字符串根本没有长度为3的情况,只是有3个一样的字符;
public static String resolve65(String str1,String str2){
if(str1.length() > str2.length()){
String temp=str1;
str1=str2;
str2=temp;
}
int m = str1.length() + 1;
int n = str2.length() + 1;
int[][] dp = new int[m][n];
int max=Integer.MIN_VALUE,index=0;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
if(max < dp[i][j]){
max = dp[i][j];
index = i;
}
}
}
}
return str1.substring(index-max,index);
}
2)动态规划中还有一道题与这道题类似,给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
这道题用int型一维数组dp[i],表示以nums[i-1]结尾的元素中最大连续子数组和,dp[0]默认0,不使用;这里我个人觉得,在动态规划中,当使用dp[i]数组时,统一表示成第i个元素前后比较方便,那么dp数组长度就是nums数组长度加1,dp[0]不用,从dp[1]开始,表示第一个元素,那么对于nums数组就是索引为0的元素;否则一会儿dp[0]表示nums[0],一会dp[1]表示nums[0],对我来说不是很方便,当然因人而异;
public static int maxSubArray(int[] nums){
//dp[i]表示以nums[i-1]结尾的元素中最大连续子数组和
int[] dp = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
if(dp[i-1] > 0){
dp[i]=dp[i-1]+nums[i-1];
}else{
dp[i]=nums[i-1];
}
}
int max=Integer.MIN_VALUE;
//遍历的时候从dp[1]开始,dp[0]不计入比较
for (int i = 1; i < dp.length; i++) {
max=Math.max(max,dp[i]);
}
return max;
}
包括杨辉三角的题也是这样的,按常规dp[i][j]表示第i行第j列且实际计算时从索引0开始,但这里我方便自己做到统一处理,只要用到dp数组,我就把索引1当做第一个元素;
public static List<List<Integer>> generate(int numRows){
int[][] dp = new int[numRows + 1][numRows + 1];
List<List<Integer>> list = new ArrayList();
for (int i = 1; i <= numRows; i++) {
ArrayList tempList = new ArrayList();
for (int j = 1; j <= i; j++) {
if(j==1 || j==i){
dp[i][j]=1;
}else {
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
}
tempList.add(dp[i][j]);
}
list.add(tempList);
}
return list;
}
3)动态规划中最长递增子序列问题(梅花桩问题)和最大子数组和的区别在于,前者以第i个数结尾子序列中,若第j个数小于第i个数(j < i),则dp[i]取dp[i]和(dp[j]+1)之间的较大者,如2 3 4 1 5,虽然1小于5,2 3 4均小于5,所以dp[5]取4,只有一个元素的时候dp[i]为1;而最大子数组的关键在于以第i个数结尾数组中,若前i-1个数之和小于0,则丢弃,否则累加;
题目:
Redraiment可以选择任意一个起点,从前到后,但只能从低处往高处的梅花桩走。他希望走的步数最多,你能替Redraiment研究他最多走的步数吗?
输入描述:
数据共2行,第1行先输入数组的个数,第2行再输入梅花桩的高度
dp[1]表示第一个数代码如下
public static void meihuazhuang(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String count = br.readLine();
String str = br.readLine();
int[] dp = new int[Integer.valueOf(count)+1];
int max=0;
for (int i = 1; i <= Integer.valueOf(count); i++) {
dp[i]=1;
for (int j = 1; j < i; j++) {
if(Integer.valueOf(str.split(" ")[j-1]) < Integer.valueOf(str.split(" ")[i-1])){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
max=Math.max(dp[i],max);
}
System.out.println(max);
} catch (IOException e) {
e.printStackTrace();
}
}
dp[0]表示第一个数代码如下
public static void meihuazhuang(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String count = br.readLine();
String str = br.readLine();
int[] dp = new int[Integer.valueOf(count)];
int max=0;
for (int i = 0; i < Integer.valueOf(count); i++) {
dp[i]=1;
for (int j = 0; j < i; j++) {
if(Integer.valueOf(str.split(" ")[j]) < Integer.valueOf(str.split(" ")[i])){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
max=Math.max(dp[i],max);
}
System.out.println(max);
} catch (IOException e) {
e.printStackTrace();
}
}
4)由最长子序列可以延伸出合唱队问题,中间有一个数字最大,两边各自递减;现在已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。无非是左边求最大递增子序列,右边求最大递减子序列;
public static void hechangdui(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
int count = Integer.valueOf(br.readLine());
String[] str = br.readLine().split(" ");
int[] dp1 = new int[count+1];
int[] dp2 = new int[count+1];
int max1=0;
int max2=0;
for (int i = 1; i <= count; i++) {
dp1[i]=1;
for (int j = 1; j < i; j++) {
if(Integer.valueOf(str[i-1]) > Integer.valueOf(str[j-1])){
dp1[i]=Math.max(dp1[i],dp1[j]+1);
}
}
max1=Math.max(dp1[i],max1);
}
for (int i = count; i >= 1; i--) {
dp2[i]=1;
for (int j = count; j > i; j--) {
if(Integer.valueOf(str[i-1]) > Integer.valueOf(str[j-1])){
dp2[i]=Math.max(dp2[i],dp2[j]+1);
}
}
max2=Math.max(dp2[i],max2);
}
int max3=0;
for (int i = 1; i <= count; i++) {
max3 = Math.max(dp1[i]+dp2[i]-1,max3);
}
System.out.println(count-max3);
} catch (IOException e) {
e.printStackTrace();
}
}
上边是dp[1]表示第一个数据;如果是dp[0]表示第一个数据,那么代码如下
public static void hechangdui(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
int count = Integer.valueOf(br.readLine());
String[] str = br.readLine().split(" ");
int[] dp1 = new int[count];
int[] dp2 = new int[count];
int max1=0;
int max2=0;
for (int i = 0; i < count; i++) {
dp1[i]=1;
for (int j = 0; j < i; j++) {
if(Integer.valueOf(str[i]) > Integer.valueOf(str[j])){
dp1[i]=Math.max(dp1[i],dp1[j]+1);
}
}
max1=Math.max(dp1[i],max1);
}
for (int i = count-1; i >= 0; i--) {
dp2[i]=1;
for (int j = count-1; j > i; j--) {
if(Integer.valueOf(str[i]) > Integer.valueOf(str[j])){
dp2[i]=Math.max(dp2[i],dp2[j]+1);
}
}
max2=Math.max(dp2[i],max2);
}
int max3=0;
for (int i = 0; i < count; i++) {
max3 = Math.max(dp1[i]+dp2[i]-1,max3);
}
System.out.println(count-max3);
} catch (IOException e) {
e.printStackTrace();
}
}
5)走方格也是动态规划常见题,这里有两种出题方式,一种是沿着走格子,一种是沿着线走,比如,2行2列的格子,如果沿着格子走,那么只有2种方式,但是如果沿着线走,那么就有6种方式;
先看走方格,现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。
dp[i][j]从索引为1的数据开始,即dp[1][1]开始,代码如下
public static int uniquePathsWithObstacles(int[][] obstacleGrid){
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int[][] dp = new int[m+1][n+1];
if(obstacleGrid[0][0]==1){
return 0;
}
for (int i = 1; i <= m && obstacleGrid[i-1][0] == 0; i++) {
dp[i][1]=1;
}
for (int j = 1; j <= n && obstacleGrid[0][j-1]==0; j++) {
dp[1][j]=1;
}
for (int i = 2; i <= m; i++) {
for (int j = 2; j <= n; j++) {
if(obstacleGrid[i-1][j-1]==1){
continue;
}
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
dp[i][j]从索引为0的数据开始,即dp[0][0]开始,代码如下
public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] a = new int[m][n];
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
a[i][0]=1;
}
for (int i = 0; i < n && obstacleGrid[0][i] == 0; i++) {
a[0][i]=1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if(obstacleGrid[i][j] == 1)continue;
a[i][j]=a[i-1][j]+a[i][j-1];
}
}
return a[m-1][n-1];
}
走线与走方格的区别是前者比后者要多一行一列,如二行二列的数,走方格就是二行二列,而走线的话,就是三行三列;
dp[i][j]把索引为1的数据当做第一个数据的话,则代码如下,走线那么原来的m行n列,实际要走m+1行,n+1列,然后由于是从索引1开始的,所以dp数组长度为m+2行,n+2列,行从索引1到索引m+1,列从索引1到索引n+1;
public static void zouXian(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s = br.readLine();
String[] s1 = s.split(" ");
int m = Integer.valueOf(s1[0]);//横向格子数
int n = Integer.valueOf(s1[1]);//纵向格子数
int[][] dp = new int[m+2][n+2];
for (int i = 1; i <=m+1 ; i++) {
for (int j = 1; j <=n+1 ; j++) {
if(i==1 || j==1){
dp[i][j]=1;
}else{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
System.out.println(dp[m+1][n+1]);
} catch (IOException e) {
e.printStackTrace();
}
}
dp[i][j]把索引为0的数据当做第一个数据的话,则代码如下,走线那么原来的m行n列,实际要走m+1行,n+1列,然后由于是从索引0开始的,所以dp数组长度就是m+1行,n+1列,行从索引0到索引m,列从索引0到索引n;
public static void zouXian(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s = br.readLine();
String[] s1 = s.split(" ");
int n=Integer.valueOf(s1[0]);
int m=Integer.valueOf(s1[1]);
int[][] dp = new int[n+1][m+1];
for (int i = 0; i <= n; i++) {
dp[i][0]=1;
}
for (int j = 0; j <= m; j++) {
dp[0][j]=1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
System.out.println(dp[n][m]);
} catch (IOException e) {
e.printStackTrace();
}
}
从左上角到右下角走格子(不是走线),还有一种求路径的和最小,也一样的;
当dp[i][j]从索引1开始表示第一个数,代码如下
public static int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m+1][n+1];
dp[1][1]=grid[0][0];
for (int i = 2; i <= m; i++) {
dp[i][1]=dp[i-1][1]+grid[i-1][0];
}
for (int i = 2; i <= n; i++) {
dp[1][i]=dp[1][i-1]+grid[0][i-1];
}
for (int i = 2; i <= m; i++) {
for (int j = 2; j <= n; j++) {
dp[i][j]=Math.min(dp[i][j-1],dp[i-1][j])+grid[i-1][j-1];
}
}
return dp[m][n];
}
当dp[i][j]从索引0开始表示第一个数,代码如下
public static int minPathSum(int[][] grid) {
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 < m; i++) {
dp[i][0]=dp[i-1][0]+grid[i][0];
}
for (int i = 1; i < n; i++) {
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j]=Math.min(dp[i][j-1],dp[i-1][j])+grid[i][j];
}
}
return dp[m-1][n-1];
}
6)动态规划常见题还有计算两个字符串的编辑距离,牛客网华为机试第52题
public static void bianjiJuli(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s1 = br.readLine();
String s2 = br.readLine();
int l1 = s1.length();
int l2 = s2.length();
int[][] dp = new int[l1+1][l2+1];
//这道题感觉这么理解更好理解
//首先是特殊情况,当dp数组有一个为0时
for (int i = 0; i <= l1; i++) {
//表示长度为0的字符串变为长度为i的字符串所需要的编辑距离
//注意这里是所需要的编辑距离
dp[i][0]=i;
}
for (int j = 0; j <= l2; j++) {
dp[0][j]=j;
}
//另外一种情况是,当字符串长度不为0时,dp[i][j]表示字符串A的前i位与
// 字符串B的前j位之间转换成一样所需要的编辑次数,即编辑距离
for (int i = 1; i <= l1; i++) {
for (int j = 1; j <= l2; j++) {
if(s1.charAt(i-1) == s2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
}
System.out.println(dp[l1][l2]);
} catch (IOException e) {
e.printStackTrace();
}
}
牛客网华为机试第61题放苹果
public static void fangPingguo(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String[] str = br.readLine().split(" ");
int m = Integer.valueOf(str[0]);
int n = Integer.valueOf(str[1]);
int[][] dp = new int[m+1][n+1];
String s = Integer.toBinaryString(2);
for (int i = 0; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if(i==0 || i==1 || j==1){
dp[i][j]=1;
continue;
}
if(i<j){
dp[i][j]=dp[i][i];
}else {
dp[i][j]=dp[i-j][j]+dp[i][j-1];
}
}
}
System.out.println(dp[m][n]);
} catch (IOException e) {
e.printStackTrace();
}
}
牛客网华为机试第71题 字符串通配符
public static void zifuchuantongpei(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s1 = br.readLine();//通配符
String s2 = br.readLine();//字符串
int m = s1.length();
int n = s2.length();
boolean[][] dp = new boolean[m+1][n+1];
dp[0][0]=true;
for (int i = 1; i <= m; i++) {
if(s1.charAt(i-1) == '*'){
dp[i][0]=true;
}else{
break;
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if(Character.toLowerCase(s1.charAt(i-1)) == Character.toLowerCase(s2.charAt(j-1)) ||
(s1.charAt(i-1)=='?')&&(Character.isDigit(s2.charAt(j-1))||Character.isLetter(s2.charAt(j-1)))){
dp[i][j]=dp[i-1][j-1];
}else if(s1.charAt(i-1)=='*'){
//dp[i][j-1]表示*匹配至少一位,那么此时即使去掉一个字符,*一样能匹配,故j-1;
//dp[i-1][j]表示*匹配空,即去掉*,不影响,故i-1;
dp[i][j]=dp[i][j-1] || dp[i-1][j];
}
}
}
System.out.println(dp[m][n]);
} catch (IOException e) {
e.printStackTrace();
}
}
牛客网华为机试第85题 字符串通配符
public static void huiwenzichuan(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s = br.readLine();
int len = s.length();
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < len; i++) {
dp[i][i]=true;
}
int max=1;
for (int substrLen = 2; substrLen <= len; substrLen++) {
for (int i = 0; i < len; i++) {
int j=substrLen+i-1;
if(j>=len)break;
if(s.charAt(i) != s.charAt(j)){
dp[i][j]=false;
}else {
if(j-i<3){
dp[i][j]=true;
}else{
dp[i][j]=dp[i+1][j-1];
}
if(dp[i][j] && max<j-i+1){
max=j-i+1;
}
}
}
}
System.out.println(max);
} catch (IOException e) {
e.printStackTrace();
}
}
以及力扣上一些题