无尽算法之 从简单例子 一步一步倒推 深入浅出动态规划算法的原理(呕心沥血版)

题目: 给定一个正整数s, 判断一个数组arr中,是否有一组数字加起来等于s。

思路:

普通递归法:
还是老套路, 每个数字都有两种可能, 选和不选, 递归搞定, 注意2个跳出条件, 其中arr[i]>s 这个条件可有可无, 只是为了避免不必要的计算, 但是注意, 这个条件在递归中无所谓, 在迭代的动态规划中, 如果去除此条件, 注意防止数组越界, 同时将该位置置为false.

动态规划法:
我们需要一个二维数组subset[][],
subset长度 = arr[]的长度,
subset[i]的长度= 我们需要0~S的长度也就是S+1

本例子中 S=9

在这里插入图片描述
如图我们可以看到, 我们利用subset构成了一个二维数组, 横坐标为S, 纵坐标为arr的索引值i (所以我们还需要利用 arr[i] 来找到真正的值), 我们需要反向来看,

假设计算进行到了最后一步, 以为如果进行到了最后一步还没出结果, 那么只有一种情况可以返回true, 就是arr[i]=S的剩余值, 也就是数组arr的最后一个数正好可以填上S的剩余值, 其他结果因为填不上, 所以全部为false.

我们继续前进到倒数第二步, 如果进行到了倒数第二步, 还是同理, 因为i=1, arr[i]=34, 34>S, 那么显然, 这步走跟没走一样, 还是要看最后一步的结果, 所以我们直接将倒数第二步的每一列填上最后一步的计算结果.

最终我们会得到这个二维数组, 从中我们可以看出, 0~S, 也就是0~9中, 我们除了2都能凑出来, 同时因为中间计算结果被保存了下来, 避免了重复计算相同子问题, 这就是动态规划的优势.

0001000000
1001000000
1001100100
1001100100
1001110111
1011111111
true
其实就是反向的推出我们这个数字能凑出0~S中的多少数, 并将其保存到中间结果中!

题解:

普通递归法:

 public static boolean rec_subset(int i, int s) {
        if (i == 0) return arr[i] == s;
        if (s == 0) return true;
        // 避免不必要的计算
        if (arr[i] > s) return rec_subset(i - 1, s);
        // 选i
        boolean a = rec_subset(i - 1, s - arr[i]);
        // 不选i
        boolean b = rec_subset(i - 1, s);
        return a || b;
    }

动态规划法:

 public static boolean dp_subset(int S) {
        int[][] subset = new int[arr.length][S+1];

        // 当s=0时, 为 true
        for (int i = 0; i < arr.length; i++) {
            subset[i][0]=1;
        }

        // 当找到数组最后一个数时, 如果arr[i]==s, 才为 true
        for (int i = 0; i < S; i++) {
            subset[0][i]=arr[0]==i?1:0;
        }

        // 开始将中间计算结果保存到subset中
        for (int i = 1; i < arr.length; i++) {
            for (int s = 1; s < S+1; s++) {
                // 如果arr[i]大于s 直接跳过, 等于上一个(i-1)的计算结果
                if (arr[i] > s) {
                    subset[i][s] = subset[i - 1][s];
                } else {
                    // 选i
                    int a = subset[i - 1][s - arr[i]];
                    // 不选i
                    int b = subset[i - 1][s];
                    // 将计算结果插入到subset中
                    subset[i][s] = a + b > 0 ? 1 : 0;
                }
            }
        }
        // 返回右下角的值
        return subset[subset.length-1][subset[0].length-1]==1;
        }

main()

static int[] arr = {3, 34, 4, 12, 5, 2};
    
    public static void main(String[] args) {
        System.out.println(rec_subset(arr.length - 1, 11));
        System.out.println(rec_subset(arr.length - 1, 12));
        System.out.println(rec_subset(arr.length - 1, 13));
        System.out.println(rec_subset(arr.length - 1, 197));
        System.out.println("--------------------------");
        System.out.println(dp_subset( 11));
        System.out.println(dp_subset( 12));
        System.out.println(dp_subset( 13));
        System.out.println(dp_subset( 197));

    }
发布了188 篇原创文章 · 获赞 323 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/104112314