算法学习动态规划-5

前言

继续做几道动态规划的题目,从中继续体会动态规划。最后一道n皇后的问题无法使用动态规划。

一、累加和最小(不限集合个数)

题目

 * 给定一个正数数组arr,
 * 请把arr中所有的数分成两个集合,尽量让两个集合的累加和接近
 * 返回:
 * 最接近的情况下,较小集合的累加和

分析

先算出总累加和,分成两个集合后,我们只要转化成新组成集合的累加和最接近总累加和的一半的问题

递归代码

 public static int right(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }

        //求出总累加和
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }

        return process(arr, 0, sum / 2);
    }

    /**
     * 返回:arr[index...]从index位置及其往后,最接近rest的累加和
     *
     * @param arr
     * @param index 数组的下标
     * @param rest  剩余要组成最接近的数
     * @return
     */
    public static int process(int[] arr, int index, int rest) {
        //base case
        if (index == arr.length) {
            return 0;
        }

        //普遍情况,从左往右模型,要和不要
        //index位置不要
        int p1 = process(arr, index + 1, rest);

        //index 位置要
        int p2 = 0;
        if (arr[index] <= rest) {
            p2 = arr[index] + process(arr, index + 1, rest - arr[index]);
        }

        return Math.max(p1, p2);
    }

举个例子,当arr[5,2,3,.....] 总累加和为10
第一种递归的选择:只选择下标0的位置5,然后下标12不选择,此时就是要求arr下标从3开始最接近5的选择
第二张递归的选择:下标0不选择,然后下标12选择,此时就是求arr下标从3开始最接近5的选择
通过以上分析可以知道存在重复解,需要改动态规划

动态规划版本

    public static int dp(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }

        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }

        sum /= 2;
        int N = arr.length;
        int[][] dp = new int[N + 1][sum + 1];

        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= sum; rest++) {
                //index位置不要
                int p1 = dp[index + 1][rest];

                //index 位置要
                int p2 = 0;
                if (arr[index] <= rest) {
                    p2 = arr[index] + dp[index + 1][rest - arr[index]];
                }

                dp[index][rest] = Math.max(p1, p2);
            }
        }

        return dp[0][sum];
    }

二、累加和最小(限制集合个数)

题目:

 * 给定一个正数数组arr,请把arr中所有的数分成两个集合
 * 如果arr长度为偶数,两个集合包含数的个数要一样多
 * 如果arr长度为奇数,两个集合包含数的个数必须只差一个
 * 请尽量让两个集合的累加和接近
 * 返回:
 * 最接近的情况下,较小集合的累加和

分析:

基于上一道题目,在递归入参增加集合个数限制即可

递归代码:

public static int right(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }

        if (arr.length % 2 == 0) {
            //偶数情况
            return process(arr, 0, arr.length / 2, sum / 2);
        } else {
            //奇数情况
            return Math.max(process(arr, 0, arr.length / 2, sum / 2), process(arr, 0, arr.length / 2 + 1, sum / 2));
        }


    }


    /**
     * 返回arr[index...]从index位置及其往后,满足累加picks个数情况下,最接近rest目标的累加和
     *
     * @param arr
     * @param index 数组下标
     * @param picks 还剩下多少个数需要选择
     * @param rest  距离目标累加和
     * @return
     */
    public static int process(int[] arr, int index, int picks, int rest) {
        //base case
        if (index == arr.length) {
            return picks == 0 ? 0 : -1;
        }

        //普遍情况
        //index位置不要
        int p1 = process(arr, index + 1, picks, rest);

        //index位置要
        int p2 = -1;
        if (arr[index] <= rest && picks > 0) {
            int next = process(arr, index + 1, picks - 1, rest - arr[index]);
            if (next != -1) {
                p2 = arr[index] + next;
            }
        }

        return Math.max(p1, p2);
    }

动态规划代码

    public static int dp(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        int N = arr.length;
        int M = (N + 1) / 2;
        sum /= 2;

        int[][][] dp = new int[N + 1][M + 1][sum + 1];

        //默认都是-1
        for (int index = 0; index <= N; index++) {
            for (int picks = 0; picks <= M; picks++) {
                for (int rest = 0; rest <= sum; rest++) {
                    dp[index][picks][rest] = -1;
                }
            }
        }

        //base case
        for (int rest = 0; rest <= sum; rest++) {
            dp[N][0][rest] = 0;
        }

        for (int index = N - 1; index >= 0; index--) {
            for (int picks = 0; picks <= M; picks++) {
                for (int rest = 0; rest <= sum; rest++) {
                    //index位置不要
                    int p1 = dp[index + 1][picks][rest];

                    //index位置要
                    int p2 = -1;
                    if (arr[index] <= rest && picks > 0) {
                        int next = dp[index + 1][picks - 1][rest - arr[index]];
                        if (next != -1) {
                            p2 = arr[index] + next;
                        }
                    }

                    dp[index][picks][rest] = Math.max(p1, p2);
                }
            }
        }

        if (N % 2 == 0) {
            //偶数情况
            return dp[0][N / 2][sum];
        } else {
            //奇数情况
            return Math.max(dp[0][N / 2][sum], dp[0][N / 2 + 1][sum]);
        }

    }

三、n皇后

题目:

 * N皇后问题是指在N*N的棋盘上要摆N个皇后,
 * 要求任何两个皇后不同行、不同列, 也不在同一条斜线上
 * 给定一个整数n,返回n皇后的摆法有多少种。n=1,返回1
 * n=232皇后和3皇后问题无论怎么摆都不行,返回0
 * n=8,返回92

分析:

每行每列都去尝试,同时需要保证不同列和左右斜对角线

递归版本:

 public static int right(int n) {
        if (n <= 0) {
            return 0;
        }

        int[] record = new int[n];

        return process1(0, record, n);
    }

    /**
     * 返回摆放n个皇后共有多少种方法数
     *
     * @param i      第几行
     * @param record 记录每行皇后摆放位置
     * @param n      摆放几个皇后
     * @return
     */
    public static int process1(int i, int[] record, int n) {
        //base case
        if (i == n) {
            return 1;
        }

        int ways = 0;
        //递归求每一列皇后放置的情况
        for (int j = 0; j < n; j++) {
            //剪枝
            if (isValid(record, i, j)) {
                record[i] = j;
                ways += process1(i + 1, record, n);
            }
        }

        return ways;
    }

    /**
     * 返回在0...i行的皇后摆放是否合法
     * 不能同列
     * 不能左右斜线
     *
     * @param record 记录数
     * @param i      行
     * @param j      列
     * @return
     */
    public static boolean isValid(int[] record, int i, int j) {
        for (int k = 0; k < i; k++) {
            if (j == record[k] || Math.abs(j - record[k]) == Math.abs(i - k)) {
                return false;
            }
        }

        return true;
    }

因为不存在重复解,所以就不需要改动态规划

总结

不是所有的递归都需要改成动态规划,只有当递归中存在重复解的时候才有必要去改写动态规划

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值