给一个int []整数数组,[2,7,3,5,3]会有重复值,现在给aim=10,组成10最少用多少个硬币?
public static int coinsWay1(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
return process1(arr, 0, aim);
}
// 暴力递归
// arr[index...]组成rest这么多钱,最少的硬币数量返回。
// arr硬币都在里面
// index:自由选择arr[index...]
// rest:剩余到达成的目标
// 组成rest这么多钱,返回最少的硬币数
public static int process1(int[] arr, int index, int rest) {
if(rest < 0){
return -1; // 代表到最后怎么都组成都组成不了
}
if(rest == 0){
return 0; // 说明已经搞定了aim,需要0枚
}
// rest>0情况
// index == arr.length 没有硬币了,没有这样的方案
if(index == arr.length){
return -1;
}
// rest>0,并且也有硬币
// 不选择当前硬币,和选择当前硬币
// 不选择index硬币
int p1 = process1(arr, index + 1, rest);
// 选择当前硬币
int p2Next = process1(arr, index + 1, rest - arr[index]);
if(p1 == -1 && p2Next == -1){
return -1;
}else{
if(p1 == -1){ // 决策p1无效,返回p2 , +1是因为用了当前idnex硬币
return p2Next + 1;
}
if(p2Next == -1){ // 决策p2无效,返回p1
return p1;
}
return Math.min(p1, p2Next+1); // 都有效,决策选择出用的硬币最少的,无效解-1会干扰决策
}
// 不能这样,等于-1时会影响到决策
// return Math.min(process1(arr, index + 1, rest),
// 1 + process1(arr, index + 1, rest - arr[index]));
}
// 记忆化搜索,搞定可变参数组合
public static int coinsWay2(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
int [][] dp =new int [arr.length+1][aim+1];
for (int i = 0; i <= arr.length; i++) {
for (int j = 0; j <= aim; j++) {
dp[i][j] = -2; //表示没计算过
}
}
return process2(arr, 0, aim, dp);
}
// arr硬币都在里面
// index:自由选择arr[index...]这些硬币
// rest:剩余到达成的目标
// 组成rest这么多钱,返回最少的硬币数
public static int process2(int[] arr, int index, int rest,int[][] dp) {
// 认为中了无效缓存
if(rest<0){
return -1; // 直接放前面,也算一种缓存
}
if(dp[index][rest]!= -2){
return dp[index][rest];
}
if(rest==0){
dp[index][rest]=0;
}else if(index == arr.length){
// rest>0,没有硬币
dp[index][rest]=-1;
}else{
int p1 = process2(arr, index + 1, rest,dp);
int p2Next = process2(arr, index + 1, rest - arr[index],dp);
if(p1 == -1 && p2Next == -1){
dp[index][rest]=-1;
}else{
if(p1 == -1){ // 决策p1无效,返回p2
dp[index][rest]= p2Next + 1;
}else if(p2Next == -1){ // 决策p2无效,返回p1
dp[index][rest]= p1;
}else{
dp[index][rest]= Math.min(p1, p2Next+1); // 都有效,决策
}
}
}
return dp[index][rest];
}
// 动态规划
public static int coinsWay3(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
int N=arr.length;
int [][] dp =new int [N+1][aim+1];
// base case
for (int row = 0; row <= N; row++) {
dp[row][0]=0;
}
// base case
for (int col = 1; col <= aim; col++) {
dp[N][col] = -1;
}
// n行填好, 从n-1行开始,从左往右,从下向上
for (int index = N-1; index >=0; index--) {
// 0列填好,从开始
for (int rest = 1; rest <= aim; rest++) {
// 把递归代码黏贴过来改
// 递归过程变成了,动态规划格子取值过程
int p1 = dp[index+1][rest];
int p2Next = -1; // 越界-1 ,没越界赋值,注意边界
if(rest - arr[index]>=0){ // [rest - arr[index]]可能越界所以要这样写
p2Next = dp[index+1][rest - arr[index]];
}
if(p1 == -1 && p2Next == -1){
dp[index][rest] = -1;
}else{
if(p1 == -1){ // 决策p1无效,返回p2
dp[index+1][rest]= p2Next + 1;
}
if(p2Next == -1){ // 决策p2无效,返回p1
dp[index+1][rest]= p1;
}
dp[index+1][rest]= Math.min(p1, p2Next+1); // 都有效,决策 ,因为最后一行是-1还是有干扰
}
}
}
// 最终返回
return dp[0][aim];
}