描述:给定n个整数(可能为负数)组成的序列 a[1],a[2],a[3]... ,a[n],求该序列如a[i]+a[i+1]+... +a[j]的子段和的最大值。 当所给的整数均为负数时定义子段和为0,如果序列中全部是负数则最大子断和为0,依此定义,所求的最优值为Max{0,a[i]+a[i+ 1] +....+a[1},1≤i≤j≤n。输入:-2,11,-4,13,-5,-2 ;输出:20
分析:状态dp[i]表示以第i个元素结尾的最大字段和
dp[0] : 0 dp[1] : 11 + dp[0] = 11 dp[2] : -4 + dp[1] = 7 dp[3] : 13 + dp[2] = 20 dp[4] : -5 + dp[3] = 15 dp[5] : -2 + dp[4] = 13 dp[0] = 0 状态转移方程:dp[i] = ar[i] + dp[i-1]
代码:
import java.util.Arrays;
public class sum {
public static void main(String[] args) {
int[] arr = new int[]{-2,11,-4,13,-5,-2};
int[] dp = new int[arr.length];
int sum = maxSegmentSum(arr,dp);
System.out.println (Arrays.toString (dp));
System.out.println (sum);
}
private static int maxSegmentSum(int[] arr, int[] dp) {
dp[0] = arr[0];
//负数的最大字段和为0
if (dp[0] < 0){
dp[0] = 0;
}
int max = dp[0];
for (int i = 1; i < arr.length; i++) {
dp[i] = arr[i] + dp[i-1];
if(dp[i] < 0){
dp[i] = 0;
}
//找出最大的字段和赋值给max
if(max < dp[i]){
max = dp[i];
}
}
return max;
}
}
描述:
/** * 描述: * 有一个三角数组,形如: * 5 * 12 6 * 7 13 18 * 12 14 10 9 * 求从最上面元素开始,每一层选择一个元素,每一个元素可以向下选择, * 向左斜下方选择,向右斜下方选择,问最终选择出来的数字的最大值是多少? * */
思路:我们可以先将最后一层的元素进行遍历,存储到dp中,然后从倒数第二列开始向下选择,即每个数有三种相加可能(选择最后一层元素),将相加后的得到数中最大数存储到dp中,然后逐层向上遍历,就可求出每一层元素相加最大和
void func(int[] arr, int i, int j){
arr[i][j] + func(arr[i+1][j]);//向下面加
arr[i][j] + func(arr[i+1][j-1]);//向左斜下加
arr[i][j] + func(arr[i+1][j+1]);//向右斜下加}
代码:
public class 三角数组求和的最大值 {
public static void main(String[] args) {
int[][] arr = new int[][]{
{5},
{12, 6},
{7, 13, 18},
{12, 14, 10, 9}
};
int[][] dp = new int[arr.length][arr.length];
int row = arr.length - 1;
//将最后一层的数赋值给dp,作为已知值
for (int i = 0; i < arr[row].length; i++) {
dp[row][i] = arr[row][i];
}
int sum = maxSum (arr,dp);
System.out.println (sum);
// //递归:
// int max = func (arr,0,0,dp);
// System.out.println (max);
}
/**
* 非递归形式
*/
private static int maxSum(int[][] arr, int[][] dp) {
//从倒数第二行开始遍历(倒数第一列元素以遍历完赋值给dp)
for (int i = arr.length - 1 - 1; i >= 0; i--) {
for (int j = 0; j < arr[i].length; j++) {//从每行的第一个数开始遍历
//向下面加
int n1 = arr[i][j] + dp[i+1][j];
//向右斜下加
int n2 = arr[i][j] + dp[i+1][j+1];
int n3 = 0;
//向左斜下加,需做判断
if(j > 0){
n3 = arr[i][j] + dp[i+1][j-1];
}
//将最大值赋值给dp
dp[i][j] = Math.max (Math.max(n1,n2),n3);
}
}
return dp[0][0];
}
/**
* 递归
* @param arr
* @param i
* @param j
* @param dp
* @return
*/
private static int func(int[][] arr, int i, int j, int[][] dp) {
//界限判断
if(i > arr.length-1 || j < 0 || j > arr[i].length-1){
return 0;
}
//防止子规模重复求解
if(dp[i][j] > 0){
return dp[i][j];
}
int n1 = arr[i][j] + func(arr, i+1, j, dp);
int n2 = arr[i][j] + func(arr, i+1, j-1, dp);
int n3 = arr[i][j] + func(arr, i+1, j+1, dp);
dp[i][j] = Math.max(Math.max(n1, n2), n3);
return dp[i][j];
}
}
描述:给定一串字符串,求其中最长的字符序列从小到大排序的序列长度
/**
* 其状态dp[i]表示以第i个元素结尾的最长非降子序列的长度值,
* 状态转移方程是:dp(i) = max{1, dp(j)+1}
* 其中j dp[i]){
dp[i] = dp[j] + 1;
}
}
if(max < dp[i]){
max = dp[i];
}
}
return max;
}
}
/**
* 非递归
* 状态:dp[m][n]表示两个序列的最大公共字段和的值
* 动态规划方程为:
* dp[m][n]=1 + dp[m-1][n-1] (str1[i]=str2[j])
*/
public class test {
public static void main(String[] args) {
String str1 = "helloworld";
String str2 = "helxtld";
int[][] dp = new int[str1.length () + 1][str2.length () + 1];
int sum = func (str1, str2, dp);
System.out.println (sum);
}
private static int func(String str1, String str2, int[][] dp) {
int k = 0;
int max = 0;
for (int m = 1; m <= str1.length (); ++m) {
for (int n = 1; n <= str2.length (); ++n) {
if (str1.charAt (m - 1) == str2.charAt (n - 1)) {
dp[m][n] = 1 + dp[m - 1][n - 1]; // 对角线
}
if (max < dp[m][n]) {
max = dp[m][n];
//公共字段在两个字符串中都有,将k赋成任意一个字符串公共长度就行
k = m;
}
}
}
//substring()方法用于提取字符串中介于两个指定下标之间的字符
System.out.println (str1.substring ((k-max),k));
return max;
}
}
运行结果:
我们能得到公共字符串长度,和公共字符串
hel
3
public class test {
public static void main(String[] args) {
String str1 = "helloworld";
String str2 = "helxtld";
int[][] dp = new int[str1.length ()+1][str2.length ()+1];
// int lcs = func (str1, str2,str1.length ()-1,str2.length ()-1, dp);
int lcs1 = func (str1,str2,dp);
// System.out.println (lcs);
System.out.println (lcs1);
// for (int i = 0; i < dp.length; i++) {
// for (int j = 0; j < dp[i].length; j++) {
// System.out.print (dp[i][j] + " ");
// }
// System.out.println ();
// }
}
}
/**
* 非递归实现LCS
* @param str1
* @param str2
* @param dp
* @return
*/
private static int func(String str1, String str2, int[][] dp) {
/**
* dp[m][n] : str1以m位元素结尾,str2以n位元素结尾
* 的两个字符串的LCS的长度
*
* if(xm == yn)
* dp[m][n] = 1 + dp[m-1][n-1]
* else {
* dp[m][n] = max{dp[m][n-1], dp[m-1][n]}
* }
*/
for(int m=1; m<=str1.length(); m++){
for(int n=1; n<=str2.length(); n++){
if(str1.charAt(m-1) == str2.charAt(n-1)){
dp[m][n] = 1 + dp[m-1][n-1]; // 对角线
} else {
if(dp[m][n-1] > dp[m-1][n]){
dp[m][n] = dp[m][n-1]; // 左边
} else {
dp[m][n] = dp[m-1][n]; // 上面
}
}
}
}
return dp[str1.length()][str2.length()];
}
/**
* 递归实现
* @param str1
* @param str2
* @param m
* @param n
* @param dp
* @return
*/
private static int func(String str1, String str2, int m, int n, int[][] dp) {
if(m < 0 || n < 0){
return 0;
}
if(dp[m][n] > 0){
return dp[m][n];
}
if(str1.charAt(m) == str2.charAt(n)){
dp[m][n] = 1 + func(str1, str2, m-1, n-1, dp);
return dp[m][n];
} else {
int n1 = func(str1, str2, m, n-1, dp);
int n2 = func(str1, str2, m-1, n, dp);
if(n1 > n2){
dp[m][n] = n1;
} else {
dp[m][n] = n2;
}
return dp[m][n];
}
}
}
上面的代码只是打印出最长公共子序列的长度,如果想要打印最长公共子序列的元素,则要记录dp表的走向,然后用回溯法进行元素打印,代码如下:
public static void main(String[] args) {
String str1 = "helloworld";
String str2 = "helxtld";
int[][] dp = new int[str1.length () + 1][str2.length () + 1];
// 辅助数组,记录LCS元素的走向
int[][] path = new int[str1.length () + 1][str2.length () + 1];
int lcs = func (str1, str2, dp, path);
System.out.println ("不一定连续长度:" + lcs);
//打印bp表,记录bp表走向
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[i].length; j++) {
System.out.print (dp[i][j] + " ");
}
System.out.println ();
}
backstrace (str1, str1.length (), str2.length (), path);
}
//打印最长公共子序列元素,需要用递归实现,在回溯回来时打印才能保证正序输出
private static void backstrace(String str1, int m, int n, int[][] path) {
if (m < 0 || n < 0) {
return;
}
if (path[m][n] == 1) {
backstrace (str1, m - 1, n - 1, path);
System.out.print (str1.charAt (m - 1));
} else {
if (path[m][n] == 2) {
backstrace (str1, m, n - 1, path);
} else {
backstrace (str1, m - 1, n, path);
}
}
}
private static int func(String str1, String str2, int[][] dp, int[][] path) {
for (int m = 1; m <= str1.length (); ++m) {
for (int n = 1; n <= str2.length (); ++n) {
if (str1.charAt (m - 1) == str2.charAt (n - 1)) {
dp[m][n] = 1 + dp[m - 1][n - 1]; // 对角线
path[m][n] = 1;
} else {
if (dp[m][n - 1] > dp[m - 1][n]) {
dp[m][n] = dp[m][n - 1]; // 左边
path[m][n] = 2;
} else {
dp[m][n] = dp[m - 1][n]; // 上面
path[m][n] = 3;
}
}
}
}
return dp[str1.length ()][str2.length ()];
}
}
运行结果:
不一定连续长度:5
0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 1
0 1 2 2 2 2 2 2
0 1 2 3 3 3 3 3
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 4
0 1 2 3 3 3 4 5
helld
打印出的bp表,会将对角线上递增的数记录下来,即公共子序列的长度