算法之动态规划

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();
        }
    }

以及力扣上一些题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

orcharddd_real

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值