算法30:arr是货币数组,其中的值都是正数。再给定一个正数aim。每个值都认为是一张货币,即便是值相同的货币也认为每一张都是不同的,返回组成aim的方法数-------从左往右尝试模型

题目:

arr是货币数组,其中的值都是正数。再给定一个正数aim。 每个值都认为是一张货币, 即便是值相同的货币也认为每一张都是不同的, 返回组成aim的方法数 例如:arr = {1,1,1},aim = 2 第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2 一共就3种方法,所以返回3

假设,这个一维数组为 【1,7,3,5】,目标值aim为4. 我们就可以以肉眼可见的速度知道,返回值只有1种,即 1+3.

那么该如何推导呢?

其实,所谓的从左到右尝试模型,口诀就是 针对固定集合:张数固定,值不相同,就是讨论要和不要的累加和。

怎么理解呢?

就是要1 和不要1 。要7 和不要7,依此类推.....

以下是我推导的全部过程,Y代表要,N代表不要。

所谓的累加和,就是把所有推导的结果进行相加。

递归版本:

//暴力递归
    public static int coinsWay(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }
        return process(arr, 0, aim);
    }


    public static int process (int[] arr, int index, int rest)
    {
        //rest小于0, 代表已经超过目标值了
        if (rest < 0)
        {
            return 0;
        }
        if (index == arr.length) { // 没钱了!
            return rest == 0 ? 1 : 0;
        } else {
            return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]);
        }
    }

既然有了递归版本,接下来就是如何修改成动态规划的代码了。

1. 动态规划,首先就是定义一个二维数组。通常情况下,我们以当前数组的行为行,以aim为列。

2. 然后构建二维数组表,然后根据递归版本构建出整张表的初始值。

rest 0rest 1rest 2rest 3rest 4
index 0
index 1
index 2
index 3
index 4

这里很多人会觉得奇怪,明明数组只有4个值,aim也只是4,为什么要构建5行5列的二维数组呢?

因为,我们在递归的时候有index == arr.length的判断,所有要多一行。从左到右尝试模型,一般是直接返回aim处的值,所以要多构建一列。这就是技巧,记住就好。

3. 根据递归推导数据:

    if (rest < 0) { return 0; } 代表列小于0,针对动态规划版本的二维数组,第一次进入是不存在的

    if (index == arr.length) {  return rest == 0 ? 1 : 0 } 这就是为什么要多建一行的原因。由此可以推导出,如果是最后一行第一列为1,其余的都为0.可得:

rest 0rest 1rest 2rest 3rest 4
index 0
index 1
index 2
index 3
index 410000

4. 根据 process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]); 可知。每一列都是按照此规则进行依赖的。下面进行逐行推导:

dp[3][0] = dp[4][0] + 0; 这个0是根据 rest - arr[index]得到的。如果rest - arr[index] 大于0,就返回dp[index+1][ rest - arr[index]的值。如果小于0, 折直接返回0。

下面的大于0,小于0,都是 rest - arr[index]的结果. rest为列的小标,arr[index]为一维数组的值,即对应行的下标

rest 0rest 1rest 2rest 3rest 4
index 0 (1)

index 1

(7)

index 2

(3)

index 3

(5)

0 - 5 < 0 得到0

dp[4][0] + 0:

1+ 0 = 1. 此列为1

1 - 5 < 0

得到0

0 + 0 = 0;

此列为 0

依次类推:

0 + 0 = 0

0 + 0 =00 + 0 = 0
index 410000

index2推导:

rest 0rest 1rest 2rest 3rest 4
index 0 (1)

index 1

(7)

index 2

(3)

10

0

此时rest = arr[2],都为3

所以此列为

dp[3][3] + dp[3][0]. 即 0 + 1= 1

0

index 3

(5)

0 - 5 < 0 得到0

dp[4][0] + 0:

1+ 0 = 1. 此列为1

1 - 5  < 0

得到0

0 + 0 = 0;

此列为 0

依次类推:

0 + 0 = 0

0 + 0 =00 + 0 = 0
index 410000

index1推导:

rest 0rest 1rest 2rest 3rest 4
index 0 (1)

index 1

(7)

10010

index 2

(3)

10

0

此时rest = arr[2],都为3

所以此列为

dp[3][3] + dp[3][0]. 即 0 + 1= 1

0

index 3

(5)

0 - 5 < 0 得到0

dp[4][0] + 0:

1+ 0 = 1. 此列为1

1 - 5  < 0

得到0

0 + 0 = 0;

此列为 0

依次类推:

0 + 0 = 0

0 + 0 =00 + 0 = 0
index 410000

index 0推导:

rest 0rest 1rest 2rest 3rest 4
index 0 (1)1

rest = arr[0], 都为1;

所以可得到

dp[1][1] + dp[1][0]; 即

0+1 = 1

dp[1][2] + dp[1][1]; 即

0+0 = 0

dp[1][3] + dp[1][1]; 即

1+0 =1

dp[1][4] + dp[1][3]; 即

0+1= 1

index 1

(7)

10010

index 2

(3)

10

0

此时rest = arr[2],都为3

所以此列为

dp[3][3] + dp[3][0]. 即 0 + 1= 1

0

index 3

(5)

0 - 5 < 0 得到0

dp[4][0] + 0:

1+ 0 = 1. 此列为1

1 - 5  < 0

得到0

0 + 0 = 0;

此列为 0

依次类推:

0 + 0 = 0

0 + 0 =00 + 0 = 0
index 410000

因此,最终的返回值 dp[0][4] 为1:

代码如下:

//动态规划
    public static int coinsWay2(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        if (aim == 0) {
            return 1;
        }

        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++) {
                dp[index][rest] = dp[index + 1][rest] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : 0);
            }
        }
        return dp[0][aim];

    }

完整代码,添加对数器进行海量数据测试:

package code03.动态规划_07.lesson4;

/**
 * arr是货币数组,其中的值都是正数。再给定一个正数aim。
 * 每个值都认为是一张货币,
 * 即便是值相同的货币也认为每一张都是不同的,
 * 返回组成aim的方法数
 * 例如:arr = {1,1,1},aim = 2
 * 第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2
 * 一共就3种方法,所以返回3
 */
public class ContainWaysEveryPaperDiff_04 {

    //暴力递归
    public static int coinsWay(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }
        return process(arr, 0, aim);
    }


    public static int process (int[] arr, int index, int rest)
    {
        //rest小于0, 代表已经超过目标值了
        if (rest < 0)
        {
            return 0;
        }
        if (index == arr.length) { // 没钱了!
            return rest == 0 ? 1 : 0;
        } else {
            return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]);
        }
    }


    //动态规划
    public static int coinsWay2(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        if (aim == 0) {
            return 1;
        }

        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++) {
                dp[index][rest] = dp[index + 1][rest] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : 0);
            }
        }
        return dp[0][aim];

    }




    // 为了测试
    public static int[] randomArray(int maxLen, int maxValue) {
        int N = (int) (Math.random() * maxLen);
        int[] arr = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = (int) (Math.random() * maxValue) + 1;
        }
        return arr;
    }

    // 为了测试
    public static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    // 为了测试
    public static void main(String[] args) {
        int maxLen = 20;
        int maxValue = 30;
        int testTime = 1000000;
        System.out.println("测试开始");
        int aim = (int) (Math.random() * maxValue);
        int[] a = randomArray(maxLen, maxValue);
        int a1 = coinsWay(a, aim);
        int a2 = coinsWay2(a, aim);
        System.out.println("a1:" + a1 + ", a2:" + a2);
        System.out.println("测试结束");
    }
}

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值