美文网首页动态规划
动态规划_实战2

动态规划_实战2

作者: 小碧小琳 | 来源:发表于2018-08-29 21:19 被阅读16次

网易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时应该怎么办,是不是允许重复的。题目这一点也出的不好。

相关文章

  • Swift 算法实战:动态规划

    Swift 算法实战:动态规划 Swift 算法实战:动态规划

  • 动态规划_实战2

    网易2017年合唱团 做题经验:对于一个复杂的题,若是附加条件太多,不能一步到位的解决,可以逐渐的减少附加条件,也...

  • 《数据结构与算法之美》27——初识动态规划

    前言 今天开始学习动态规划,一共有三节,分别是:初识动态规划、动态规划理论、动态规划实战。今天这一节就是初识动态规...

  • 动态规划实战

    如何量化两个字符串的相似度? 编辑距离指的就是,将一个字符串转化成另一个字符串,需要的最少编辑操作次数(比如增加一...

  • 斐波那契数列

    递归解法 动态规划解法1 动态规划解法2

  • 4. 动态规划算法

    1. 动态规划算法总结2. 漫画:什么是动态规划?3.算法之动态规划4. 动态规划-算法

  • 动态规划_实战1

    参考剑指offer 一、前言——递归与循环 即使同一个算法用循环和递归两种思路实现的时间效率可能会大不一样。 1....

  • 动态规划(2)

    如果在只由'('和')'两种字符组成的字符串中,每一个符号都有合理的匹配,我们说这个字符串是完整的。问题1:怎么判...

  • 动态规划2

    参考:https://www.cnblogs.com/CheeseZH/p/8830482.html最长公共子序列...

  • iOS开发集锦之 2017.04.12(Swift 算法实战之路

    1. Swift 算法实战之路:动态规划 作者: 故胤道长描述: 深度优先搜索(Depth-First-Searc...

网友评论

    本文标题:动态规划_实战2

    本文链接:https://www.haomeiwen.com/subject/kixlwftx.html