Acwing动态规划2——线性DP

本文详细介绍了动态规划在解决计算机科学问题中的应用,包括数字三角形的最大路径和、最长上升子序列的求解以及最短编辑距离的计算。对于每个问题,文章提供了原始代码并进行了优化,优化后的代码更简洁且效率更高。此外,还讨论了当数据规模增大时如何调整算法以避免超时,例如使用堆栈优化最长上升子序列的求解,将时间复杂度降低到O(nlogn)。
摘要由CSDN通过智能技术生成

1、数字三角形

题目链接

1.题目描述
在这里插入图片描述
2.思路

分析方法:闫氏DP分析
在这里插入图片描述
时间复杂度:遍历二维O(n^2),求最大值O(1) => O(n^2)

3.代码

package chapter05.src;

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/22 18:23
 */
public class p898 {
    static int N = 510;

    public static void main(String[] args) {
        int[][] a = new int[N][N], f = new int[N][N];//a:数字三角形 f:状态
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();//三角形的层数

        //输入
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= i; j ++) {
                a[i][j] = input.nextInt();
            }
        }

        //从下往上开始遍历
        for (int i = n; i >= 1; i --) {
            for (int j = 1; j <= i; j ++) {
                //f[i + 1][j + 1]:从右边上去的值 f[i + 1][j]:从左边上去的值
                f[i][j] = Math.max(f[i + 1][j + 1] + a[i][j], f[i + 1][j] + a[i][j]);
            }
        }
        //输出最顶端元素和
        System.out.println(f[1][1]);
    }
}

代码优化:
1、二维数组a可以不要,直接用f表示即可
2、代码写法的优化 :f[i][j] = Math.max(f[i + 1][j + 1] + a[i][j], f[i + 1][j] + a[i][j]);
=> f[i][j] += Math.max(f[i + 1][j + 1], f[i + 1][j]); 逻辑不变,写法简单

最终代码

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/22 18:23
 */
public class Main {
    static int N = 510;

    public static void main(String[] args) {
        int[][] f = new int[N][N];//a:数字三角形 f:状态
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();//三角形的层数

        //输入
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= i; j ++) {
                f[i][j] = input.nextInt();
            }
        }

        //从下往上开始遍历
        for (int i = n; i >= 1; i --) {
            for (int j = 1; j <= i; j ++) {
                //f[i + 1][j + 1]:从右边上去的值 f[i + 1][j]:从左边上去的值
                f[i][j] += Math.max(f[i + 1][j + 1], f[i + 1][j]);
            }
        }
        //输出最顶端元素和
        System.out.println(f[1][1]);
    }
}

2、最长上升子序列

1.题目描述
在这里插入图片描述
2.思路
在这里插入图片描述
f[i] : 从第一个数字开始计算,以a[i]结尾的最长上升子序列
时间复杂度:状态数n 每个状态需要的时间 n -->O(n^2)

3.代码

package chapter05;

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/24 11:24
 */
public class p895 {
    static int N = 1010;

    public static void main(String[] args) {
        int[] a = new int[N], f = new int[N];//a:序列 f:状态
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();//序列长度

        //输入序列
        for (int i = 0; i < n; i ++) {
            a[i] = input.nextInt();
        }

        for (int i = 0; i < n; i ++) {//从前往后计算某个状态的上升序列长度
            f[i] = 1;//只有a[i]一个数时,上升序列最长为1
            for (int j = 0; j < i; j ++) {
                if (a[j] < a[i]) {//f[j]:i之前的小于自己值的最长子序列长度
                    f[i] = Math.max(f[i], f[j] + 1);//f[j] + 1:+1表示加上自己
                }
            }
        }

        //输出最长子序列
        int res = 0;
        for (int i = 0; i < n; i ++) {
            res = Math.max(res, f[i]);
        }
        System.out.println(res);
    }

}

4.优化

如果数据范围增大到如下,用上面的O(n^2)复杂度的就会超时
数据范围:1≤N≤100000,−109≤数列中的数≤109

思路:模拟一个堆栈(用数组表示栈比直接使用库函数更快),遍历元素,如果该元素大于栈顶元素,入栈;否则替换掉第一个大于或等于该元素的数字

例 n: 7
arr : 3 1 2 1 8 5 6

stk : 3

13 小
stk : 1

21 大
stk : 1 2

12 小
stk : 1 2

82 大
stk : 1 2 8

58 小
stk : 1 2 5

65 大
stk : 1 2 5 6

stk 的长度就是最长递增子序列的长度

最终代码实现:

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/24 12:26
 * 数据范围增大,O(n^2)会超时,使用优化方法,时间复杂度O(nlogn)
 */
public class p896 {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();

        //输入序列
        int[] a = new int[n];
        for (int i = 0; i < n; i ++) {
            a[i] = input.nextInt();
        }

        int[] stk = new int[n];//模拟堆栈
        stk[0] = a[0];//现将第一个元素入栈
        int tt = 0;

        for (int i = 1; i < n; i ++) {
            if (a[i] > stk[tt]) {//如果当前元素大于栈顶元素,当前元素入栈
                stk[++ tt] = a[i];
            } else {
                //否则找到栈中第一个大于or等于该元素的数字,并用该元素替换
                //因为栈中元素是有序递增的,可以使用二分查找
                int l = 0, r = tt;
                while (l < r) {
                    int mid = (l + r) >> 1;
                    if (stk[mid] >= a[i]) {//数字在左半部分
                        r = mid;
                    } else {//数字在右半部分
                        l = mid + 1;
                    }
                }
                stk[l] = a[i];//l就是查找到的位置,替换
            }
        }
        System.out.println(tt + 1);
    }
}

3、最长公共子序列

1.题目描述
在这里插入图片描述

2.思路
在这里插入图片描述
3.代码

/**
 * @author mys
 * @date 2022/3/24 13:28
 */
public class poffer95_longestCommonSubsequence {
    public int longestCommonSubsequence(String text1, String text2) {
        int n = text1.length(), m = text2.length();
        int[][] f = new int[n + 1][m + 1];//状态
        //遍历所有状态
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= m; j ++) {
                //分为a[i] == a[j]和不等于两种情况来看
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    f[i][j] = f[i - 1][j - 1] + 1;//等于
                } else {
                    f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);//不等于,再看是否包含a[i]或者b[j]
                }
            }
        }
        return f[n][m];
    }
}

对应leetcode题目:剑指 Offer II 095. 最长公共子序列

4、最短编辑距离

1.题目描述
在这里插入图片描述

2.思路
在这里插入图片描述
集合划分的思路:通常看最后一个操作之前的状态,然后加入最后一个操作的状态
时间复杂度:遍历所有状态:n^2,每个状态操作次数:3,==> O(n^2)

3.代码

package chapter05;

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/25 13:38
 */
public class p902 {
    static int N = 1010;

    public static void main(String[] args) {
    	//输入
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        String a = input.next();
        int m = input.nextInt();
        String b = input.next();

        int[][] f = new int[N][N];

        //边界初始化
        //1.从a的前0个字符匹配b的前i个字符,进行的是插入操作,操作次数就是i
        for (int i = 0; i <= m; i ++) {
            f[0][i] = i;
        }
        //2.从a的前i个字符匹配b的前0个字符,进行的是删除操作,操作次数就是i
        for (int i = 0; i <= n; i ++) {
            f[i][0] = i;
        }
        //遍历所有状态
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= m; j ++) {
                //(1)f[i - 1][j] + 1:删除  (2)f[i][j - 1] + 1:插入
                f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
                //(3)替换,分为两种情况 charAt(i - 1):从1开始,所以需要-1
                if (a.charAt(i - 1) == b.charAt(j - 1)) {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
                } else {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
                }
            }
        }
        //求得把a的前n个字符与b的前m个字符进行匹配
        System.out.println(f[n][m]);
    }
}

4.改变条件
在这里插入图片描述
代码:

package chapter05.src;

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/3/25 14:15
 */
public class p899 {
    static int N = 1010, M = 15;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();//字符串个数
        int m = input.nextInt();//询问次数
        String[] str = new String[N];
        for (int i = 0; i < n; i ++) {
            str[i] = input.next();
        }

        for (int i = 0; i < m; i ++) {
            int count = 0;
            String s = input.next();
            int limit = input.nextInt();//限制的操作次数
            for (int j = 0; j < n; j ++) {
                if (minDistance(str[j], s) <= limit) {
                    count++;
                }
            }
            System.out.println(count);
        }
    }

    public static int minDistance(String s1, String s2) {
        int n = s1.length(), m = s2.length();
        int[][] f = new int[M][M];

        //边界条件初始化
        //1.从a的前0个字符匹配b的前i个字符,进行的是插入操作,操作次数就是i
        for (int i = 0; i <= m; i ++) {
            f[0][i] = i;
        }
        //2.从a的前i个字符匹配b的前0个字符,进行的是删除操作,操作次数就是i
        for (int i = 0; i <= n; i ++) {
            f[i][0] = i;
        }

        //遍历所有状态
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= m; j ++) {
                //f[i - 1][j] + 1:删除  f[i][j - 1] + 1:插入
                f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
                //替换,分为两种情况
                if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
                } else {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
                }
            }
        }
        //求得把a的前n个字符与b的前m个字符进行匹配的操作次数
        return f[n][m];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值