1.爬楼梯问题,到达楼梯的第i阶有多少中爬法
关键:第i阶楼梯,只可能从楼梯第i-1阶与i-2阶到达,所以到达第i阶的爬法与第i-1阶、第i-2阶的爬法直接相关。
这类问题分为四步:
1)原问题与子问题:找到子问题
2)第i个状态即为i阶台阶的所有走法数量
3)确认边界状态的值,边界状态为1阶台阶有一种走法,2阶有两种走法,即dp[1]=1,d[2]=2
4)确定状态转移方程dp[n] = dp[i-1] + dp[i-2]
代码实现:
public class Solution { public int JumpFloor(int target) { if(target<0) return -1; int[] dp = new int[target+2]; //每个数组里存的是有多少种方法 dp[1] = 1; dp[2] = 2; for(int i=3;i<=target;i++){ dp[i] = dp[i-1] + dp[i-2]; } return dp[target]; } }
扩展:如果青蛙一次可以跳一阶,两阶,三阶,求到n阶台阶的跳法:
dp[n] = dp[n-1] + dp[n-2] + dp[n-3]
2.打家劫舍问题
一条直线上,有n个房屋,每个房屋中有数量不等的财宝,盗贼从房屋盗取财宝,如果从相邻的两个房屋盗取财宝就会触发报警器,问在不触发报警器的前提下,最多可以获取多少财宝。
记忆化搜索:
首先偷取了数组下标为0的房子,此时下一个房子就是从[2,n-1](因为不能偷取相邻的),可以理解题目是求从下标为0出开始偷取的最大值,如果先偷取了下标为0,那么转变为求从下标为2出开始偷取的最大值(递归下去),同理如果偷取的是第1个房子,此时下一个房子的偷取范围[3,n-1].........
从图中可以看到重叠子问题,所以可以使用记忆化搜索的方式。
代码实现(记忆化搜索方式):
class JianZhiOffer{ static int[] memo; //memo[i]表示抢劫nums[i...n]所能获得的最大收益。 public static void main(String[] args) { int[] a = {5,2,6,3,1,7}; memo = new int[a.length]; Arrays.fill(memo,-1); System.out.println(tryRob(a,0)); } public static int tryRob(int[] nums,int index){ if(index>=nums.length) //这个条件??? return 0; if(memo[index]!=-1) return memo[index]; int temp = -1; for(int i=index;i<nums.length;i++){ if(nums[i]+tryRob(nums,i+2)>temp) temp = nums[i]+tryRob(nums,i+2); memo[index] = temp; } return temp; } }这里注意递归结束的条件:
if(index>=nums.length) //这个条件??? return 0;
如果index = 3,此时temp = nums[3] + tryRob(nums,5);其实tryRob(nums,5) = 0
如果index = 4 ,此时temp = num[4] + tryRob(nums,6);其中tryRob(nums,6) = 0
memo[i]表示考虑抢劫nums[i...n]所能获得的最大收益关键:选择第i个房间盗取财宝,就一定不能选择第i-1个房间盗取财宝,若不选择第i个房间盗取财宝,则相当于只考虑前i-1个房间盗取财宝。
与上题的思路有一点一样:上题n级台阶有两种情况到达,第一种是从n-1台阶,另一个中是从n-2级台阶。
这题,盗取第n个房间的财宝,有两种方式,一种是从n-1房间,另一种是从n-2个房间
状态转移方程:dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
代码实现:
class DP{ public static void main(String[] args){ int[] a = {5,2,6,3,1,7}; rob(a); } public static void rob(int nums[]){ int[] dp = new int[nums.length]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); for(int i=2;i<=nums.length-1;i++){ dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]); } System.out.println(dp[nums.length-1]); } }
3.最大字段和
给定一个数组,求这个数组的连续子数组中,最大的那一段的和例如:{14,-2,4,-3,5,7,2,-39,22},和最大子序列是{14,-2,4,-3,5,7,2},返回27。
思路:求n个数的数组的最大子序列的和,转为分别求以第1个、第2个......第n个数字结尾的最大自序列和,在找出n个结果中的最大值。
dp[i]代表以第i个数字结尾的最大子段和,求dp[i]与dp[i-1]的关系。
代码实现:
//数组的所有连续子段的最大和 class DP { public static void main(String[] args) { int[] a = {14,-2,4,-3,5,7,2,-39,22}; maxSum(a,a.length); } public static void maxSum(int[] a,int n) { int[] dp = new int[a.length]; dp[0] = a[0]; for(int i=1;i<n;i++) { dp[i] = Math.max(dp[i-1]+a[i], a[i]); } //System.out.println(Arrays.toString(dp)); //取出dp数组中的最大值 int max = Integer.MIN_VALUE; for(int i=0;i<dp.length;i++) { if(dp[i]>max) max = dp[i]; } System.out.println(max); } }
4.剑指Offer(第二版)面试题14:剪绳子
题目一:给你一根长度为n的绳子,请把绳子剪成m段 (m和n都是整数,n>1并且m>1)每段绳子的长度记为k[0],k[1],...,k[m].请问k[0]*k[1]*...*k[m]可能的最大乘积是多少?
例如,当绳子的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最大乘积是18.
分析:定义函数dp(n)为把长度为n的绳子剪成若干段后各段程度乘积的最大值。在剪第一刀的时候,我们有n-1中选择,也就是剪出来的第一段绳子的可能长度分别为1,2,.....;n-1,对应的第二段绳子的长度为n-1,n-2......1。因此 dp(n)=max(dp(i)*dp(n-i)),其中0<i<n。我们按照从下到上的顺序计算,也就是说我们先得到dp[(2)、dp(3)...然后求得dp(n),其中dp(n)是dp(1)*dp(n-1),dp(2)*dp(n-2).....dp(n-1)*dp(1)中的最大值。举例子说明:n=4时,dp[4]=dp[1]*dp[3],表示将长度为4的绳子,剪成1,3两段后的乘积,同样dp[2] = dp[2]*dp[2],表示将长度为4的绳子,减成2,2两段后的乘积。
代码实现:
//剪绳子问题 class JianZhiOffer{ public static void main(String[] args) { System.out.println(maxCutting(8)); } public static int maxCutting(int length){ if(length<=1) //如果小于等于1,输入不合法 return 0; if(length==2) //可以分为1,1 return 1; if(length==3) //可以分为:1,2 1,1,1 return 2; int[] dp = new int[length+1]; //dp[n]计为长度为n的绳子剪开后的最大乘积 dp[0] = 0; dp[1] = 1; dp[2] = 2; dp[3] = 3; //从dp[1]-dp[3]都包含了自己在内 for(int i=4;i<=length;i++){ int max = 0; for(int j=1;j<=i/2;j++){ int temp = dp[j]*dp[i-j]; //长度为i的绳子的剪开后的最大的绳子等于它被剪开后子绳子的最大长度 if(temp>max) max = temp; } dp[i] = max; } return dp[length]; } }
说明:从dp[1]到dp[3]都包含了自己在内,而从dp[4]开始是按照题意所求的值并且从4开始都是由1,2,3拼凑成的。
同样的问题有
5.给定一个m*n的方格,起始点为方格的最左上方,终点为方格的最右下方,一个机器人只能向下以及向右移动,需要求出机器人从起始点到终点一共有多少种不重复的路径。问题的输入为方格的长度m以及宽度n,输出为不同路径的数量。
https://blog.csdn.net/codekiller_/article/details/73008244https://blog.csdn.net/codekiller_/article/details/73008244
机器人只能经过方格上方或者方格左侧到达方格,dp[i][j] = dp[i-1][j] + dp[i][j-1],同时dp[0][j] = dp[i][0] = 0。
代码实现:
class DP{ public static void main(String[] args) { int m = 3; int n = 6; int[][] dp = new int[m][n]; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(i==0||j==0){ dp[i][j] = 1; } else{ dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ System.out.print(dp[i][j]+" "); } System.out.println(); } System.out.println(dp[m-1][n-1]); } }
同种类型的题:
https://blog.csdn.net/weixin_38314447/article/details/79074795
代码实现(动态规划的方法):
class DP{ public static void main(String[] args) { int m = 9; int n = 9; int[][] dp = new int[9][9]; for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ if(i==0||j==0){ dp[i][j] = 1; } else{ dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ System.out.print(dp[i][j]+" "); } System.out.println(); } System.out.println(dp[m-1][n-1]); } }
DFS的方法实现:http://www.it610.com/article/5451938.htm
分析:在遇到grid[i][j] = 1的地方,dp[i][j] 设置为0
代码实现:
class DP{ public static void main(String[] args) { int m = 3; int n = 3; int[][] grid = new int[m][n]; //格子中的数组 grid[1][1] = 1; //其中中间值为1 int[][] dp = new int[m][n]; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(i==0||j==0){ dp[i][j] = 1; } else if(grid[i][j]==0){ dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } }//打印看一下dp矩阵 for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ System.out.print(dp[i][j]+" "); } System.out.println(); } System.out.println(dp[m-1][n-1]); } }
1 1 1 1 0 1 1 1 2 2
在[1,1]位置,dp[1][1]设为0.
6.完美平方数问题