递归和动态规划专题(二)----剑指offer+左程云算法

来源:互联网 发布:软考中级数据库工程师 编辑:程序博客网 时间:2024/05/20 23:33

递归和动态规划专题(二)—-剑指offer+左程云算法


【前言】1.动态规划算法是从暴力搜索算法优化过来的,如果我们不清楚暴力搜索的过程,就难以理解动态规划的实现,当我们了解了动态规划算法的基本原理的文字概述,实现条件之后,这时可能并不是太理解这种思想,去面对实际问题的时候也是无从下手,这个时候我们不能停留在文字层面上,而应该去学习经典动态规划算法的实现,然后倒回来看这些概念,便会恍然大悟。

  2.动态规划算法的难点在于 从实际问题中抽象出动态规划表dp,dp一般是一个数组,可能是一维的也可能是二维的,也可能是其他的数据结构。

 3.总体来说,动态规划算法就是一系列以空间换取时间的算法。
这里写图片描述


(一).矩阵的最小路径和

【题目】给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径1,3,1,0,6,1,0就是最小路径和,返回12.
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0

这是经典的动态规划问题:假设m是m行n列的矩阵,那么我们用dp[m][n]来抽象这个问题,dp[i][j]表示的是从原点到i,j位置的最短路径和。我们首先计算第一行和第一列,直接累加即可,那么对于其他位置,要么是从它左边的位置达到,要么是从上边的位置达到,我们取左边和上边的较小值,然后加上当前的路径值,就是达到当前点的最短路径。也就是说问题分为三种情况:第一行dp[0][j] 第一列 dp[i][0] 和非第一行第一列dp[i][j]. dp矩阵称为动态规划表!

import java.util.*;public int minPathSum1(int[][] m){    if(m==null||m.length==0||m[0]==null||m[0].length==null){        return 0;    }    rows=m.length;    cols=m[0].length;    int[][] dp=new int[rows][cols];    dp[0][0]=m[0][0];    //第一行都是前面的累加    for(int j=1;j<cols;j++){        dp[0][j] = dp[0][j-1]+m[0][j];    }    //第一列都是上面的累加    for(int i=1;i<cols;i++){        dp[i][0] = dp[i-1][0]+m[i][0];    }    //构建动态规划表dp    for(int i=2;i<cols;i++){        for(int j=2;j<cols;j++){            dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+m[i][j];        }    }    return dp[rows-1][cols-1];}

该方法时间复杂度为O(N*M),空间复杂度由于新建了动态规划表dp,所以为O(N*M)!但是动态规划可以通过空间压缩的方法将额外空间复杂度变成O(min{M,N}),也就是不使用M*N的dp矩阵,而是使用arr[]一维数组。

import java.util.*;public int minPathSum1(int[][] m){    if(m==null||m.length==0||m[0]==null||m[0].length==null){        return 0;    }    int more = Math.max(m.length,m[0].length);//行数和列数较多的那个    int less = Math.min(m.length,m[0].length);//行数和列数较少的那个    int[] arr= new int[less];    boolean rowMore = m.length==more;//判断是行数多还是列数多    arr[0]==m[0][0];    //让第一行或第一列赋值给arr数组    for(int i=1;i<less;i++){        arr[i]=arr[i-1]+(rowMore ? m[0][i]:m[i][0]);    }    //arr数组往下滚 从arr[0]开始逐渐得到该行的最短路径;    for(int i=1;i<more;i++){        arr[0]=arr[0]+(rowMore ? m[i][0]:m[0][i]);        for(int j=1;j<less;j++){            arr[j] = Math.min(arr[j-1],arr[j])+(rowMore?m[i][j]:m[j][i]);        }    }    return arr[less-1];}

(二).换钱的方法

【题目】给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。

【举例】

arr=[5,10,25,1],aim=0。组成0元的方法有1种,就是所有面值的货币都不用。所以返回1。

arr=[5,10,25,1],aim=15。组成15元的方法有6种,分别为3张5元,1张10元+1张5元,1张10元+5张1元,10张1元+1张5元,2张5元+5张1元,15张1元。所以返回6。

arr=[3,5],aim=2。任何方法都无法组成2元。所以返回0。

这个题目讲的是从暴力递归到动态规划:
递归 –> 记忆化搜索(备忘录方法)– dp –> dp状态合并!在这里直接给出动态规划基础方法及动态规划的优化方法!

【分析】生成行数为N,列数为aim+1的动态规划表矩阵dp,dp[i][j]的含义表示使用arr[0…i]种货币组成要找的数j有多少种?

  1. 对于矩阵dp第一列 ,dp[0….i][0]表示组成要找的数为0,很明显是1种即为不使用任何货币!
  2. 对与矩阵dp第一行,dp[0][0……aim]表示组成的数为0…aim,很明显dp[0][k*arr[0]]==1(0<=arr[0]*k<=aim)!
  3. 对于矩阵dp上非第一行第一列的数,即为位置(i ,j)。dp[i][j]有以下多种情况:

1.完全不用arr[i],用arr[0….i-1]货币组成j;
2.用一张arr[i],用arr[0….i-1]货币组成j-arr[i];
………
k+1.用k张arr[i],用arr[0….i-1]货币组成j-k*arr[i] (j-k*arr[i]>=0);

【注意】通过分析我们可以发现上述的第三种情况可以简化为dp[i][j] = dp[i-1][j]+dp[i][j-arr[i]];最终代码如下,时间复杂度为O(N*aim),空间复杂度为O(N*aim);
这里写图片描述

import java.util.*;public class Exchange {    public int countWays(int[] penny, int n, int aim) {    if(penny==null||penny.length==0||aim==0){        return 0;    }    int[][] dp=new int[penny.length][aim+1];    //第一列,均为0;    for(int i=0;i<penny.length;i++){        dp[i][0]=1;    }    //第一行,k*arr[0]为0;    for(int j=0;j*penny[0]<aim+1;j++){        dp[0][j*penny[0]]=1;    }    for(int i=1;i<penny.length;i++){        for(int j=1;j<aim+1;j++){            dp[i][j]=dp[i-1][j];            dp[i][j]+=j-penny[i]>=0?dp[i][j-penny[i]]:0;        }    }    return dp[penny.length-1][aim];    }}

【注意】通过动态规划的空间压缩,把dp矩阵用dp一维矩阵表示,滚下一行!使额外空间复杂度变为O(aim)方法。

import java.util.*;public class Exchange {    public int countWays(int[] penny, int n, int aim) {    if(penny==null||penny.length==0||aim==0){        return 0;    }    int[] dp=new int[aim+1];    for(int i=0;i*penny[0]<=aim;i++){        dp[i*dp[0]]=1;    }    for(int i=1;i<n;i++){        for(int j=1;j<=aim;j++){            dp[j]+=j-penny[i]>=0?dp[j-penny[i]]:0;        }    }    return dp[aim];    }}

(三).记录几道《剑指offer》中与连续子序列(连续子数组)有关的题目!

【题目】HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

public class Solution {    public int FindGreatestSumOfSubArray(int[] array) {        //记住:整数最大值为:2^31-1 0x7FFFFFFF   最小值:-2^31  0x80000000        if(array==null||array.length==0){            return 0;        }        int sum=0;        int greatSum=0x80000000;        for(int i=0;i<array.length;i++){            //如果sum小于0,把之前累加的都去除            if(sum<=0){                sum=array[i];            }else{                sum+=array[i];            }            if(sum>greatSum){                    greatSum = sum;            }        }        return greatSum;                 }}

【题目】小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列?
Good Luck!

import java.util.ArrayList;public class Solution {    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {       //和为S的连续正数序列;        ArrayList<ArrayList<Integer> > arrayList = new ArrayList<ArrayList<Integer> > ();        if(sum<3){            return arrayList;        }        int small=1;        int big=2;        int middle = (1+sum)/2;        int s=small+big;        //至少包括俩个数,所以由此可以把small<middle作为循环判断条件        while(small<middle){            if(s==sum){                arrayList.add(Sequence(small,big));            }            //当系列和大于sum以及small仍小于middle时;            while(s>sum&&small<middle){                s-=small;                small++;                if(s==sum){                    arrayList.add(Sequence(small,big));                }            }            big++;            s+=big;        }        return arrayList;    }    public ArrayList<Integer> Sequence(int small,int big){        ArrayList<Integer> list = new ArrayList<Integer>();        if(small<big){            for(int i=small;i<=big;i++){                list.add(i);            }        }        return list;    }}

【题目】输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

import java.util.ArrayList;public class Solution {    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {        //因为递增排序,所以考虑从左右俩边开始往中间遍历;        ArrayList<Integer> list= new ArrayList<Integer>();        if(array==null||array.length<2){            return list;        }        int i=0;        int j=array.length-1;        while(i<j){            int s=array[i]+array[j];            if(s>sum){                j--;            }else if(s<sum){                i++;            }else if(s==sum){                list.add(array[i]);                list.add(array[j]);                return list;            }        }        return list;    }}

(四).最长上升子序列问题(LIS)

【注意】为什么上面第三点要记录《剑指offer》里的子序列题目呢?因为我刷到这道题时发现思路和上面的几道题相似!然而非也,我错以为最长上升子序列为最长连续上升子序列,所以最优解仍然需要使用动态规划!

如果为最长连续上升子序列:

import java.util.*;public class LongestIncreasingSubsequence {    public int getLIS(int[] A, int n) {        //记录上升子序列的长度        int count=1;        if(A==null||n==0){            return 0;        }        int i=0;        int greatNum=1;        for(int j=0;j<n-1;j++){            if(A[j]>=A[j+1]){                count=1;            }else{                count++;            }            if(count>greatNum){                greatNum=count;            }        }        return greatNum;    }}

按照题意,使用动态规划的解法:

import java.util.*;public class LongestIncreasingSubsequence {   public int getLIS(int[] A, int n) {        // write code here        if (n <= 0)            return -1;        int[] dp = new int[n];        dp[0] = 1;        for (int i = 1; i < n; i++) {            int j = i - 1;            int max = 1;            while (j >= 0) {                if (A[i] > A[j]) {                    if (dp[j] + 1 > max)                        max = dp[j] + 1;                }                j--;            }            dp[i] = max;        }        int max = 1;        for (int i = 0; i < dp.length; i++) {            if (dp[i] > max)                max = dp[i];        }        return max;    }}

****要回家啦,未完待续**

0 0
原创粉丝点击