递归和动态规划专题(二)----剑指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有多少种?
- 对于矩阵dp第一列 ,dp[0….i][0]表示组成要找的数为0,很明显是1种即为不使用任何货币!
- 对与矩阵dp第一行,dp[0][0……aim]表示组成的数为0…aim,很明显dp[0][k*arr[0]]==1(0<=arr[0]*k<=aim)!
- 对于矩阵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; }}
****要回家啦,未完待续**
- 递归和动态规划专题(二)----剑指offer+左程云算法
- 递归和动态规划专题(一)----剑指offer+左程云算法
- 剑指offer-动态规划算法
- 贪心,递归,动态规划,及分治算法之间的区别和联系(二)
- 递归和动态规划的算法题(1)
- 剑指Offer——动态规划算法
- 剑指offer------动态规划与贪婪算法
- 动态规划和递归
- 递归和动态规划
- 动态规划和递归
- 递归和动态规划
- 递归和动态规划
- 递归和动态规划
- 动态规划和贪心算法问题(二)
- 剑指offer 链表专题(二)
- 算法学习-(二)动态规划算法
- 二、动态规划算法
- 递归和动态规划(1)
- c——复合类型——数组
- Maven
- 2017.1.13【初中部 】普及组模拟赛C组 excel 电子表格 题解
- mysql sql语句大全
- 13.2 连接到世界银行
- 递归和动态规划专题(二)----剑指offer+左程云算法
- 汇编实验16 编写包含多个功能子程序的中断例程——浅谈直接地址表
- open() close() 函数的使用
- 49个常用sql语句
- 设子数组A[0:k]和A[k+1:N-1]已排好序(0≤K≤N-1)。试设计一个合并这2个子数组为排好序的数组A[0:N-1]的算法。
- Android studio录屏按钮不能用怎么办
- 素数筛选
- 有向图的十字链表存储表式
- visual studio code jupyter错误!No kernel specs found