动态规划:合唱团问题解析(一)

牛客网网易的校招编程题

题目:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入:每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出:输出一行表示最大的乘积

因为本人刚学动态规划,所以我先把问题简化后先用递归方式求解,再改进为记忆化搜索,然后用动态规划解决问题,最后求解原问题。
简化后的问题:从 n 个自然数中选取 k 个数,使得这 k 个数的乘积最大。

递归求解

先尝试用递归的方式自上而下的解决,定义状态函数为 F(start, k),start 为自然数数组索引的起点,k 为要取的数的数量,返回从 start 到数组结束位置中取得的 k 个数的乘积的最大值。假设自然数组为 (X0, X1 ,X2, …, Xn-1) 共 n 个数,我们最终要求的就是 F(0, k)。假设我们选取其中一个数为必选的数,可以得出如下的递归树去解释该问题。

递归的终止条件为当 start >= n 的时候,这时 start 索引已经越界,所以直接返回数字 1 乘以被选取的数字,就相当于返回数组最后一个数字 Xn-1; 同时,当最后 k <= 0 的时候,说明这时无可选取的数字,也就是返回数字 1。基于以上条件,当存在选取超过一定范围内的 k 个数时,会返回范围内所有数字的乘积。实现的代码如下所示:

 1 long long recursive(int a[], int index, int n, int k) {
 2     if (k <= 0 || index >= n)
 3         return 1;
 4
 5     long long result = 0;
 6     for (int i=index; i < n; i++)
 7          result = max(result, a[i] * recursive(a, i+1, n, k-1));
 8     return result;
 9 }
10
11 long long result(int a[], int n, int k){
12     return recursive(a, 0, n, k);
13 }

记忆化搜索

因为递归在处理更大规模的数据时运行效率是很低的,存在大量的重复运算,所以我们可以用记忆化搜索的方式去优化递归的方法。因为每个状态依赖于两个变量的变化,所以需要一个二维的数组去存储已经计算过的值。实现的代码如下所示:

 1 vector<vector<long long>> memo;
 2 long long memoSearch(int a[], int index, int n, int k) {
 3     if (k <= 0 || index >= n)
 4         return 1;
 5     if (memo[index][k] != -1)
 6         return memo[index][k];
 7
 8     long long result = 0;
 9     for (int i = index; i < n; i++)
10         result = max(result, a[i] * memoSearch(a, i+1, n, k-1));
11         memo[index][k] = result;
12         return result;
13 }
14
15 long long result(int a[], int n, int k){
16     memo = vector<vector<long long>>(n, vector<long long>(k+1, -1));
17     return memoSearch(a, 0, n, k);
18 }

动态规划

通过上面递归的分析,我们知道该问题是要求一个最优的解,当自顶向下的分析问题时,我们发现该问题是存在最优子问题的,同时这些子问题可能被重复的计算,所以我们可以用动态规划的方法去自底向上的解决问题,提高计算效率。

我们从最基本的一个子问题 F(start, 1) 开始分析,F(start, 1)=max(Xstart*F(1, 0), Xstart+1*F(2, 0), …, Xn-1*F(n, 0))。因为假设 k=0 时返回数字 1,所以可得 F(start, 1)=max(Xstart, Xstart+1, …, Xn-1)。所以当 k = 1 时,我们保留原来所有数组的值,而当 k = 2 时,从头遍历数组,在位置 (start, 2) 上存储 Xstart*F(start+1, 1)。所以在编程实现时需要三个 for 循环,第一重循环以 k 计数,第二重以自然数组下标 n 计数,第三重循环取该下标 n 后被存储的数,循环内计算该下标的自然数与存储的数的最大值的积。当计算完最后一列 k 时,最后一列 k 中的最大值就是我们要求的问题的最优解。实现的代码如下所示:

 1 vector<vector<long long>> memo;
 2 long long dpAlgorithm(int a[], int n, int k, int d) {
 3     memo = vector<vector<long long>>(n, vector<long long>(k+1, -1));
 4     long long result = 0;
 5     for (int j = 1; j < k+1; j++) {
 6         for (int i = 0; i < n; i++) {
 7             if (j == 1) {
 8                 memo[i][j] = a[i];
 9                 continue;
10             }
11             long long temp = 0;
12             for (int index = i + 1; index < n; index++) {
13                 temp = max(temp, memo[index][j - 1]);
14                 memo[i][j] = temp * a[i];
15             }
16             if (j == k) result = max(result, memo[i][j]);
17         }
18     }
19     return result;
20 }

动规算法优化

要实现前面所述的动态规划算法,我们需要一个 n * (k + 1) 的二维矩阵去存储已经计算出的最优值,当 n 和 k 的值很大的时候,就需要更多额外的空间去求解。而实际上我们可以进一步的优化这个空间复杂度。因为在这个问题中,当我们选取一个固定的值时,我们是从其之后存储的最大值与该固定的值相乘,所以之前被存储的值事实上是可以被覆盖的。所以我们只需要一个一维的长度为n的矩阵去实现该算法。实现的代码如下所示:

 1 long long dpAlgorithm2(int a[], int n, int k, int d) {
 2     vector<long long> memo2 = vector<long long>(n, -1);
 3     long long result = 0;
 4     for (int j = 1; j < k + 1; j++) {
 5         for (int i = 0; i < n; i++) {
 6             if (j == 1) {
 7                 memo2[i] = a[i];
 8                 continue;
 9             }
10             for (int index = i + 1; index < n; index++) {
11                 memo2[i] = max(memo2[i], a[i] * memo2[index]);
12             }
13             if (j == k) result = max(result, memo2[i]);
14         }
15     }
16     return result;
17 }

原文地址:https://www.cnblogs.com/ToBeDeveloper-Zhen/p/8519664.html

时间: 2024-11-13 03:39:34

动态规划:合唱团问题解析(一)的相关文章

动态规划:合唱团问题解析(二)

牛客网网易的校招编程题 题目:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?输入:每个输入包含 1 个测试用例.每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50).接下来的一行包含两个整数,k 和 d

动态规划的详细解析(01背包问题)

                                  算法分析之动态规划详解 先举个例子01背包问题具体例子:假设现有容量15kg的背包,另外有4个物品,分别为a1,a2,a3, a4.物品a1重量为3kg,价值为4:物品a2重量为4kg,价值为5:物品a3重量为5kg,价值为6, a4重6千克,价值为7.将哪些物品放入背包可使得背包中的总价值最大? 对于这样的问题,如果如上述所涉及的数据比较少的时候,我们通过列举就能算出来,例如,上边的例子的答案是:将a4和a3与a2放入背包中,

动态规划23题解析

最近两周做了动态规划的23道经典好题,涉及到区间.树形.数位等三种动态规划类型,现在将这23道题的题解写在下面,方便大家借鉴以及我加深记忆. upd at:20190814 20:46.T7二叉苹果树 1.石子合并 经典的区间DP问题,枚举合并的堆数作为阶段,设f[i][j]表示i->j这段区间内的最优方案,考虑在这段区间内枚举断点k,不难得到f[i][j]=min(f[i][k]+f[k+1][j]+sum(i,j))(最大值同理).破环为链后直接进行DP即可. #include<iostr

编辑距离和编辑距离的动态规划算法(Java代码)

编辑距离概念描述: 编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数.许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符. 例如将kitten一字转成sitting: sitten (k→s) sittin (e→i) sitting (→g) 俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念. 编辑距离的应用在信息检索.拼写纠错.机器翻译.命名实体抽取.同义词寻找等问题中有较多的应用 问题:找出

【动态规划】合唱团

问题 : [动态规划]合唱团 时间限制: 1 Sec  内存限制: 64 MB提交: 31  解决: 9[提交][状态][讨论版] 题目描述 N位同学站成一排,墨老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形.合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,  则他们的身高满足T1<T2<…<Ti>Ti+1>…>TK(1≤i≤K). 你的任务是,已知所有N位同学的身高,计算最少需要几位同学

网易笔试题之合唱团---动态规划

动态规划学习 [编程题]合唱团 有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗? 输入描述: 每个输入包含 1 个测试用例.每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50).接下来的一行包含两个整数,k

动态规划:背包问题

例题:装箱问题 ( http://www.wikioi.com/problem/1014/  ) 题目描述 有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数). 要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小. 输入描述 一个整数v,表示箱子容量,一个整数n,表示有n个物品 接下来n个整数,分别表示这n 个物品的各自体积 输出描述 一个整数,表示箱子剩余空间. 样例输入 24 6 8 3 12 7 9 7

HNOI 2004 打砖块 动态规划

题意: 在一个凹槽中放置了n层砖块,最上面的一层有n块砖,第二层有n-1块,--,最下面一层仅有一块砖.第i层的砖块从左至右编号为1,2,--,i,第i层的第j块砖有一个价值a[i,j](a[i,j]<=50).下面是一个有5层砖块的例子: 如果要敲掉第i层的第j块砖的话,若i=1,可以直接敲掉它,若i>1,则必须先敲掉第i-1层的第j和第j+1块砖. 你的任务是从一个有n(n<=50)层的砖块堆中,敲掉(m<=500)块砖,使得被敲掉的这些砖块的价值总和最大. 方法:动态规划 解

HDU-1231-最大连续子序列(Java+DP动态规划)

最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 21101    Accepted Submission(s): 9361 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j