1. 程式人生 > 實用技巧 >【原創】【演算法】合唱團

【原創】【演算法】合唱團

本人小菜鳥,望各位大神多指點~

演算法來源於牛客網: https://www.nowcoder.com/practice/661c49118ca241909add3a11c96408c8?tpId=182&&tqId=34280&rp=1&ru=/ta/exam-all&qru=/ta/exam-all/question-ranking

題目描述

有 n 個學生站成一排,每個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎?

輸入描述

每個輸入包含 1 個測試用例。每個測試資料的第一行包含一個整數 n (1 <= n <= 50),表示學生的個數,接下來的一行,包含 n 個整數,按順序表示每個學生的能力值 ai

(-50 <= ai <= 50)。接下來的一行包含兩個整數,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

輸入描述

輸出一行表示最大的乘積。

示例

輸入

3
7 4 7
2 50

輸出

49

本題知識點分享

這題是比較難的題目,主要使用動態規劃演算法,動態規劃演算法之後會有一個隨筆會展示一個簡單動態規劃的流程。主要思想就是區域性最優求解全域性最優。

輸入變數

n:輸入的n名學生

a[]:長度為n,表示每名學生的能力值(注意:存在負數)

k:需要選取k名學生

d:選取的相鄰學生之間的下標不能差值不能超過d,比如ai,aj兩個學生是選取學生中相鄰的兩個,則abs(i
-j)<=d
自定義變數

dpMin:k*n的二維陣列。第k次選取學生n的能力乘積的最小值(考慮負數的情況,因為存在負負得正)
    其中:dpMin[kk][nn]表示當a[nn]作為第kk個學生時,儲存能力乘積最小值
       dpMin需要宣告為long型別,因為升級會超過Int的最大值
dpMax:k
*n的二維陣列。第k次選取學生n的能力乘積的最大值。
    其中:dpMax[kk][nn]表示當a[nn]作為第kk個學生時,儲存能力乘積最大值
       dpMax需要宣告為long型別,因為升級會超過Int的最大值
maxAbilit:最大能力值。作為輸出的臨時變數。

解題思路

Step1:初始化第一個目標。將a[i]作為第1次尋找的目標,則最優路徑就為其本身

for(int i=0;i<n;i++){//初始化第1個目標為其本身
    dpMax[0][i] = a[i];
    dpMin[0][i] = a[i];
}

Step2:遍歷尋找第k個目標

1.第一層迴圈:

  ①作用:需要尋找第k個目標。

  ②因為第一個目標在之前初始化的時候選好了,所以這裡kk的初始值為1而不是0

for(int kk=1;kk<k;kk++)//尋找第K個目標

2.第二層迴圈:

  ①作用:當將第nn個學生作為第k個目標時,執行下一個迴圈,找到將該目標加入的能力乘積最大值

  for(int nn=0;nn<n;nn++)//如果將第nn個學生作為新目標學生加入到第K個目標,分別計算能力乘積的值

3.第三層迴圈:

  ①作用:向後尋找d個目標,在這些目標中尋找能力乘積最大值/最小值存入dpMax

  ②使用Math.min(nn+d+1,n) 是考慮越界的情況

    for(int dd=nn+1;dd<Math.min(nn+d+1,n);dd++)

Step3:根據記錄的dpMax[k-1][]中(最後一次選舉的結果)中尋找最大解

//列印輸出
long maxAbility = Long.MIN_VALUE;
for(int i=0;i<n;i++){
    if(maxAbility<Math.max(dpMax[k-1][i],dpMin[k-1][i])){
        maxAbility = Math.max(dpMax[k-1][i],dpMin[k-1][i]);
    }
}
System.out.println(maxAbility);    


例項分析1(能力值全為正數的情況)

先舉一個學生能力值全為正數的情況,便於理解,此時只需要考慮dpMax這個陣列的資料

輸入
4
7 4 8 6
3 50
輸出
336

可以很直觀的看出結果為7*8*6=336

k=0

第0次選舉

7 4 8 6

k=1

第1次選舉
7 4 8 6
7*8=56 4*8=32 8*6=48 0

k=2

第2次選舉
7 4 8 6
56 32 48 0
7*48=336 4*48=192 0 0

所以結果為最後一次選擇學生後結果的最大值336

程式碼展示

 1 import java.util.Scanner;
 2 
 3 public class Main20200809_3 {
 4     public static void main(String args[]){
 5         Scanner in = new Scanner(System.in);
 6         while (in.hasNext()){
 7             int n = in.nextInt();//共n個學生
 8             int a[] = new int[n];//能力值,-50~50
 9             for(int i=0;i<n;i++){
10                 a[i] = in.nextInt();
11             }
12             int k = in.nextInt();//選取k名學生
13             int d = in.nextInt();//兩個學生位置編號不能超過d
14             //動態規劃計算最佳路徑
15             long dpMin[][] = new long[k][n];//第k次選取學生n的能力乘積的最小值(考慮負數的情況,因為存在負負得正)
16             long dpMax[][] = new long[k][n];//第k次選取學生n的能力乘積的最大值
17             for(int i=0;i<n;i++){//初始化第1個目標為其本身
18                 dpMax[0][i] = a[i];
19                 dpMin[0][i] = a[i];
20             }
21             for(int kk=1;kk<k;kk++){//尋找第K個目標
22                 for(int nn=0;nn<n;nn++){//如果將第nn個學生作為新目標學生加入到第K個目標,分別計算能力乘積的值
23                     for(int dd=nn+1;dd<Math.min(nn+d+1,n);dd++){
24                         dpMax[kk][nn] = Math.max(dpMax[kk][nn],Math.max(dpMin[kk-1][dd]*a[nn],dpMax[kk-1][dd]*a[nn]));
25                         dpMin[kk][nn] = Math.min(dpMin[kk][nn],Math.min(dpMin[kk-1][dd]*a[nn],dpMax[kk-1][dd]*a[nn]));
26                     }
27                 }
28             }
29             //列印輸出
30             long maxAbility = Long.MIN_VALUE;
31             for(int i=0;i<n;i++){
32                 if(maxAbility<Math.max(dpMax[k-1][i],dpMin[k-1][i])){
33                     maxAbility = Math.max(dpMax[k-1][i],dpMin[k-1][i]);
34                 }
35             }
36             System.out.println(maxAbility);
37         }
38     }
39 }