【牛客网编程题——JAVA实现】《合唱团》算法解析(含思路解答示意图)

原创 2018年04月16日 21:11:08

考点:动态规划】动态规划变形,难度指数上升

题目描述

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

输入描述

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

输出描述

输出一行表示最大的乘积。


———————————————————————————————————————————————

以下解题思路整理来自牛客网讨论区

1. 题目分析

题目要求n各学生中选择k个,使这k个学生的能力值乘积最大。这是一个最优化的问题。另外,在优化过程中,提出了相邻两个学生的位置编号差不超过d的约束。

如果不用递归或者动态规划,问题很难入手,并且,限制条件d也需要对每一个进行约束,编程十分复杂

所以,解决的方法是采用动态规划(理由:1.求解的是最优化问题;2.可以分解为最优子结构)

2. 问题分解
  • 对该问题的分解是关键
    从n个学生中,选择k个,可以看成是:先从n个学生里选择最后1个,然后在剩下的里选择k-1个,并且让这1个和前k-1个满足约束条件

  • 数学描述
    为了能够编程实现,需要归纳出其递推公式,而在写递推公式之前,首先又需要对其进行数学描述

    记第k个人的位置为one,则可以用f[one][k]表示从n个人中选择k个的方案。然后,它的子问题,需要从one前面的left个人里面,选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].
    (1)学生能力数组记为arr[n+1],第i个学生的能力值为arr[i]
    (2)one表示最后一个人,其取值范围为[1,n];
    (3)left表示第k-1个人所处的位置,需要和第k个人的位置差不超过d,因此max{k-1,one-d}<=left<=one-1


    在n和k定了之后,需要求解出n个学生选择k个能力值乘积的最大值。因为能力值有正有负,所以有:

    如上图所示:
    当one对应的学生能力值为正时,
        f[one][k] = max{f[left][k-1] * arr[i]}        (min{k-1,one-d}<=left<=one-1);
    当one对应的学生能力值为负时
        f[one][k] = max{g[left][k-1] * arr[i]}       (min{k-1,one-d}<=left<=one-1);
    此处g[][]是存储n个选k个能力值乘积的最小值数组

3. 编程实现


    归纳以上思想,我们现在编程实现,可以先根据变量习惯进行变更,得到相应的递推公式。我们从curK = 1递推到curK = K

import java.util.*;

public class Main{
    
    public static void main(String[] args){
        
        //获取学生个数
        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();
        }
        //获取k值
        int k = scanner.nextInt();
        //获取d值
        int d = scanner.nextInt();
        //创建最大值和最小值两个辅助数组,long数组存放数值,范围更大
        long[][] f = new long[n + 1][k + 1]; 
        long[][] g = new long[n + 1][k + 1];
        //初始化两个数组,即K=1的情况
        for(int i = 1; i <= n; i++) {
            f[i][1] = arr[i];
            g[i][1] = arr[i];
        }
        //从k = 2开始填充(遍历每一行)
        for(int curK = 2; curK <= k; curK++) {
            for(int curN = curK; curN <= n; curN++) {  //遍历每一列
                long tempMax = Long.MIN_VALUE;         //临时最大、最小值变量
                long tempMin = Long.MAX_VALUE;
                //根据left的两个边界条件进行遍历
                for(int left = Math.max(curK - 1, curN - d); left <= curN - 1; left++){
                    //根据所得的递推公式更新最小值 最大值
                    if(tempMax < Math.max(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN])) {
                        tempMax = Math.max(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN]);
                    }
                    
                    if(tempMin > Math.min(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN])) {
                        tempMin = Math.min(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN]);
                    }
                }
                //更新最大值
                f[curN][curK] = tempMax;
                //更新最小值
                g[curN][curK] = tempMin;
            }
        }
        //确定了K值,要得到最大值,则遍历第K列,即搜索f[curN~n][K]  (curN >= k)
        long maxResult = Long.MIN_VALUE;
        for(int curN = k; curN <= n; curN++) {
            if(f[curN][k] > maxResult)
                maxResult = f[curN][k];
        }
        System.out.println(maxResult);
    }
}

思路补充

    这道题我一开始用传统个套路去做,一般的动态规划题目,中间使用的表的最后一个元素,dp[N][K]就是所求的结果。但这个题目不能这样,因为如果那样建表,子问题就成了“在前n个学生中,取k个,使乘积最大”——然而,本题目有额外的限制条件“相邻两个学生的位置编号的差不超过d”就没有办法代入递推公式了,因为子问题中本身并不包含位置信息。
    因此将子问题定为:在前n个学生中,取k个,第n个必取,使乘积最大。这样的话,最终的结果就不是dp[N][K],而是dp[..][K]这一列中最大的那个值。
    其次,求最大乘积比求最大和的问题要复杂许多。求最大和的话,子问题中也只需要求最大和就行了。但求最大乘积的时候,在子问题中,每一步需要求最大正积和最小负积。因为如果某学生的能力值为负数,乘以前面求得的最小负积,结果才是最大乘积。
    再次,这个问题最后算的数据比较大,已经不是int型能够包含的了,需要用long。

【牛客网】网易2017内推笔试编程题合集(一)

1、[编程题]合唱团 有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,...
  • xiaoquantouer
  • xiaoquantouer
  • 2017-01-01 18:26:01
  • 3468

合唱团——2016网易内推编程题

题目描述:(题目链接:合唱团) 有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的...
  • JIEJINQUANIL
  • JIEJINQUANIL
  • 2016-08-15 18:51:38
  • 2272

【算法题】合唱团

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

[编程题]创造新世界/牛客网/Python/解题报告+源代码

题目:https://www.nowcoder.com/questionTerminal/b8bc8459f0d34aaa8c1af1328cab2432 算是0-1背包问题的升级版本,用动态规划...
  • yyqhere
  • yyqhere
  • 2017-09-07 21:03:39
  • 222

牛客网编程题-滑动窗口的最大值(java)

思路:遍历数组,窗口右移,每移一次,替换下最大值,并将最大值添加到结果链表中 代码: import java.util.ArrayList; public class Solution {...
  • brainhang
  • brainhang
  • 2017-08-02 09:43:36
  • 131

牛客网-网易2017春招笔试真题编程题集合-解题思路及源码

一、双核处理 本题目是0-1背包的变种,题目的目标是求最少需要处理的时间。可以将目标转化为在 总任务长度/2的时间内,一个CPU最多能处理的任务量,那么答案为 总任务量-在总任务长度/2的时间内最多...
  • LieQueov
  • LieQueov
  • 2018-01-26 17:34:12
  • 178

合唱团(2016网易编程题)

题目: 有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最...
  • hui1140621618
  • hui1140621618
  • 2017-03-23 10:13:04
  • 1602

【牛客网】网易2017内推笔试编程题合集(二)

1、【*】[编程题] 混合颜料 你就是一个画家!你现在想绘制一幅画,但是你现在没有足够颜色的颜料。为了让问题简单,我们用正整数表示不同颜色的颜料。你知道这幅画需要的n种颜色的颜料,你现在可以去商店...
  • xiaoquantouer
  • xiaoquantouer
  • 2017-01-11 15:01:18
  • 1396

牛客网[编程题] 连续整数(Java实现)

牛牛的好朋友羊羊在纸上写了n+1个整数,羊羊接着抹除掉了一个整数,给牛牛猜他抹除掉的数字是什么。牛牛知道羊羊写的整数神排序之后是一串连续的正整数,牛牛现在要猜出所有可能是抹除掉的整数。例如: 10 ...
  • zjkC050818
  • zjkC050818
  • 2017-03-20 13:58:46
  • 828

牛客网在线编程题规范实例

牛客网华为笔试题通过案例,注意要引入包和输出: import java.util.Scanner; public class Main { public static int getIntF...
  • maoyeqiu
  • maoyeqiu
  • 2016-07-29 10:53:10
  • 8048
收藏助手
不良信息举报
您举报文章:【牛客网编程题——JAVA实现】《合唱团》算法解析(含思路解答示意图)
举报原因:
原因补充:

(最多只允许输入30个字)