网易校招《合唱》动态规划解放,思路清晰,样例推导,注释细致,简单易懂。

目录

写在前面:

题目:

输入描述:

输出描述:

示例1

输入

输出

思路总结:

样例推导:

代码展示:


写在前面:

刷到一道算法题,是网易校招的原题,不甚懂,打算看博客瞧瞧大佬们的思路

但由于本人较为愚钝,大佬们的博客写得太简略,我结合了许多篇,花了很长时间才看懂,

如果对这道题,你也有同样的困惑,恭喜刷到了这一篇博客。

这篇博客,将以我小白的观点啰嗦地理一理这道《合唱》

题目:

小Q和牛博士合唱一首歌曲,这首歌曲由n个音调组成,每个音调由一个正整数表示。

对于每个音调要么由小Q演唱要么由牛博士演唱,对于一系列音调演唱的难度等于所有相邻音调变化幅度之和, 例如一个音调序列是8, 8, 13, 12, 那么它的难度等于|8 - 8| + |13 - 8| + |12 - 13| = 6(其中||表示绝对值)。

现在要对把这n个音调分配给小Q或牛博士,让他们演唱的难度之和最小,请你算算最小的难度和是多少。

如样例所示: 小Q选择演唱{5, 6}难度为1, 牛博士选择演唱{1, 2, 1}难度为2,难度之和为3,这一个是最小难度和的方案了。

输入描述:

输入包括两行,第一行一个正整数n(1 ≤ n ≤ 2000) 第二行n个整数v[i](1 ≤ v[i] ≤ 10^6), 表示每个音调。

输出描述:

输出一个整数,表示小Q和牛博士演唱最小的难度和是多少。

示例1

输入

5 1 5 6 2 1

输出

3

思路总结:

首先要明确一个点,题目的意思是将一串音符的序列抽取出来,分成AB两个组,某个人演奏A组,另一个人个人演奏B组

也就是说,这两个人演奏的都是原字符数组里的互补的字符序列。

现在定义一个dp二位数组,(我们定义i始终大于j,也就是说,i这张二维表我们只用到一小半)。

dp[i][j]的意义为:现在已经演奏到了第i个音符,并且快的人以i音符结尾,慢的人演奏完了j音符,此时的最小难度代价的值为dp[i][j]

结果:结果存储Σ(dp[n-1][i])的最小值中,意思为更快的人已经演奏完了最后一个音符,此时音乐结束,通过for循环找音乐结束后时难度最小值

对于dp[i][j]

  1. 当i与j相邻,即i=j+1
    1. 说明此时发生了演奏的交换,即上一个音符是由演奏者A演奏,下一个音符换成了演奏者B演奏
    2. 那么此时dp[i][j] = 对所有k求min(dp[i-1][k] + abs(arr[i] - arr[k]) ) 其中(0
  2. 当i与j不相邻,即i>j+1
    1. 说明某个演奏家已经至少连续演奏了两个音符,所以dp[i][j]=dp[i-1][j]+abs(arr[i]-arr[i-1])

定义一个acc数组作为中间变量,acc[i]意思是,截止到如今,累加的音符差值和

acc[i]的作用:可以做为dp[i-1][j-1]的初始值,意思为前面所有的音符都由演奏家A演奏,最后一个音符由演奏家B演奏时的难度和。但是acc[i-1]不一定是dp[i][i-1]的最小值。

样例推导:

代码展示:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main{
    public static void main(String[] args)throws IOException{
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(reader.readLine());
        int array[] = new int[n];
        
        if(n<3){
            System.out.println(0);
            return;
        }
        
        String[] str = reader.readLine().split(" ");
        for(int i=0;i<n;i++){
            array[i]=Integer.parseInt(str[i]);
        }
        
        //dp二位数组,dp[i][j]的意义为:现在已经演奏到了第i个音符,并且快的人以i音符结尾,慢的人演奏完了j音符,此时的最小难度代价的值为dp[i][j]
        int dp[][] = new int[n][n];
        //acc[i]意思是,截止到如今,累加的音符绝对值的和
        int acc[] = new int[n];
        
        //累加绝对值
        for(int i=1;i<n;i++){
            acc[i] = acc[i-1]+Math.abs(array[i]-array[i-1]);
        }
        
        for(int i=1;i<n;i++){
            //acc[i]的作用:可以做为dp[i-1][j-1]的初始值,意思为前面所有的音符都由演奏家A演奏,最后一个音符由演奏家B演奏时的难度和。
            //但是acc[i-1]不一定是dp[i][i-1]的最小值。
            dp[i][i-1]=acc[i-1];
            for(int j=0;j<i-1;j++){
                //当i与j不相邻,即i>j+1。说明某个演奏家已经至少连续演奏了两个音符
                dp[i][j]=dp[i-1][j] + Math.abs(array[i] - array[i-1]);
                //说明此时发生了演奏的交换,即上一个音符是由演奏者A演奏,下一个音符换成了演奏者B演奏
                //那么此时dp[i][j] = 对所有k求min(dp[i-1][k] + abs(arr[i] - arr[k]) ) 其中(0 <= k < i-1)
                dp[i][i-1] = Math.min(dp[i][i-1], dp[i-1][j] + Math.abs(array[i]-array[j]));
            }
        }
        
        int min = Integer.MAX_VALUE;
        //结果存储Σ(dp[n-1][i])的最小值中,意思为更快的人已经演奏完了最后一个音符,说明音乐结束,找音乐结束后时难度最小值
        //要注意j<n-1,一开始在定义时就要求i>j,dp[n-1][n-1]没有意义,因为不可能两个人同时演奏最后一个音符
        for(int j=0;j<n-1;j++){
            min=Math.min(min, dp[n-1][j]);
        }
        
        System.out.println(min);
        
        
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
合唱队形问题是一个经典的动态规划问题。假设有一支合唱队,由N个人组成每个人的身高不同。现在要求将这支合唱队分成两个部分,左边为递增子序列,右边为递减子序列,且两个部分的人数之和最大。 解决这个问题的关键是找到最长的递增子序列和最长的递减子序列。首先,可以使用动态规划来求解最长递增子序列的长度。定义一个数组dp1,其中dp1[i]表示以第i个人结尾的最长递增子序列的长度。初始时,将dp1数组中的每个元素初始化为1。 然后,从第二个人开始遍历到第N个人,对于每个人i,遍历其前面的所有人j(j从1到i-1),如果第j个人的身高小于第i个人的身高,并且dp1[j]+1大于dp1[i],则更新dp1[i]=dp1[j]+1。 接下来,可以使用类似的方法求解最长递减子序列的长度。定义一个数组dp2,其中dp2[i]表示以第i个人开头的最长递减子序列的长度。同样地,将dp2数组中的每个元素初始化为1。 从倒数第二个人开始遍历到第一个人,对于每个人i,遍历其后面的所有人j(j从i+1到N),如果第j个人的身高小于第i个人的身高,并且dp2[j]+1大于dp2[i],则更新dp2[i]=dp2[j]+1。 最后,遍历每个人,计算dp1[i]+dp2[i]-1的最大值,就是可以分成两个部分的合唱队形的最大人数。 这就是使用动态规划解决合唱队形问题的基本思路。希望能对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值