package com.butupi.violence2dp;
/**
* 一个正整数数组,代表一组货币面值,货币可以随意拿去多张,问有多少中方式 ,使 总值 达到 aim目标值?
*
* @description: 所有的面值,每一个面值都可以任意选择张数,组成正好rest这么多钱,方法数多少?
* @author:1252319301
* @version:
* @date: 2022年5月8日
*/
public class CoinsWay {
// ----------------------------------violencereCursive暴力递归-----------------------------------------
public static int coinsWay(int[] arr, int aim) {
// Check parameter
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
// process
return process(arr, 0, aim);
}
// arr[index....] 所有的面值,每一个面值都可以任意选择张数,组成正好rest这么多钱,方法数多少?
/**
* @description: 处理方法
* @param arr 货币面值数组
* @param index 选择货币位置
* @param rest 剩余钱数
* @return 方法多少种 自变量: index、rest 因变量: ways 由此改进
*/
public static int process(int[] arr, int index, int rest) {
// BASE CASE :可要可不要,case2 的for循环不满足,退出时,由于for的判断条件,不会遍历到最后,return的是0
if (rest < 0)
return 0;
// CASE 1: no money can choose
if (index == arr.length) { // 没钱了
// 是否剩余的为零,为零返回1
return rest == 0 ? 1 : 0;
}
// CASE 2: choose free 自由选择货币
int ways = 0;
for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
// 之后的 index+1 的方法种类
ways += process(arr, index + 1, rest - (zhang * arr[index]));
}
// RETURN ANS
return ways;
}
// ----------------------------------dp1改进一:记忆化搜索-----------------------------------------
/*
* 记忆化搜索: 在递归的时候查缓存表, 如果命中,拿数据, 没有命中,递归得到结果,并存入缓存表中
*/
public static int coinsWay2(int[] arr, int aim) {
// Check parameter
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
// BUILD dpMaps
int[][] dp = new int[arr.length + 1][aim + 1];
for (int i = 0; i < dp.length; i++)
for (int j = 0; j < dp[0].length; j++)
dp[i][j] = -1;
// process
return process2(arr, 0, aim, dp);
}
// arr[index....] 所有的面值,每一个面值都可以任意选择张数,组成正好rest这么多钱,方法数多少?
/**
* @description: 处理方法
* @param arr 货币面值数组
* @param index 选择货币位置
* @param rest 剩余钱数
* @return 方法多少种 自变量: index、rest 因变量: ways 由此改进
*/
public static int process2(int[] arr, int index, int rest, int[][] dp) {
// CACHE
if (dp[index][rest] != -1) {
return dp[index][rest];
}
// CASE 1: no money can choose
if (index == arr.length) { // 没钱了
// 是否剩余的为零,为零返回1
// return rest == 0 ? 1 : 0;
dp[index][rest] = rest == 0 ? 1 : 0;
return dp[index][rest];
}
// CASE 2: choose free 自由选择货币
int ways = 0; // dp初始化的时候是-1,这里不要用dp来累加,不然会少一个解法
for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
// 之后的 index+1 的方法种类
ways += process(arr, index + 1, rest - (zhang * arr[index]));
// dp[index][rest] += dp[index+1][rest-(zhang*arr[index])];
}
// RETURN ANS
return dp[index][rest] = ways;
}
// ----------------------------------dp1改进二:动态规划-----------------------------------------
/*
* 自变量: index、rest 0 <= index <= arr.length 0 <= rest <= aim 因变量: ways
*/
public static int dp1(int[] arr, int aim) {
// CHECK parameter
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
// BUILD dpMap
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
// CASE 1: no money can choose ,//if (index == arr.length) { return rest == 0 ?
// 1 : 0;
dp[N][0] = 1;
// CASE 2: choose free
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= aim; rest++) {
int ways = 0;
for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
ways += dp[index + 1][rest - (zhang * arr[index])];
}
dp[index][rest] = ways;
}
}
// RETURN ANS
return dp[0][aim];
}
// ----------------------------------dp2改进三:动态规划-----------------------------------------
/*
* 优化枚举:
*
* for (int zhang = 0; zhang * arr[index] <= rest; zhang++) ways += dp[index
* +1][rest - (zhang * arr[index])]; 每一项 要 进行所有相关项累加 优化 为 与上一项的和
*/
public static int dp2(int[] arr, int aim) {
// CHECK parameter
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
// BUILD dpMap
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= aim; rest++) {
// optimize 优化 (枚举优化,累加和)
dp[index][rest] = dp[index + 1][rest];
if (rest - arr[index] >= 0) {
dp[index][rest] += dp[index][rest - arr[index]];
}
}
}
// RETURN ANS
return dp[0][aim];
}
// ----------------------------------for-test-----------------------------------------
/**
* @description: 生成不重复随机数组 ?
* @param maxLen 最大长度
* @param maxValue 最大值
* @return 随机数组
*/
public static int[] randomArray(int maxLen, int maxValue) {
int N = (int) (Math.random() * maxLen);
int[] arr = new int[N];
// 不重复, 是否存在数组[在生成的 数字的 boolean数组上 true为已经生成
boolean[] has = new boolean[maxValue + 1];
for (int i = 0; i < N; i++) {
do {
arr[i] = (int) (Math.random() * maxValue) + 1;
} while (has[arr[i]]);
has[arr[i]] = true;
}
return arr;
}
/**
* @description: 打印数组
* @param arr 数组
*/
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// ----------------------------------main-----------------------------------------
public static void main(String[] args) {
int maxLen = 10;
int maxValue = 30;
// 测试次数
int testTime = 1000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int[] arr = randomArray(maxLen, maxValue);
int aim = (int) (Math.random() * maxValue * 2);
int ans1 = coinsWay(arr, aim);
int ans2 = coinsWay2(arr, aim);
int ans3 = dp1(arr, aim);
int ans4 = dp2(arr, aim);
if (ans1 != ans2 || ans1 != ans3 || ans2 != ans4) {
System.out.println("error!");
printArray(arr);
System.out.println(aim);
System.out.println(ans1);
System.out.println(ans2);
System.out.println(ans3);
System.out.println(ans4);
break;
}
}
System.out.println("测试结束");
int[] arr1 = { 5, 10, 50, 100 };
int sum = 1000;
System.out.println(coinsWay(arr1, sum));
System.out.println(coinsWay2(arr1, sum));
System.out.println(dp1(arr1, sum));
int[] arr2 = randomArray(3, maxValue);
printArray(arr2);
}
}
算法-从暴力递归到动态规划
于 2022-05-08 17:46:45 首次发布