leetcode 343
整体来看就是不断对数字进行划分,然后不断求乘积。那么一半的做法应该是递归调用,但是会出现大量的重复,因为每个值对应的最大乘积没有保存
而动态规划的优势和核心思想就在于先变动会先固定下来的,然后把固定下来的值用于更新后固定下来的值的更新,把每一次先变动对应的值都存储下来了,这样就降低了复杂度。
class Solution { public int integerBreak(int n) { int[] dp=new int[n+1]; dp[1]=1; for(int i=2;i<=n;i++){ for(int j=1;j<=i-1;j++){//至少应该是1*(j-1) dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j])); //注意都没有算上本身值dp[i]相比j*(i-j)是本身值,j*(i-j)相比j*dp[i-j]是本身值 } } return dp[n]; } }
leetcode 279
这道题不难想用动态规划的方法,但是问题是怎么去遍历满足条件的平方数呢?
相比于上面的题目,没有条件限制,也就是直接用n与迭代的i进行做差都是可以满足的,但是本题是有条件限制的,所以想到的就是先把所有满足条件的平方数都求出来并
存储起来。
class Solution { public int numSquares(int n) { //找到满足条件的所有平方数 List<Integer> res=new ArrayList<>(); int diff=3; int squre=1; while(squre<=n){ res.add(squre); squre=squre+diff; diff+=2; } int[] dp=new int[n+1]; for(int i=1;i<=n;i++){ int max=Integer.MAX_VALUE;//注意这种写法 for(int s:res){ if(s>i) break;//不能大于n max=Math.min(max,dp[i-s]+1);//dp[i-s]+1中的1表示的是s的个数为1 } dp[i]=max; } return dp[n]; } }
leetcode 91
这道题毫无疑问看到首先想到DFS或者动态规划
动态规划主要在于:1.如何确定递推式;2.如何设置初值
1. 任意往后推进一个数字都是分为两部份的计算。如1 2
- 1|2 此时dp[2]+=dp[1],因为分法就是取决于2前面的数字1的值
- |1 2此时dp[2]+=dp[0],因此分法就是将2的前一个与2进行捆绑,不可能捆绑三个,所以最多只能捆绑2前面的一个数值。
2. 确定初值设置
- dp[0] 由于用到这个数值的时候类似|1 2的情况,其本身要算做一个,所以dp[0]=1
- dp[1] 由于表示只有一个字符的时候,此时如果这个数值为0表示无效,则设置为0,如果此时不是0,说明算一个字符,则为1
class Solution { public int numDecodings(String s) { int n=s.length(); if(s==null||n==0) return 0; int[] dp=new int[n+1]; dp[0]=1; dp[1]=(s.charAt(0)=='0')?0:1; for(int i=2;i<=s.length();i++){ int one=Integer.valueOf(s.substring(i-1,i));//one代表的是最后一个考虑的数字,如1|2中的2,1已经在初始值中考虑过了 if(one!=0) dp[i]+=dp[i-1]; if(s.charAt(i-2)=='0') continue; int two=Integer.valueOf(s.substring(i-2,i)); if(two<=26) dp[i]+=dp[i-2]; } return dp[n]; } }
leetcode 64
- 动态规划方法其实有一个核心问题需要去考虑,那就是建立递推公式的时候,以怎样的方式更新使得先更新的先确定下来(不会再变化),后更新的后稳定下来,也就是说要寻找一种策略或者说是搜索方法。但是我就是困在了不知道要怎么更新才会是不会重复或者说能够稳定下来的方法。
- 然后仔细审题发现,题目中说只能向下或者向右,我就一下通透了,说明不能走回头路(不能为了短路径兜圈子),所以也就说明对于(i,j)位置dp更新只能通过左侧或者上侧的数值来更新,不可能再从右侧或者下侧来到当前位置。
- 然后毫无疑问,用二维的dp数组一定可以不断进行更新,但是能不能用一维数组进行更新?
- 那么问题又来了,为什么一位数组就可以有效存储下一步会用到的数值进行对下一步的位置进行更新?
- 主要原因在于遍历的顺序是按行方向填充(列号逐增j++),一维数组只存储每一列当前的dp值,当在i,j位置的时候,此时的数值只与dp[i-1,j]和dp[i,j-1]有关,对应一维数组就是dp[j]和dp[j-1]
class Solution { public int minPathSum(int[][] grid) { int m=grid.length; int n=grid[0].length; int[] dp=new int[n]; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(j==0){ //在第一行 dp[0]=dp[0]+grid[i][j]; }else if(i==0){ //在第一列 dp[j]=dp[j-1]+grid[i][j]; }else{ dp[j]=Math.min(dp[j-1],dp[j])+grid[i][j]; } } } return dp[n-1]; } }
leetcode 70
题目进一步优化,通过考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2] 即可,使得原来的 O(n) 空间复杂度优化为 O(1) 复杂度。
优化前
class Solution { public int climbStairs(int n) { // int count_p=0; // while(n>=0) { // if(n==1){ // count_p=count_p+1; // return count_p; // } // else{ // climbStairs(n-1); // climbStairs(n-2); // count_p=count_p+2; // return count_p; // } // } // return count_p; if(n==1) return 1; int[] dp=new int[n+1]; dp[0]=1; dp[1]=1; for(int i=2;i<=n-1;i++){ dp[i]=dp[i-1]+dp[i-2]; } return dp[n-1]+dp[n-2]; } }
优化后
public int climbStairs(int n) { if(n == 1) return 1; if(n == 2) return 2; // 前一个楼梯、后一个楼梯 int pre1 = 2, pre2 = 1; for(int i = 2; i < n; i++){ int cur = pre1 + pre2; pre2 = pre1; pre1 = cur; } return pre1; }