动态规划题目-合唱团-学习笔记

动态规划题目-合唱团

题目地址及我参考的解析

牛客网-[编程题]合唱团

题目

[编程题]合唱团

  • 热度指数:86848 时间限制:1秒 空间限制:32768K

有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?

输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。

示例1

输入

3
7 4 7
2 50

输出

49

我的思考过程

起初看题目与解析都非常费劲,@菜鸡华的解析很棒,但是我在看的时候还是有一些疑惑,通过走读他的源码与理解其他网友的解析与源码,我才弄明白这道题的解析过程。

总体解题思想梳理

我们首先定义one为“选中的同学中”的最后一名的位置(也就是第k名同学)。

我们用fmax[one][k]表示“先选上第one个同学,让其作为选中者的最后一名,加上这个同学总共需要选择k个同学”可获得的最大乘积,所以我们只需要求出每一名同学作为第one个可以获得的最大乘积之后,再比较“各人作为最后一名所获得的最大乘积”的最大值即可。

那么我们怎么求出所有同学的fmax[one][k]呢?

这里我们就需要对fmax[one][k]这个原问题进行分解了:

​ 我们首先定义left为“被选中的同学中,one同学前面那个同学”的位置(也就是第k-1名同学)。

​ 又因为不同同学的能力值有正有负,所以我们需要比较这两种情况下的最大值,如果第one名同学的能力值为正,那就需要乘最大的数才能得到最大值,如果能力值为负,那就需要乘最小的值才能获得最大值,比较得到的两个最大值,比较结果就是fmax[one][k]的值。

​ 根据以上分析则fmax[one][k] = max{fmax[left][k-1]*arr[one],fmin[left][k-1]*arr[one]}

​ 这里我们用fmin表示最小值,与fmax相对。

​ 这里还要说一下left的取值范围,left需要 >= max{one-d,k-1} && <= one-1,必须大于等于k-1是因为“如果<k了,就没办法选满k个人了”。

最后得到了fmax之后,我们遍历比较“选中k个人时,不同同学作为最后一名被选中者所得到的最大乘积”,得到其中的最大值就是结果了。

代码及注释

private static void mine() {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int arr[] = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = scanner.nextInt();
        }
        int K = scanner.nextInt();
        int D = scanner.nextInt();

        //这个地方用k:代表着我需要求出来选择小于等于k个人的所有情况
        //fmax[j][i]表示“先选上第j个同学,让其作为选中者的最后一名,总共需要选择i个”可获得的最大乘积
        long[][] fmax = new long[n + 1][K + 1];
        long[][] fmin = new long[n + 1][K + 1];

        //初始化选择“总共需要选择1个的情况”
        for (int i = 1; i <= n; i++) {
            fmax[i][1] = arr[i];
            fmin[i][1] = arr[i];
        }

        //从选择2个开始,一直到K个
        for (int i = 2; i <= K; i++) {
            /**
             * 我们至少应该从第i位同学开始计算其/fmax[j][i]的值(要不然最后不可能选够i位学生),
             * 这里我们令left为“被选中的同学们中,第j个同学前面那个同学的位置”,
             * fmax[j][i] = 从left第一个可能的值(max{j-d,k-1}<=left<=j-1)开始
             * 一直到j-1的位置,计算得到f[left][i-1]*arr[j]的值中最大的那个。
             */
            for (int j = i; j <= n; j++) {
                long tempmax = Long.MIN_VALUE;
                long tempmin = Long.MAX_VALUE;
                for (int left = Math.max(j - D, i - 1); left <= j - 1; left++) {
                    if (tempmax < Math.max(fmax[left][i - 1] * arr[j], fmin[left][i - 1] * arr[j])) {
                        tempmax = Math.max(fmax[left][i - 1] * arr[j], fmin[left][i - 1] * arr[j]);
                    }
                    if (tempmin > Math.min(fmax[left][i - 1] * arr[j], fmin[left][i - 1] * arr[j])) {
                        tempmin = Math.min(fmax[left][i - 1] * arr[j], fmin[left][i - 1] * arr[j]);
                    }
                }
                fmax[j][i] = tempmax;
                fmin[j][i] = tempmin;
            }
        }

        /**
         * 遍历选择k位同学的一列,比较得出以谁为最后一名获得的乘积最大
         */
        long result = Long.MIN_VALUE;
        for (int i = K; i <= n; i++) {
            if (result < fmax[i][K]) {
                result = fmax[i][K];
            }
        }
        System.out.println(result);
    }

通过解析的思路总结可以借鉴的地方用来做动态规划题目

我们经常做从n中选k,单纯的从n中选k很简单,但是这个题难在加上了d的约束与能力值的正负。
但我们可以从中借鉴一下解析中思考此类问题的方式:

  • 从n个人中顺序选择k个人,这k个人中一定会有个最后一名(ps:为什么不从第一名考虑呢?因为第一名不好递推),而且这个最后一名一定是在[n-k,n]中选择的,所以我们从这个角度入手,求出[n-k,n]范围的人他们作为最后一名出现,所得到的最大乘积,最后比较其中的最大值即可。
  • 那么我们怎么求出某人作为最后一名出现可以获得的最大乘积呢?
  • fmax[one][k] = max{fmax[left][k-1]*arr[one],fmin[left][k-1]*arr[one]}
  • 这个公式的左边使我们要求的,右边是向前递推并结合题目的约束得到的。

最后

我觉得用眼睛光看是没有办法理解明白的,最好自己举个例子,走读一下代码比较好。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值