面试中算法(金矿)

有一位国王拥有5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人人数也不同。

例如,有的金矿储量是5ookg黄金,需要5个工人来挖掘;有的金矿储量是2ookg黄金,需要3个工人来挖掘...... 如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半的金矿。要求用程序要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

金矿按照性价比从高到低进行排序,排名结果如下:

第1名,350kg黄金/3人的金矿,人均产量约为116.6kg黄金。

第2名,500kg黄金/5人的金矿,人均产量为100kg黄金。

第3名,400kg黄金/5人的金矿,人均产量为80kg黄金。

第4名,300kg黄金/4人的金矿,人均产量为75kg黄金。

第5名,200kg黄金/3人的金矿,人均产量约为66.6kg黄金。

由于工人数量是10人,优先挖掘性价比排名为第1名和第2名的金矿之后,工人还剩下2人,不够再挖掘其他金矿了。

得出的最佳金矿收益是350+500即850kg黄金。
解决思路是使用贪心算法。这种思路在局部情况下是最优解,但是在整体上却未必是最优的。

    如果我放弃性价比最高的350kg黄金/3人的金矿,选择500kg黄 金/5人和400kg黄金/5人的金矿,加起来收益是900kg黄金,是不是大于你得到的850kg黄金?

动态规划,就是把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。 

10个工人在前4个金矿的收益,和7个工人在前4个金矿的收益+最后一个金矿的收益谁大谁小了。

 首先针对10个工人4个金矿这个子结构,第4个金矿(300kg黄金/4人)可以选择挖与不挖。根据第4个金矿的选择,问题又简化成了两种更小的子结构:

1、10个工人在前3个金矿中做出最优选择。

2、(10-4=6)6个工人在前3个金矿中做出最优选择。

相应地,对于7个工人4个金矿这个子结构,第4个金矿同样可以选择挖与不挖。根据第4个金矿的选择,问题也简化成了两种更小的子结构:

1、7个工人在前3个金矿中做出最优选择。

2、(7-4=3)3个工人在前3个金矿中做出最优选择。

就这样,问题一分为二,二分为四,一直把问题简化成在0个金矿或0个工人时的最优选择,这个收益结果显然是0,也就是问题的边界。
这就是动态规划的要点:确定全局最优解和最优子结构之间的关系,以及问题的边界。

这个关系用数学公式来表达,叫作状态转移方程式。

金矿数量设为n,工人数量设为w,金矿的含金量设为数组g[],金矿所需开采人数设为数组p[],设F (n,w)为n个金矿、w个工人时的最优收益函数,那么状态转移方程式如下:

F(n,w) =0 (n=0或w=0)     问题边界,金矿数为0或工人数为0的情况。

F(n,w)=F(n-1,w)(n≥1,w<p[n-1])    当所剩工人不够挖掘当前金矿时,只有一种最优子结构。

F(n,w)= max(F(n-1,w),F(n-1,w-p[n-1])+g[n-1])(n≥1,wzp[n-1])    在常规情况下,具有两种最优子结构(挖当前金矿或不挖当前金矿)。 

 

在上图中,标为橘色的方法调用是重复的。可以看到F (2,7) 、F(1,7)、F (1,2)这几个入参相同的方法都被调用了两次。

当金矿数量为5时,重复调用的问题还不太明显。金矿数量越多,递归层次越深,重复调用也就越多,这些无谓的调用必然会降低程序的性能。 

动态规划的另一个核心要点:自底向上求解。

此时,最后1行最后1个格子所填的900就是最终要求的结果,即5个金矿、10个工人的最优收益是9ookg黄金。 

使用二维数组来代表表格

def get_best_gold(worker,person=[],gold=[]):
    '''
    :param worker:  工人数量
    :param person:  金矿开采人数
    :param gold:  金矿存储量
    :return:  最优收益
    '''
    #初始化表格0
    r_table=[[0 for i in range(worker+1)] for j in range(len(gold)+1)]
    double_for(r_table)
    print('&'*50)
    #填充表格数据
    for i in range(1,len(gold)+1):
        for j in range(1,worker+1):
            if j<person[i-1]:
                r_table[i][j]=r_table[i-1][j]
            else:
                r_table[i][j] =max(r_table[i - 1][j],r_table[i - 1][j-person[i-1]]+gold[i-1])

    double_for(r_table)
    #返回最后一个格子的数据
    return r_table[len(gold)][worker]


def double_for(ll):
    for row in ll:
            print(row)


if __name__ == '__main__':
    #采矿人数
    person=[5,5,3,4,3]
    #采矿黄金量
    gold=[400,500,200,300,350]
    print(get_best_gold(10, person, gold))

      程序利用双循环来填充一个二维数组,所以时间复杂度和空间复杂度都是O ( nw) ,比递归的性能好多啦! 

def get_best_gold2(worker,person=[],gold=[]):
    '''
    优化
    :param worker:  工人数量
    :param person:  金矿开采人数
    :param gold:  金矿存储量
    :return:  最优收益
    '''
    #初始化表格0
    results=[0]*(worker+1)
    print(results)
    #填充一维数据
    for i in range(1,len(gold)+1):
        for j in range(worker,0,-1):
            if j>=person[i-1]:
                results[j] =max(results[j],results[j-person[i-1]]+gold[i-1])

        print(results)

    #返回最后一个格子的数据
    return results[worker]


if __name__ == '__main__':
    #采矿人数
    person=[5,5,3,4,3]
    #采矿黄金量
    gold=[400,500,200,300,350]
    print(get_best_gold2(10, person, gold))

空间复杂度降低到了O (n)。 

相关推荐

  1. 面试-算法

    2024-05-13 09:58:02       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-13 09:58:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-13 09:58:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-13 09:58:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-13 09:58:02       18 阅读

热门阅读

  1. Mac 查看jdk版本

    2024-05-13 09:58:02       11 阅读
  2. Oracle的高级分组函数grouping和grouping_id

    2024-05-13 09:58:02       12 阅读
  3. 【重庆橙子科技】了解调用链

    2024-05-13 09:58:02       9 阅读
  4. 大型ERP设计-业务与功能指引:物料库存状态

    2024-05-13 09:58:02       15 阅读
  5. shell编程

    2024-05-13 09:58:02       11 阅读
  6. Kafka 面试题(八)

    2024-05-13 09:58:02       10 阅读
  7. C语言每日一题—约瑟夫问题

    2024-05-13 09:58:02       9 阅读
  8. Excel生成符合标准正态分布的随机数

    2024-05-13 09:58:02       14 阅读
  9. C++:完美转发(二)(std::forward)

    2024-05-13 09:58:02       13 阅读
  10. MFC:鼠标左键

    2024-05-13 09:58:02       10 阅读