leetcode之动态规划

核心就两步:

(1)确定dp[i]的含义

(2)写出状态转移方程

另外还要注意两点:

然后还要初始化dp数组一些位置的值,比如dp[0]和dp[1],因为递推的时候是需要先知道一些值才能推出另一些值

决定开多大数组的时候,可以想一下你要返回的是什么,根据这个推导出数组的长度,比如你要返回dp[n],那么数组的长度肯定是n+1,如果只开大小为n的数组,那dp[n]就越界了

1.以爬楼梯问题为例,总结动态规划的步骤:

第一步:定义dp数组(可能时一维数组,也可能是二维数组),这一步需要确定两个问题:

(1)dp[i]或者dp[i][j]是什么含义(比如在爬楼梯问题中,dp[i]表示到达第i层有多少种爬法

(2)到底开多大的空间,你搞懂了第一个问题dp[i]或者dp[i][j]是什么含义,就知道到底要开多大的空间了,由于在爬楼梯问题中,dp[i]表示到达第i层有多少种爬法,你最后求的是到达第n层所需要的爬法数,所以最后要返回的是dp[n],所以显然要开n+1大小的数组,这样才能有dp[n](如果你只开n大小的数组,那么最后只能返回dp[n-1])

第二步:初始化这个数组中的部分值

dp[0]题目已经给了等于1

dp[1]就是到达第一层有几种爬法,显然dp[1]=1

第三步:/写出状态转移方程确定dp数组中其他位置元素的值

class Solution 
{
    public int minCostClimbingStairs(int[] cost) 
    {
       
       //dp[i]表示到达第i级楼梯所需的最小代价,台阶的编号从0开始
       //比如总共有7级台阶,0号,1号,2号...6号,cost数组就是第0阶,第1阶,第2阶...第5阶向上爬的费用的,最后返回dp[6],所以dp数组长度是cost数组的长度+1
       int[] dp=new int [cost.length+1];

       //由于可以选择从0或者1号台阶出发,所以到达第0级台阶或者第一级台阶的花费为0
       dp[0]=0;
       dp[1]=0;
       for(int i=2;i<dp.length;i++)
       {
          dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
       }
       return dp[dp.length-1];     
    }
}

力扣509  斐波那契数列

class Solution {
    public int fib(int n) {

        if(n==0)  return  0;
        if(n==1)  return   1;
                          
        int[]  dp=new int[n+1];

        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

下面两道题青蛙跳台阶和爬楼梯是一样的题目

爬楼梯问题:爬到第n层有可能是从第n-1层然后跨一层到达第n层的,也可能时从第n-2层然后跨两层到达第n层的dp[i]=dp[i-1]+dp[i-2]

青蛙跳台阶:力扣

 leetcode70爬楼梯:力扣     dp[i]表示到达第i层有多少种爬法

class Solution 
{
    public int numWays(int n) 
    {
        if (n == 0) return 1;
        if (n == 1) return 1;

        //定义一个dp数组,dp[i]表示到达第i层有多少种爬法
        int[] dp = new int[n + 1];

        //初始化dp数组
        dp[0] = 1;
        dp[1] = 1;

        //写出状态转移方程确定数组中其他位置元素的值
        for (int i = 2; i <= n; i++) 
        {
            dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
        }
        return dp[n];
    }
}

力扣746 使用最小花费爬楼梯

 dp[i]表示到达第i级楼梯所需的最小代价

状态转移方程:有可能跨越一个台阶上来的,也有可能是跨越两个台阶上来的

所以状态转移方程:dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])注意传入的cost数组里面元素个数等于台阶数-1。因为最高台阶是没有代价的

2.连续子数组的最大和        力扣53

比如数组为:1,-2,3,10,-4,7,2,-5

以1结尾的所有子数组,以-2结尾的所有子数组,以3结尾的所有子数组.........以7结尾的所有子数组,以2结尾的所有子数组,以-5结尾的所有子数组

开一个数组dp[nums.length],

dp[i]表示以nums[i]结尾的连续子数组的最大和

比如:dp[0]表示以nums[0](也就是1)结尾的所有子数组中最大和,显然dp[0]=1

dp[1]表示以nums[1](也就是-2)结尾的所有子数组中最大和,显然dp[1]=-1

.......

显然dp[i+1]=max(nums[i+1],dp[i]+nums[i+1])

也就是说,dp[i+1]要么就是(1)等于dp[i]加上nums[i+1],即前面的子序列+当前位置这个数

(2)要么就是不要前面的子序列,只要nums[i+1]这个数(nums[i]这个数一定要有,因为是以这个数结尾的连续子数组)

其实到底要不要前面的子数组dp[i],就看它是不是大于0的,大于0肯定就要,是负数就不要

class Solution 
{
    public int maxSubArray(int[] nums) 
    {
        int[] dp=new int[nums.length];
        dp[0]=nums[0];
        for(int i=0;i<nums.length-1;i++)
        {
            dp[i+1]=Math.max(nums[i+1],dp[i]+nums[i+1]);
        }
        
        //对dp数组进行排序,找出最大值,最大值就是排序后的最后一个值
        Arrays.sort(dp);
        return dp[nums.length-1];
    }
}

如果题目要求你打印出这个 最大和连续子数组,dp[i]表示以nums[i]结尾的连续子数组的最大和

所以找出这个最大值,然后从后往前加,一直加到最大和为止

class Solution 
{
    public int[] maxSubArray(int[] nums) 
    {
        int[] dp=new int[nums.length];
        dp[0]=nums[0];
        for(int i=0;i<nums.length-1;i++)
        {
            dp[i+1]=Math.max(nums[i+1],dp[i]+nums[i+1]);
        }
        
        //对dp数组进行排序,找出最大值,最大值就是排序后的最后一个值
        Arrays.sort(dp);

        int  max=dp[nums.length-1];
        ArrayList result=new ArrayList();
        int sum=0; 

        for(int i=0;i<dp.length;i++)
        {
           if(dp[i]==max)
           {
              for(int j=i;j>0;j--)
              {
                 result.add(nums[j])
                 sum=sum+num[j];
                 if(sum==max)
                 {
                   result.reverse();
                   int[]  temp=new  int[result.size()];
                   for(int k=0;k<result.size();k++)
                   {
                     temp[k]=result.get(k);
                   }
                   return  temp;
                 }
              }
           }

        }
    }
}

3.用动态规划来创建前缀和数组

力扣303

class NumArray 
{
    //定义一个数组,这个数组是前缀和数组,也就是说presum[i]表示前i个数的和
    //也就是从nums[0]到nums[i]所有数之和

    int[]  presum;
    public NumArray(int[] nums)
    {
        //这里不接收nums数组,而是根据nums数组求出前缀和数组

        presum=new int[nums.length];
        //初始化
        presum[0]=nums[0];

        for(int i=0;i<nums.length-1;i++)
        {
            presum[i+1]=presum[i]+nums[i+1];
        }

    }
    
    public int sumRange(int left, int right)
    {
      if(left==0)
      {
          return presum[right];
      }
      return  presum[right]-presum[left-1];
    }
}

4.力扣55 跳跃游戏

从后往前,把数组填完

class Solution
{
    public boolean canJump(int[] nums)
    {
       //开一个数组,dp[i]代表nums[i]这个位置能否跳到最后一个下标
       boolean[]  dp=new  boolean[nums.length]; 

       dp[nums.length-1]=true;

       for(int i=nums.length-2;i>=0;i--)
       {
         //dp[i]=false;

         //遍历dp[i]后面的nums[i]个数,nums[i]表示第i个台阶最多可以前进nums[i]个位置
         //只要第i级台阶能跳到的台阶中,有一个台阶能跳到最后一级台阶,那第i级台阶就一定能跳到最后一级台阶
        //遍历第i级台阶能跳到的台阶,dp[i+1],dp[i+2]....dp[i+nums[i]],有一个能跳到,dp[i]就能跳到
         for(int j=1;j<=nums[i];j++)
         {
             if(dp[i+j]==true)
             {
                 dp[i]=true;
                 break;
             }
         }
       }
       return dp[0];
    }
}

5.力扣198 打家劫舍

其实概括一下就是不能偷连续两家

dp[i][0]表示0~i个房屋,而且不偷第i个屋,获得的最高金额
dp[i][1]表示0~i个房屋,而且偷第i个屋,获得的最高金额

状态转移方程:dp[i]和dp[i-1]之间的关系

class Solution 
{
    public int rob(int[] nums) 
    {
        int[][] dp=new int[nums.length][2];
        //dp[i][0]表示0~i个房屋,而且不偷第i个屋,获得的最高金额
        //dp[i][1]表示0~i个房屋,而且偷第i个屋,获得的最高金额

        dp[0][0]=0;
        dp[0][1]=nums[0];

        for(int i=1;i<nums.length;i++)
        {
           //第i个房屋不偷,所以前一个房屋偷不偷都可以
           dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);

           //如果第i个房屋偷,那前一个房屋不能偷
           dp[i][1]=dp[i-1][0]+nums[i];
        }
        
        //最后一个房子只有偷还是不偷两种可能
        return Math.max(dp[nums.length-1][0],dp[nums.length-1][1]);
    }
}

力扣213 打家劫舍||d

打家劫舍里面nums数组是nums[0]~nums[n-1]

就是将打家劫舍|里面的nums数组替换成nums[0]~nums[n-2]和nums[1]~nums[n-1],进行两次打家劫舍|

1.偷第一个房子,不偷最后一个房子,即只考虑nums[0]~倒数第二个元素的情况下的最大金额p1

2.不偷第一个房子,偷最后一个房子,即只考虑nums[1]~倒数第一个元素的情况下的最大金额p2

Math.max(p1,p2)即可

也就是打家劫舍2其实就是做两次打家劫舍1,然后求更大值即可

class Solution 
{
    public int rob(int[] nums) 
    {
        if(nums.length==0)  return 0;
        if(nums.length==1)  return nums[0];
        int[] nums1=Arrays.copyOfRange(nums, 0, nums.length - 1);
        int[] nums2=Arrays.copyOfRange(nums, 1, nums.length);
        int num1=help(nums1);
        int num2=help(nums2);
        return Math.max(num1,num2);
    }    
    public int help(int[] nums)
    {
        int[][] dp=new int[nums.length][2];
        //dp[i][0]表示0~i个房屋,而且不偷第i个屋,获得的最高金额
        //dp[i][1]表示0~i个房屋,而且偷第i个屋,获得的最高金额

        dp[0][0]=0;
        dp[0][1]=nums[0];

        for(int i=1;i<nums.length;i++)
        {
           //第i个房屋不偷,所以前一个房屋偷不偷都可以
           dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
           //如果第i个房屋偷,那前一个房屋不能偷
           dp[i][1]=dp[i-1][0]+nums[i];
        }
        return Math.max(dp[nums.length-1][0],dp[nums.length-1][1]);
    }    
}

6.不同路径

力扣62

dp[i][j]表示从(0,0)处到(i,j)这个位置有多少种不同的路径

状态转移方程:    dp[i][j]=dp[i-1][j]+dp[i][j-1];

到(i,j)这个位置要么上一步从(i-1,j)位置向下走一步,要么上一步从(i,j-1)位置向右走一步
           

class Solution 
{
    public int uniquePaths(int m, int n) 
    {
        int[][] dp=new int[m][n];
        //dp[i][j]表示从(0,0)处到(i,j)这个位置有多少种不同的路径

        dp[0][0]=1;

        //由于只能向下走或者是向右走一步
        //所以dp[i][0]也就是第一列,还有dp[0][j]也就是第一列都只有1种走法
        
        //第一列全部置为1
        for(int i=0;i<m;i++)
        {
            dp[i][0]=1;
        }
        //第一行全部置为1
        for(int j=0;j<n;j++)
        {
            dp[0][j]=1;
        }
        //现在根据状态转移方程填上除了第一行第一列的其他位置
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
             //到(i,j)这个位置要么上一步从(i-1,j)位置向下走一步,要么上一步从(i,j-1)位置向右走一步
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}

64. 最小路径和 - 力扣(LeetCode)leetcode64  最小路径和

比如希望从D走到B,最后一步有两种情况(1)从A走到B  (2)从C走到B

从D走到A的最小路径和为6,从D走到C的最小路径和为8

由于6<8,所以从D走到B的最小路径和是先从D走到A,然后再从A走到B

dp[i][j]的意思是从nums[0][0]到nums[i][j]的最小路径和

状态转移方程:dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+nums[i][j]; 只能从左边或者上面走过来

class Solution 
{
    public int minPathSum(int[][] nums) 
    {
        //m*n的矩阵
        int m=nums.length;
        int n=nums[0].length;

        //dp[i][j]的意思是从nums[0][0]到nums[i][j]的最小路径和
        int[][] dp=new int[m][n];

        dp[0][0]=nums[0][0];

        for(int i=1;i<m;i++)
        {
            //由于只能往右走或者往下走,所以第一列的位置只能从上一行来
            dp[i][0]=dp[i-1][0]+nums[i][0];
        }

        for(int j=1;j<n;j++)
        {
            //由于只能往右走或者往下走,所以第一行的位置只能从左边的一列来
            dp[0][j]=dp[0][j-1]+nums[0][j];
        }

        for(int i=1;i<m;i++)
        {
          for(int j=1;j<n;j++)
          {
            //d[i][j]的上一行元素是d[i-1][j](列值相同)
            //d[i][j]的左边一列元素是d[i][j-1](行值相同)
            dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+nums[i][j];
          }   
        }
        return dp[m-1][n-1];
    }
}

力扣剑指offer47 礼物的最大价值

和上面的最小路径和刚好相反,相当于求最大路径

只要改一行代码即可

dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+nums[i][j];

改成:

 dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+nums[i][j];

 63  不同路径||

与1相比只是多了障碍物,在代码上唯一不同的地方在于:初始化第一行和第一列的时候,如果这个位置有障碍物,那么dp[0][i]或者dp[i][0]就都等于0,也就是没有路径可达,其他代码是一样的

 与1相比只是多了障碍物,在代码上唯一不同的地方在于:初始化第一行和第一列的时候,如果这个位置有障碍物,那么这个位置以及这个位置往后的位置dp[0][i]或者dp[i][0]都等于0,也就是没有路径可达,其他代码是一样的

class Solution {
    public int uniquePathsWithObstacles(int[][] nums) {
        int m=nums.length;
        int n=nums[0].length;
        
        int[][] dp=new int[m][n];
         if(nums[0][0]==1)   return 0;

         dp[0][0]=1;

        //填第一行
        for(int i=1;i<n;i++)
        {
            if(nums[0][i]==1)
            {
                dp[0][i]=0;//这一行其实可以不写,默认这个位置就是0
                break;//这个位置以及这个位置后面的位置都是0
            }
            dp[0][i]=1;
        }

        //填第一列
        for(int j=1;j<m;j++)
        {
            if(nums[j][0]==1)
            {
                dp[j][0]=0;//这一行其实可以不写,默认这个位置就是0
                break;//这个位置以及这个位置后面的位置都是0
            }
            dp[j][0]=1;
        }

        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(nums[i][j]==1)  dp[i][j]=0;
                else
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];   
                }
            }
                
        }
        return  dp[m-1][n-1];
    }
}

leetcode72  编辑距离 

 dp[i][j]表示word1的前i个字母转换成word2的前j个字母所使用的最少操作,最终返回的是dp[length1][length2]

 

 

class Solution 
{
    public int minDistance(String word1, String word2)
    {
        //word1转化成word2
        
        
        int length1=word1.length();
        int length2=word2.length();

        //dp[i][j]表示word1的前i个字母转换成word2的前j个字母所使用的最少操作,最终返回的是dp[length1][length2]
        int[][] dp=new int[length1+1][length2+1];

        for(int i=0;i<=length1;i++)
        {
            //word1的前i个字母转换成word2的前0个字母,显然需要的最少操作数就是i(i个字母全部删除)
            dp[i][0]=i;
        }

        for(int j=0;j<=length2;j++)
        {
            //word1中的前0个字母转换成前j个字母,显然需要的最少的操作数就是j(插入j个字符)
            dp[0][j]=j;
        }

        for(int i=1;i<=length1;i++)
        {
            for(int j=1;j<=length2;j++)
            {
                //如果当前的字母相同,则dp[i][j] = dp[i - 1][j - 1],否则取增加,删除,替换三个操作的最小值
                if(word1.charAt(i-1)==word2.charAt(j-1))
                {
                    dp[i][j]=dp[i-1][j-1];
                }
                else
                {
                    //Math.min只能求两个数中更小的那一个,所以三个数求最小值需要用两次Math.min
                    //dp[i-1][j-1]+1表示替换一个字符
                    //dp[i-1][j]+1表示删除一个字符
                    //dp[i][j-1]+1表示插入一个字符
                    dp[i][j]=1+Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1]);
                }
            }
        }
        return dp[length1][length2];
    }
}

力扣279  完全平方数

也就是说求n最少可以表示成几个完全平方数之和,最多肯定是n个(拆成n个1之和)

关键是列举比n小的完全平方数,比如比21小的完全平方数有1,4,9,16

状态转移方程:dp[21]=Math.min(1+dp[21-1*1],1+dp[21-2*2],1+dp[21-3*3],1+dp[21-4*4])

=Math.min(1+dp[20],1+dp[17],1+dp[12],1+dp[5])

同理如果是求dp[72]

dp[72]=Math.min(1+dp[72-1*1],1+dp[72-2*2],1+dp[72-3*3],1+dp[72-4*4],1+dp[72-5*5),1+dp[72-6*6],1+dp[72-7*7],,1+dp[72-8*8])

dp[n]=Math.min(1+dp[n-1*1],1+dp[n-2*2],1+dp[n-3*3],1+dp[n-4*4]...........,1+dp[i-j*j])

j*j是小于n的最大完全平方数

class Solution
{
    public int numSquares(int n)
    {
        //求正整数n最少可以表示为几个完全平方数之和

        //dp[i]表示正整数i最少可以表示为几个完全平方数之和

        int[]  dp=new int[n+1];

        Arrays.fill(dp,Integer.MAX_VALUE);
        
        dp[0]=0;

        for(int i=1;i<=n;i++)
        {
            for(int j=1;j*j<=i;j++)
            {
                dp[i]=Math.min(dp[i],1+dp[i-j*j]);
            }
        }
        return  dp[n];
    }
}

力扣221 最大正方形

dp[i][j]的含义是以matrix[i]为右下角的正方形的最大边长

状态转移方程:dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i][j-1],dp[i-1][j-1]))+1;

class Solution
{
    public int maximalSquare(char[][] matrix)
    {
        int m=matrix.length;
        int n=matrix[0].length;

        //dp[i][j]的含义是以matrix[i]为右下角的正方形的最大边长
        int[][]  dp=new int[m][n];

        int  max=0;

        //初始化dp数组第一列
        for(int i=0;i<m;i++)
        {
            if(matrix[i][0]=='1')
            {
                dp[i][0]=1;
                max=1;
            }
        }

        //初始化dp数组第一行
        for(int i=0;i<n;i++)
        {
            if(matrix[0][i]=='1')
            {
                dp[0][i]=1;
                max=1;
            }
        }

        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(matrix[i][j]=='1')
                {
                    dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i][j-1],dp[i-1][j-1]))+1;
                    max=Math.max(dp[i][j],max);
                }
            }
        }
        return  max*max;
    }
}

139 单词拆分

这是一个完全背包问题,

class Solution 
{
    public boolean wordBreak(String target, List<String> wordDict)
    {
        //利用字符列表的单词是否可以拼出字符串s

        //dp[i]表示:target字符串中前i个字符组成的字符串能否由字典里的单词拼接成,i=1,2,3.....
        boolean[]  dp=new  boolean[target.length()+1];

        dp[0]=true;

        //开始填满dp数组
        for(int i=1;i<dp.length;i++)
        { 
            for(int j=0;j<i;j++)
            {
                if(dp[j]==true&&wordDict.contains(target.substring(j,i)))
                {
                  dp[i]=true;
                }
            }
        }

        return  dp[target.length()];
    }
}

力扣674   最长连续递增序列

 其实这道题不用动态规划也可以做,就是用一个指针遍历,一直滑呀滑呀滑,滑到不递增为止,继续重新开始滑

 dp[i]表示以nums[i]结尾的连续序列的最长长度(这个值最小是1)

核心代码:如果当前元素,比前一个元素大,那当前元素的dp[i]就比前一个元素的dp[i-1]要大1

           if(nums[i]>nums[i-1])
           {
               dp[i]=dp[i-1]+1;
           }
class Solution 
{
    public int findLengthOfLCIS(int[] nums) 
    {
        int[]  dp=new  int[nums.length];

        Arrays.fill(dp,1);

        int max=1;

        for(int i=1;i<nums.length;i++)
        {
           if(nums[i]>nums[i-1])
           {
               dp[i]=dp[i-1]+1;
           }
           max=Math.max(max,dp[i]);
        }
        
        return  max;
    }
}

力扣300  最长递增序列  不要求连续了,所以求dp[i]需要遍历nums[i]之前的每一个元素的dp[j],求出dp[j]中最大的

class Solution
{
    public int lengthOfLIS(int[] nums) 
    {
       //dp[i]表示以nums[i]结尾的最长递增子序列的长度
        int[] dp=new int[nums.length];

       // 初始化,所有位置先置为1,因为不管以什么结尾,起码自己本身的长度就是1
        Arrays.fill(dp,1);

        int result=1;

        for(int i=0;i<nums.length;i++)
        {
            //对于每一个位置,都要遍历前面的每个位置,前面比当前小的数中dp[i]最大的dp[i]加1,就是当前dp[i]
            int temp=0;
            for(int j=0;j<i;j++)
            {
                if(nums[j]<nums[i])
                {
                  //求出比当前数更小的数中,dp[i]最大的那个dp[i]
                  temp=Math.max(temp,dp[j]);               
                }
            }
            dp[i]=temp+1;
            //所有dp[i]中,最大的那个dp[i]就是我们要返回的结果
            result=Math.max(result,dp[i]);
        }
        return result;
    }
}

7.322. 零钱兑换 - 力扣(LeetCode)

 假设coins数组为[1,2,5,7,10],即面额为1,2,5,7,10

dp[0]=0

dp[1]=1

dp[2]=1

 dp[3]=dp[1]+两张1元的,或者是dp[3]=dp[2]+1张1元的

 所以dp[3]=Math.min(dp[1]+2,dp[2]+1)=2

 dp[4]=dp[3]+1张1元的,或者dp[2]+1张两元的,dp[4]=Math.min(dp[3]+1,dp[2]+1)=2

  .....

最后dp[14]=dp[13]+1张1元的,或者是dp[12]+1张2元的,或者dp[7]+1张7元的,或者dp[4]+1张10元

dp[14]=Math.min(dp[13]+1,dp[12]+1,dp[7]+1,dp[4]+1)

核心就是一句话:

remain_money=总金额-硬币的面值   这个数   能否被拼凑出来

如果这个数能被拼凑出来:

那拼凑总金额所需要的硬币数=dp[remain_money]+1

拼凑总金额所需要的最少硬币数=Math.min(dp[remain_money1]+1,dp[remain_money2]+1......)

class Solution 
{
    public int coinChange(int[] coins, int amount) 
    {
        //最后要拼出总金额amout
        int[] dp=new int[amount+1];
        //dp[i]的含义是;组成总金额i需要的最少钞票数量
        
        //数组初始化,所有位置填-1
        Arrays.fill(dp,Integer.MAX_VALUE);

        dp[0]=0;

        //依次求出dp[1],dp[2]......dp[amount]
        for(int i=1;i<=amount;i++)
        {
            //对于每个总金额i,遍历面值数组coins
            for(int j=0;j<coins.length;j++)
            {
              //总金额减去面值,如果dp[remain]不为-1,说明可以拼成总金额remain,那么就有dp[i]=dp[remain]+1(加1张表示加上1张已存在的面值)
              int remain_money=i-coins[j];
              
              //总金额比最小的面值还小(比如面值为2,5,10  现在轮到面值为10,总金额为8),那就下一轮面值(注意面值数组不一定是单调递增的)
              if(remain_money<0)  continue;

              //如果剩下的钱拼不出来,直接下一轮面值
              if(dp[remain_money]==Integer.MAX_VALUE)  continue;
 
              //如果剩下的钱可以拼出来
              if(dp[remain_money]!=Integer.MAX_VALUE)
              {
                  dp[i]=Math.min(dp[i],dp[remain_money]+1);
              } 
            }          
        }
        if(dp[amount]==Integer.MAX_VALUE)  return -1;
        return  dp[amount];
    }
}

8.377组合总和IV   

 这个其实就是变形版的爬楼梯问题:对于每一集级台阶可以往上前进nums[0]或者nums[1]......nums[n-1]个台阶

也就是说数组中每个元素都是能够前进的台阶数,target就是最终希望能够到第几级台阶

状态转移方程:dp[i]=dp[i-nums[0]]+dp[i-nums[1]]]+.......+dp[i-nums[n-1]],到达第i个台阶,

可以从i-nums[0]级台阶上来,也可以从i-nums[1]级台阶上来........

class Solution {
    public int combinationSum4(int[] nums, int target) {
        
      //dp[i]表示有多少种方式到第i级台阶
      int[] dp=new int[target+1];

      dp[0]=1;
      for(int i=1;i<=target;i++)
      {
          for(int num:nums)
          {
              if(i-num>=0)
              {
                 dp[i]=dp[i-num]+dp[i];
              }
          }
          
      }
      return dp[target];
    }
}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值