动态规划_实战2

网易2017年合唱团

做题经验:
对于一个复杂的题,若是附加条件太多,不能一步到位的解决,可以逐渐的减少附加条件,也就是逐渐的减少参数,等有思路了,再把参数添加进去。

对于这题,作者就是这么想的,一开始,把附加条件d去掉,就是一个很典型的DP问题,然后建模, 递归实现,DP算法实现,最后再把d添加进去。

一、递归实现

这里需要提醒的是,因为能力值有正有负,在下一个值不知道正负性的情况下,是不能确定应该返回哪一个值的,很可能这一次的最小值乘以负数就变成最大值了。
因此不是每次返回一个值,而是每次返回两个值。一个最大值,一个最小值。

import sys

n =  36
ability = [7,-15,31,49,-44,35,44,-47,-23,15,-11,10,-21,10,-13,0,-20,-36,22,-13,-39,-39,-31,-13,-27,-43,-6,40,5,-47,35,-8,24,-31,-24,-1]
k,d = 2, 31

def chorus(n, ability, k):
    if k == 1:
        return [max(ability),min(ability)]
    if k == n:
        result = 1
        for i in range(n):
            result = result * ability[i]
        return [result,result]

    #选中最后一个同学,会得到最大值与最小值
    last_list = chorus(n-1,ability[0:n-1],k-1)
    #未选中最后一个同学,得到最大值与最小值
    no_last_list = chorus(n-1,ability[0:n-1],k)
    #不能认为,index为0的就是max,因为ability[-1]的不确定性,所以可能会不断改变
    #因此需要再每一步都判断一下,得到最大或最小值
    return [max(last_list[0]*ability[-1],
                last_list[1]*ability[-1],
                max(no_last_list)),
            min(last_list[0] * ability[-1],
                last_list[1] * ability[-1],
                min(no_last_list))]

print(chorus(n, ability, k))

二、DP实现(不加d)

这里因为只有两个参数,n和k,分析状态转移方程,可以知道,k一定时,f[k][n_id]的每个状态都可以由k-1的每个状态决定,因此这里只需要维护两个一维数组即可。

代码实现:


n =  36
ability = [7,-15,31,49,-44,35,44,-47,-23,15,-11,10,-21,10,-13,0,-20,-36,22,-13,-39,-39,-31,-13,-27,-43,-6,40,5,-47,35,-8,24,-31,-24,-1]
k,d = 3, 31


def chorus(n, ability, k):
    pre_res_max = []
    pre_res_min = []
    if k == 1:
        return max(ability)
    else:
        for i in range(n):
            pre_res_max.append(max(ability[0:i+1]))
            pre_res_min.append(min(ability[0:i+1]))


    for k_id in range(2,k+1):
        after_res_max = []
        after_res_min = []
        for n_id in range(n):
            if k_id > n_id:
                res = 1
                for i in range(n_id+1):
                    res = res * ability[i]
                after_res_max.append(res)
                after_res_min.append(res)
            else:
                #对于这一点尤其要注意,用哪个元素,千万不能错了。
                #这里要是用n_id,就是49*49了,元素就重复了。
                max1 = pre_res_max[n_id-1]*ability[n_id]
                min1 = pre_res_min[n_id-1] * ability[n_id]
                max2 = after_res_max[n_id-1]
                min2 = after_res_min[n_id-1]
                result_list = [max1,max2,min1,min2]
                after_res_max.append(max(result_list))
                after_res_min.append(min(result_list))

        pre_res_max = after_res_max
        pre_res_min = after_res_min


    return max(pre_res_max)

print(chorus(n, ability, k))

三、DP实现(加d)

因为这里有三个参数n,k,d,需要好好定义一下需要保存的数组。

f[k][i]代表的是,选择k个学生并且以第i个学生作为结尾时的最大(或最小)乘积。

如何定义需要保存的数组,也是一门玄学?

基本上,是根据动态转移方程中的参数的值,来确定需要保存的东西。
比如国王与金矿问题中,下一行的某个状态,由上一行的某两个状态决定,因为需要维护一个一维数组,用来保存上一行的最优值。
然而在LeetCode第62题中,因为状态转移方程是dp[i][j] = dp[i - 1][j] + dp[i][j - 1],当前状态由上一行的第j列与当前行的前一个决定,所以只需要保存两个数据即可。

要根据状态转移方程确定需要保存的东西。

因此在k等于1时,初始化数组时,需要注意初始化的正确性。

建模分析

根据f[k][i]的定义,我们可以得到最优子结构与状态转移方程。
一定要举例说明

  • 1、例如f[3][6],已经选择第7个同学作为结尾,那么ability的第7个值就作为乘积,若是前一个同学选择第6个,那么就是f[2][5] * ability[6],这是其中一个最优子结构,同理,若是倒数第二个同学是第5个,那么就是f[2][4] * ability[6]。

  • 2、理论上,对于n_id,有n_id-1个子结构。因此应该在range(0,n_id)中遍历每个序号作为倒数第二个同学,在遍历的就结果中选择最大最小值。

  • 3、又因为这里加了限制,倒数第二个同学的编号与最后一个编号不能超过d。
    于是,修改循环的取值,若n_id大于d,则从0开始,小于d就从 n_id - d开始
    改为range(max(0, n_id - d), n_id)。
    这里真的是根据题意得到的一个小技巧。

代码实现:

n = int(input())
ability = [int(x) for x in input().split()]
k, d = [int(x) for x in input().split()]


def chorus_dp2(n,ability,k,d):
    # 初始化两个数组,分别存储最大值最小值
    # 其中,f[k][i]代表的是,选择k个学生并且以第i个学生作为结尾时的最大(或最小)乘积
    f_max = []
    f_min = []
    for k_id in range(k):
        f_max.append([])
        f_min.append([])
        for n_id in range(n):
            if k_id == 0:
                #在k等于1 的时候,根据f[k][i]的定义可知,第i个值就应该是ability[i]
                f_max[k_id].append(ability[n_id])
                f_min[k_id].append(ability[n_id])
            else:
                f_max[k_id].append(0)
                f_min[k_id].append(0)

    for k_id in range(1,k):
            res_mid_max = []
            res_mid_min = []
            for j in range(max(0, n_id - d), n_id):
                res_mid_max.append(max(f_max[k_id-1][j] * ability[n_id],
                                       f_min[k_id-1][j] * ability[n_id]))
                res_mid_min.append(min(f_max[k_id-1][j] * ability[n_id],
                                       f_min[k_id-1][j] * ability[n_id]))
            f_max[k_id][n_id] = max(res_mid_max)
            f_min[k_id][n_id] = min(res_mid_min)

    return f_max

print(max(chorus_dp2(n,ability,k,d)[k-1]))


#这段代码并没有考虑k等于n的情况。有bug。
#题目中也没有给k大于n时应该怎么办,是不是允许重复的。题目这一点也出的不好。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 4,163评论 0 2
  • 这个不错分享给大家,从扣上看到的,就转过来了 《电脑专业英语》 file [fail] n. 文件;v. 保存文...
    麦子先生R阅读 7,219评论 5 24
  • 人之为人,除肉身之外,还有情感藏在肉身之内。肉身大抵是一样的貌式,情感大抵也是一样的情感。貌式都是一副长身、一只脑...
    曾彧阅读 203评论 0 1
  • 家庭纷争运进入本周。 1,做事没有成果、缺乏上进心、业绩下降,很多计划受阻。 2,疲倦,疲劳,必须多喝水与休息,尽...
    樊易林阅读 550评论 0 0

友情链接更多精彩内容