Java算法-动态规划!(附漫画解释动态规划链接)

什么是动态规划?
1. 书上的解释:

任何数学递推公式都可以直接转换成递归算法,但是基本实现使编译器常常不能正确对待递归算法,结果导致低效的程序。当怀疑很可能是这种情况时,我们必须再给编译器提供一些帮助,降递归算法重新写成非递归算法,让后者(非递归算法)把那些子问题的答案系统地记录在一个表内。利用这种方法的一种技巧叫做动态规划

2. 为什么要用到动态规划?

在这里不得不卖弄一下自己,这学期学校开设了 “马原” 的课程,而我也只记住了 黑格尔 的一句:“存在即合理”,想想在这用也挺合适的
在这里插入图片描述
大家可能都听过斐波那契数列数列吧,可能也有部分人求过斐波那契数列的某一项的值,最简单的方法可能就是用递归实现 求解斐波那契数列的某一项 ,但是这种方法 它不仅违反了递归四条基本法则的第四条:合成效益法则,同时它的时间复杂度也极大,用递归实现斐波那契数的冗余计算的增长也是爆炸性的,如下图:
在这里插入图片描述
此时 动态规划 就站出来了,它会将已经计算好的值存在一个表里,下次使用就可以直接拿出来而不是再计算一次,当然这种只是解决了时间复杂度的问题,没有解决空间复杂度的问题

再优化的话,就不应该用表来存取值,而是用临时变量来存取已经求出来的值,这样不仅优化了时间复杂度,也优化了空间复杂度

最终优化后的求斐波那契数的代码如下:

public static int fibonacci(int n){
    //若 n 为 1 时,直接返回边界值
    if(n <= 1)
       return 1;
    //定义 3 个临时变量存储当前斐波那契数和前两项斐波那契数
    int last = 1;
    int nextToLast = 1;
    int answer = 1;
    //当 n>=2 时才会进入该循环
    for( int i = 2; i <= n; i++){
       //求出当前斐波那契数
       answer = last + nextToLast;
       //重新给临时变量赋值,准备下一次循环
       nextToLast = last;
       last = answer;
    }
    return answer;
}

当然这种 动态规划 只是一维的,比较简单

漫画形象解释动态规划

别猜了,这当然不是我这种小菜鸡整理的
在这里插入图片描述

动态规划的实例操作

前几天在 LeetCode官网 刷题就遇到了一个动态规划的题(求最长回文子串),幸幸苦苦撸几小时代码,缺栽在了最后一个时间复杂度的测试上,那时的心情…我T(O)M(CAT)。
在这里插入图片描述

我的解题思路:暴力枚举法,for循环就完事了,以下是我的代码:

import java.util.Scanner;

public class Solution {
    static boolean flag=true;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (flag){
            boolean flag2=true;
            System.out.println("请输入一段字符串,我将会返给您一段最长回文子串...");
            String s = sc.nextLine();
            System.out.println(longestPalindrome(s));
            System.out.println("是否继续测试?请输入 y or n");
            while (flag2){
                String s1 = sc.nextLine();
                if(s1.equals("n")||s1.equals("N")){
                    flag=false;
                    flag2=false;
                }else if(s1.equals("y")||s1.equals("Y")){
                    flag2=false;
                }else {
                    System.out.println("输入格式不对,请重新输入 y or n");
                }
            }
        }
    }
    //求最长回文子串的方法
    public static String longestPalindrome(String s) {
        //解决空串问题
        String result="";
        //长度由大及小,可以在第一次就找到最长回文子串
        for (int i = s.length(); i > 0; i--) {
            //若该字串没有回文子串,则返回第一个字母
            if(i==1){
                result=s.substring(0,1);
                break;
            }
            //循环改变最长回文字串的起始位置,循环次数=字符串长度-最长回文字串的长度
            for (int j = 0; j <= s.length()-i; j++) {
                //循环比较 字符串以中心对称的字母是否相同 循环次数=字符串长度/2
                for (int k = 0; k < i/2; k++) {
                    //判断以中心对称的字母是否相同
                    Boolean flag = s.substring(j+k, j+k+1).equals(s.substring((i-1)+j-k, i+j-k));
                    //如果相同,结束这次循环
                    if(!flag){
                        break;
                    }
                    //如果索引到了字符串的中间索引,则说明这个字符串以中心对称的字母全部相同
                    if(k==(i-2)/2){
                        //进到这个判断语句则说明这个字符串就是我们想要的结果了
                        result=s.substring(j,j+i);
                    }
                }
            }
            //若进到下面的判断语句则说明:这个字符串是空串,结束循环并直接return空串
            if(!(result.equals(""))){
                break;
            }
        }
        //暴力枚举过后,返回得到的结果
        return result;
    }
}

该程序的时间复杂度是:O(n^3),挂掉也是理所应当的,菜鸡的心酸
--------------------------------------------------------------------------------------------------------------------------------------------------------
借鉴其他大佬的动态规划及思想深入理解动态规划,我只是跟着他的思想再理解一次动态规划,如下:
(一)状态

  • f[i][j]表示s的第 i 个字符到第 j 个字符组成的子串,是否为回文串

(二)转移方程

  1. 如果s的第 i 个字符和第 j 个字符相同的话,且 i + 1, 到 j - 1 的子串也是回文串的话,f[i][j] 也为回文串
    • f[i][j] = f[i + 1][j - 1] and s[i] == s[j]
    • 稍微要注意的是,如果 i == j 或者 i + 1 == j 的时候,也就是单个字符的子串和两个相邻字符的子串,就不需要f[i + 1][j - 1]了
  2. 如果s的第 i 个字符和第 j 个字符不同的话,f[i][j] 不是回文串
    • 然后注意遍历顺序,i 从最后一个字符开始往前遍历,j 从 i 开始往后遍历,这样可以保证每个子问题都已经算好了

(三) 理解代码

注释是我根据自己的理解加的,理解不透彻之处,望见谅…

import java.util.Scanner;

public class Test {
    static boolean flag=true;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (flag){
            boolean flag2=true;
            System.out.println("请输入一段字符串,我将会返给您一段最长回文子串...");
            String s = sc.nextLine();
            System.out.println(longestPalindrome(s));
            System.out.println("是否继续测试?请输入 y or n");
            while (flag2){
                String s1 = sc.nextLine();
                if(s1.equals("n")||s1.equals("N")){
                    flag=false;
                    flag2=false;
                }else if(s1.equals("y")||s1.equals("Y")){
                    flag2=false;
                }else {
                    System.out.println("输入格式不对,请重新输入 y or n");
                }
            }
        }
    }
    public static String longestPalindrome(String s) {
        //解决空串问题
        String result = "";
        int len = s.length();
        //模拟一个二维的表,第一个[]内的数字是字符串的起始索引,第二个[]内的数字是字符串的终止索引,
        //指的是从起始索引到终止索引的字符串是否回文,若回文则将默认值false改为true
        boolean[][] flags = new boolean[len][len];
        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                //如果s的第 i 个字符和第 j 个字符不同的话,f[i][j] 不是回文串,就不用进到判断语句改二维数组的值
                if(s.charAt(i) == s.charAt(j) && (j - i <= 1 || flags[i + 1][j - 1])) {
                    //若进到这个判断语句,则从i索引开始,j索引结束的字符串必回文,改它对应二维数组那一项的值
                    flags[i][j] = true;
                    //如果这个回文字符串的长度大于此前回文字符串的长度,则覆盖此前的result值
                    if(j - i >= result.length()){
                        result = s.substring(i, j + 1);
                    }
                }
            }
        }
        //返回结果
        return result;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值