Leetcode1 动态规划 & Java语法知识点

前言

开始刷力扣了,希望能在实习生面试的时候不拉胯。

题目

322.给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

思路

一眼看出来典型的01背包问题啊,要用动态规划或者回溯法做。
然而我的算法真的都还给老师了,不记得动态规划要怎么做了,所以采用了贪心算法。即先给coins数组排序,通过递归取得尽可能多的面值大的货币,比如11=5+5+1就是这个思想。
这个方法极具迷惑性,然而WA在了coins={186,419,83,408},amount=6249上。贪心得出来的是26,标答是20。我举个简单的例子,面额有1,30,31,amount为300。取31元的9枚后就只能取21枚1元的来组成300;只取30元的则只需要10枚。
所以还是只能用动态规划/回溯做。更详细的解析可以去官网看这题的solution,我这里只是简单整理一下思路。

代码

自上而下

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] count=new int[amount];
        return recursion(coins,amount,count);
    }
    public int recursion(int[] coins,int remain,int[] count){
        if(remain==0) return 0;//终止条件 成功
        if(remain<0) return -1;//终止条件 失败
        if(count[remain-1]!=0) return count[remain-1];//已经计算过,直接拿来用即可。注意!=改为>将语句无效
        int min=-1;
        for(int i:coins){
            int res=recursion(coins,remain-i,count);
            if(res==-1) continue;//失败 不修改min
            else if(res>=0){
                if(min==-1)min=res+1;
                if(res+1<min)min=res+1;//成功,修改min;res+1>min不修改min
            }
        }
        //count[remain-1]存储remain余额时的最佳情况,-1为失败
        if(min==-1) count[remain-1]=-1;
        else count[remain-1]=min;
        return count[remain-1];
    }
}

自下而上

class Solution {
    public int coinChange(int[] coins, int amount) {
        int max=amount+1;//这里需要一个比amount大的数,方便比较取小
        int[] matrix=new int[amount+1];
        Arrays.fill(matrix,max);
        matrix[0]=0;//成功
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.length;j++){
                if(i>=coins[j])
                matrix[i]=Math.min(matrix[i],matrix[i-coins[j]]+1);
            }
        }
        if (matrix[amount]==max) return -1;//matrix[i]=max代表不可行
        else return matrix[amount];
    }
}

解释

自上而下

有状态方程R[remain]=R[remain-coin]+1,R[x]表示的是子问题x元的最佳组合方案(硬币总个数最少)。
从递归的角度来看就是从上而下计算R[remain-coin1]、R[remain-coin2]……再从R[remain-coin1]一直到R[remain-coin i]取最小的作为R[remain]的值。

自下而上

状态方程同上R[remain]=R[remain-coin]+1。我们需要一个数组(矩阵)来存放已经计算过的情况。
数组需要初始化为大于amount的数,方便比较取min。数组的0下标应该初始化为0,表示成功的情况。
主体为两个循环,外循环表示自下而上,即从remain为1一直填数组至remain为amount的情况,内循环表示计算R[remain-coin1]、R[remain-coin2]……再从R[remain-coin1]一直到R[remain-coin i]取最小的作为R[remain]的值。

注意点

数组初始化

Java和C++很大的不同是,int[] a=new int[n]会自动把a的每个元素初始化为0,而C++为Null。

Arrays.sort(int[] nums)

静态方法对数组排序。

Arrays.fill(int[] nums,int x)

将数组所有元素填充为x。

Integer.MAX_VALUE

最大int整数2^ 31-1 , 即2147483647,加1为-2^ 31,即-2147483648。

ArrayList

List是一个接口,而ArrayList是一个类。ListArray继承并实现了List。

List list; //正确 list=null;
List list=new List(); // 是错误的用法
List list = new ArrayList();//这句创建了一个ArrayList的对象后把上溯到了List。此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,它就不能再用了。
ArrayList list=new ArrayList();//创建一对象则保留了ArrayList的所有属性。

这是一个例子:

import java.util.*;
public class TestList{
public static void main(String[] args){
List list = new ArrayList();//list 拥有List的所有属性和方法,不会拥有其实现类ArrayList的独有的属性和方法。
ArrayList arrayList = new ArrayList();
list.trimToSize(); //错误,没有该方法。
arrayList.trimToSize(); //ArrayList里有该方法。
}
}

编译一下就知道结果了。
需要注意的是,在继承中:
属性:不可被重写,会被隐藏
方法:会被重写,不会隐藏
List a=new ArrayList();
如果List与ArrayList中有相同的属性(如int i),有相同的方法(如void f()):则a.i是调用了List中的i,a.f()是调用了ArrayList中的f()。
定义时,泛型缺省则自动匹配。比如:

List<List<Integer>> res=new ArrayList<>();

这里ArrayList默认泛型为List< Integer >。即存放的数据类型为List< Integer >。可以再定义content为其存放的内容:

 List<Integer> content=new ArrayList<>();

ArrayList E.add()

目前只用过add。

https://www.jianshu.com/p/63b01b6379fb

这个上面的可以参考下。

你可能感兴趣的:(Leetcode,动态规划,java,leetcode,动态规划求解,贪心算法)