算法设计与分析实验:动态规划与贪心

这篇具有很好参考价值的文章主要介绍了算法设计与分析实验:动态规划与贪心。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、零钱兑换

1.1 思路一:动态规划

1.2 思路二:贪心

二、安排工作以达到最大效益

2.1 具体思路

2.2 思路呈现

2.3 代码实现

2.4 复杂度分析

2.5 运行结果

三、雇佣k名工人的最低成本

3.1 具体思路

3.2 思路展示

3.3 代码实现

3.4 复杂度分析

3.5 运行结果

结尾语

“生活有意思的点就在于未知的某些时刻,你准备去a的时候,可能会遇到b,聊到c,想到d,然后一起去找了e”


一、零钱兑换

力扣第322题

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

1.1 思路一:动态规划

(1)具体思路

首先初始化一个长度为 amount+1 的数组 dp,dp[i] 表示凑齐金额 i 需要的最少硬币数,初始值为 amount+1,因为最多只需要凑齐 amount 枚 1 元硬币。

将 dp[0] 初始化为 0,表示凑齐金额 0 不需要任何硬币。

对于每个金额 i,遍历硬币数组 coins,如果当前硬币面额 j 小于等于 i,更新 dp[i] = min(dp[i], dp[i - j] + 1)。

最终返回 dp[amount],如果 dp[amount] 大于 amount,则表示无法凑齐总金额,返回 -1。

(2)思路呈现

假设有三种硬币,面额分别为 1、2、5 元,我们要凑齐总金额为 11 元。按照动态规划的思路,我们可以进行如下步骤:

初始化一个长度为 12(amount+1)的数组 dp,并将所有元素初始化为正无穷大,表示初始时无法凑出任何金额。

dp = [inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf]

将 dp[0] 初始化为 0,表示凑齐金额 0 不需要任何硬币。

dp[0] = 0

对于每个金额 i,遍历硬币数组 coins,如果当前硬币面额 j 小于等于 i,更新 dp[i] = min(dp[i], dp[i - j] + 1)。

遍历到金额 1:

当前硬币面额为 1,小于等于 1,更新 dp[1] = min(inf, dp[1-1]+1) = min(inf, dp[0]+1) = min(inf, 0+1) = 1

遍历到金额 2:

当前硬币面额为 1,小于等于 2,更新 dp[2] = min(1, dp[2-1]+1) = min(1, dp[1]+1) = min(1, 1+1) = 1

当前硬币面额为 2,小于等于 2,更新 dp[2] = min(1, dp[2-2]+1) = min(1, dp[0]+1) = min(1, 0+1) = 1

遍历到金额 3:

当前硬币面额为 1,小于等于 3,更新 dp[3] = min(1, dp[3-1]+1) = min(1, dp[2]+1) = min(1, 1+1) = 1

当前硬币面额为 2,小于等于 3,更新 dp[3] = min(1, dp[3-2]+1) = min(1, dp[1]+1) = min(1, 1+1) = 1

...以此类推

最终返回 dp[11],如果 dp[11] 大于 11,则表示无法凑齐总金额,返回 -1。

dp[11] = 3 (表示凑齐金额 11 需要的最少硬币数)

因此,对于硬币面额为 [1, 2, 5],要凑齐金额 11,最少需要 3 枚硬币。

(3)代码实现

def coinChange(coins, amount):
    dp = [amount + 1] * (amount + 1)
    dp[0] = 0
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    return dp[amount] if dp[amount] <= amount else -1

# 示例测试
print(coinChange([1, 2, 5], 11))  # 输出:3
print(coinChange([2], 3))         # 输出:-1
print(coinChange([1], 0))         # 输出:0

(4)复杂度分析

时间复杂度分析:

外层循环对每个金额 i 进行遍历,内层循环对每个硬币进行遍历,所以总的时间复杂度为 O(n * k),其中 n 为金额数,k 为硬币种类数。

空间复杂度分析:

代码中使用了一个长度为 amount+1 的数组 dp 作为动态规划的辅助空间,因此空间复杂度为 O(amount)。

综合来看,这段代码的时间复杂度为 O(n * k),空间复杂度为 O(amount)。

1.2 思路二:贪心

(1)具体思路

将硬币数组 coins 按照面额从大到小排序,这样可以保证每次选择的硬币面额尽可能大,尽量减少硬币数量。

定义一个变量 count,表示凑成总金额所需的最少硬币个数,初始化为 0。

遍历排序后的硬币数组 coins,对于每个硬币面额 coin,尽可能地使用多个硬币,直到凑够总金额 amount 或者不能再继续添加硬币为止。

如果凑够了总金额 amount,则返回 count;否则返回 -1。

(2)思路呈现

假设硬币数组 coins = [1, 2, 5],总金额 amount = 11。

首先,将硬币数组按照面额从大到小排序,得到 [5, 2, 1]。

初始化 count = 0。

开始遍历排序后的硬币数组。

当遍历到硬币面额为 5 时,我们可以使用两个硬币 5,凑够了总金额 11,此时 count = 2。返回 count。

下面是对应的示意图:

coins = [5, 2, 1]

amount = 11

count = 0

返回 count = 2

(3)代码实现

def coinChange(coins, amount):
    # 将硬币数组按照面额从大到小排序
    coins.sort(reverse=True)
    # 定义一个变量 count,表示凑成总金额所需的最少硬币个数
    count = 0
    # 遍历硬币数组,对于每个硬币面额 coin,尽可能地使用多个硬币
    for coin in coins:
        while amount >= coin:
            amount -= coin
            count += 1
    # 如果凑够了总金额 amount,则返回 count;否则返回 -1
    return count if amount == 0 else -1

# 示例测试
print(coinChange([1, 2, 5], 11))  # 输出:3
print(coinChange([2], 3))         # 输出:-1
print(coinChange([1], 0))         # 输出:0

(4)复杂度分析

时间复杂度分析:

对硬币数组进行排序的时间复杂度为 O(n log n),其中 n 为硬币种类数。

遍历排序后的硬币数组需要 O(n) 的时间,每次遍历的时间复杂度为 O(amount/coin),其中 coin 表示当前硬币的面额。

因此,总的时间复杂度为 O(n log n + n * amount/coin_max),其中 coin_max 表示硬币数组中面额最大的硬币。

空间复杂度分析:

这段代码只使用了常数级别的额外空间,所以空间复杂度为 O(1)。

这段代码的时间复杂度主要取决于排序操作的时间复杂度,为 O(n log n)。

空间复杂度为 O(1),非常低。

需要注意的是,这个算法使用了贪心策略,可能无法得到最优解。在某些情况下,可能会出现无法凑够总金额的情况,返回 -1。如果需要求得准确的最少硬币个数,可以考虑使用动态规划等其他算法。

(5)运行结果

三个示例如下

示例 1:

输入:coins = [1, 2, 5], amount = 11

输出:3 

示例 2:

输入:coins = [2], amount = 3

输出:-1

示例 3:

输入:coins = [1], amount = 0

输出:0

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

结果均与预期一致

二、安排工作以达到最大效益

力扣826题

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

本题采用贪心的思想解决

2.1 具体思路

首先,我们需要将工作按照难度从小到大进行排序,并且保持与对应的收益的对应关系。可以使用zip函数将难度数组和收益数组合并,并根据难度进行排序。

接下来,我们需要对工人的能力数组进行排序,以便之后逐个遍历工人。

初始化总收益为0,并设置两个指针:一个指向工人能力数组的当前工人,一个指向工作数组的当前工作。

开始遍历工人能力数组,对于每个工人能力,我们需要找到他能够完成的难度范围内的最高收益。

在遍历工作数组时,如果当前工作的难度小于等于当前工人的能力,更新最高收益为当前工作的收益。然后,移动工作指针以处理下一个工作。

如果当前工作的难度大于当前工人的能力,或者已经遍历完所有工作,则将当前工人的最高收益加到总收益中,并继续处理下一个工人。

最后,返回总收益作为结果。

2.2 思路呈现

假设有四个工作,难度和收益分别如下:

难度:[2, 4, 6, 8]

收益:[10, 20, 30, 40]

现在有三个工人,能力数组如下:

[4, 5, 6]

首先,我们将工作按照难度从小到大进行排序,同时保持与对应的收益的对应关系:

难度:[2, 4, 6, 8]

收益:[10, 20, 30, 40]

接下来,对工人的能力数组进行排序:

工人能力:[4, 5, 6]

初始化总收益为0,并设置两个指针,一个指向工人能力数组的当前工人,一个指向工作数组的当前工作。

开始遍历工人能力数组,对于每个工人能力,我们需要找到他能够完成的难度范围内的最高收益。

对于第一个工人,能力为4。遍历工作数组,发现第一个工作的难度2小于等于4,更新最高收益为10。继续遍历,发现第二个工作的难度4也小于等于4,但是收益20比已经记录的最高收益10要低,所以最高收益仍然是10。继续遍历,发现第三个工作的难度6大于4,结束遍历。将最高收益10加到总收益中。

对于第二个工人,能力为5。遍历工作数组,发现第一个工作的难度2小于等于5,更新最高收益为10。继续遍历,发现第二个工作的难度4也小于等于5,更新最高收益为20。继续遍历,发现第三个工作的难度6大于5,结束遍历。将最高收益20加到总收益中。

对于第三个工人,能力为6。遍历工作数组,发现第一个工作的难度2小于等于6,更新最高收益为10。继续遍历,发现第二个工作的难度4也小于等于6,更新最高收益为20。继续遍历,发现第三个工作的难度6等于6,更新最高收益为30。继续遍历,发现第四个工作的难度8大于6,结束遍历。将最高收益30加到总收益中。

最后,返回总收益作为结果。根据上述计算,总收益为10 + 20 + 30 = 60。

2.3 代码实现

def maxProfitAssignment(difficulty, profit, worker):
    # 将难度和对应的收益按照难度升序进行排序
    jobs = sorted(zip(difficulty, profit))
    # 对工人的能力进行排序
    worker.sort()

    total_profit = 0
    i = 0
    max_profit = 0

    for ability in worker:
        while i < len(jobs) and ability >= jobs[i][0]:
            # 更新当前能力范围内的最大收益
            max_profit = max(max_profit, jobs[i][1])
            i += 1
        total_profit += max_profit

    return total_profit


# 示例测试
print(maxProfitAssignment([2, 4, 6, 8, 10], [10, 20, 30, 40, 50], [4, 5, 6, 7]))  # 输出:100
print(maxProfitAssignment([85, 47, 57], [24, 66, 99], [40, 25, 25]))  # 输出:0

2.4 复杂度分析

首先,对难度和收益进行排序,时间复杂度为O(nlogn),其中n为工作的数量。

对工人的能力进行排序,时间复杂度为O(mlogm),其中m为工人的数量。

遍历工人能力数组,对于每个工人能力,在内部循环中遍历工作数组,直到找到工人能力范围内的最高收益。由于工作数组已经按照难度升序排列,因此在内部循环中只需要从上一次结束的位置开始遍历即可,避免了不必要的遍历。因此,内部循环的时间复杂度为O(n),其中n为工作的数量。

综上所述,总的时间复杂度为O(nlogn + mlogm + n),也可以简化为O(max(nlogn, mlogm))。空间复杂度为O(1),没有使用额外的空间。

2.5 运行结果

I示例及预期结果如下

示例 1:

输入: difficulty = [2,4,6,8,10], profit = [10,20,30,40,50], worker = [4,5,6,7]

输出: 100 

解释: 工人被分配的工作难度是 [4,4,6,6] ,分别获得 [20,20,30,30] 的收益。

示例 2:

输入: difficulty = [85,47,57], profit = [24,66,99], worker = [40,25,25]

输出: 0

II 实际输出与预期结果一致

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

三、雇佣k名工人的最低成本

力扣第857题

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

本题依旧使用贪心的思想,具体分析如下

3.1 具体思路

首先,我们可以计算工人的“性价比”,即 wage[i]/quality[i],表示每单位工作质量所需的工资。

然后,我们对所有工人按照性价比进行排序,并依次计算以当前工人为基准的工资组的总工资。具体步骤如下:

首先,我们对工人按照性价比进行排序。

然后,从性价比最小的工人开始,依次计算以当前工人为基准的工资组的总工资。具体做法是,对于当前工人,我们以他的性价比作为标准,计算出其他工人的期望工资,然后将这些期望工资按照工作质量排序。取前k-1 个工人的期望工资之和,加上当前工人的期望工资,就得到了以当前工人为基准的工资组的总工资。

我们遍历所有工人,不断更新最小的总工资。

3.2 思路展示

假设有5名工人,他们的工作质量和最低期望工资分别如下:

quality = [3, 1, 10, 10, 1]

wage = [4, 8, 2, 2, 7]

首先,我们计算每名工人的性价比,即 wage[i]/quality[i],得到以下结果:

性价比 = [4/3, 8/1, 2/10, 2/10, 7/1] = [1.33333, 8, 0.2, 0.2, 7]

按照性价比进行排序,得到以下顺序:

性价比排序 = [0.2, 0.2, 1.33333, 7, 8]

现在从性价比最小的工人开始,依次计算以当前工人为基准的工资组的总工资。

以第一个工人(性价比为0.2)为基准,计算其他工人的期望工资并按工作质量排序:

期望工资 = [0.2 * 3, 0.2 * 1, 0.2 * 10, 0.2 * 10, 0.2 * 1] = [0.6, 0.2, 2, 2, 0.2]

按工作质量排序后为:[0.2, 0.2, 0.6, 2, 2]

取前k-1个工人的期望工资之和,加上当前工人的期望工资,得到以当前工人为基准的工资组的总工资:

总工资 = 0.2 + 0.2 + 0.6 = 1

接下来,以第二个工人(性价比为0.2)为基准重复以上步骤:

期望工资 = [0.2 * 3, 0.2 * 1, 0.2 * 10, 0.2 * 10, 0.2 * 1] = [0.6, 0.2, 2, 2, 0.2]

按工作质量排序后为:[0.2, 0.2, 0.6, 2, 2]

总工资 = 0.2 + 0.2 + 0.6 = 1

以此类推,最后得到总工资最小的工资组。

3.3 代码实现

import heapq

def mincostToHireWorkers(quality, wage, k):
    n = len(quality)
    workers = sorted((w / q, q, w) for w, q in zip(wage, quality))
    ans = float('inf')
    pool = []
    sumq = 0
    for ratio, q, w in workers:
        heapq.heappush(pool, -q)
        sumq += q
        if len(pool) > k:
            sumq += heapq.heappop(pool)
        if len(pool) == k:
            ans = min(ans, sumq * ratio)
    return ans

# 示例测试
print(mincostToHireWorkers([10,20,5], [70,50,30], 2))  # 输出:105.00000
print(mincostToHireWorkers([3,1,10,10,1], [4,8,2,2,7], 3))  # 输出:30.66667

3.4 复杂度分析

该算法的时间复杂度为 O(nlogn),其中 n 为工人数量。主要是因为对工人进行排序所需的时间复杂度为 O(nlogn)。

空间复杂度为 O(n),主要是因为要存储每名工人的性价比、工作质量和期望工资,以及维护一个大小为 k 的小根堆。

在算法实现中,使用了 heapq 模块来快速实现小根堆的维护,从而使代码更加简洁高效。

3.5 运行结果

示例测试如下

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

运行结果如下,与预期一致

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

结尾语

“生活有意思的点就在于未知的某些时刻,你准备去a的时候,可能会遇到b,聊到c,想到d,然后一起去找了e”

算法设计与分析实验:动态规划与贪心,算法分析与设计,算法,动态规划,贪心算法,dp,leetcode

祝你天天开心!

2024-2-2文章来源地址https://www.toymoban.com/news/detail-827850.html

到了这里,关于算法设计与分析实验:动态规划与贪心的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 南邮|算法分析与设计实验二 动态规划法

    南邮|算法分析与设计实验二 动态规划法

    目录 实验目的 实验内容 实验步骤 一、最长公共子序列 二、矩阵连乘  加深对动态规划法的算法原理及实现过程的理解,学习用动态规划法解决实际应用中的 最长公共子序列问题 和 矩阵连乘问题 ,体会 动态规划法 和 备忘录方法 的异同。 一、用动态规划法和备忘录方法

    2024年02月01日
    浏览(10)
  • 算法设计与分析—动态规划作业二(头歌实验)

    任务描述 本关任务:计算寻宝人所能带走的宝物的最大价值。 一个寻宝人在沙漠中发现一处神秘的宝藏,宝藏中共有 n 个宝物( n 不超过 20 ),每个宝物的重量不同,价值也不同,宝物 i 的重量是 wi ,其价值为 vi 。 寻宝人所能拿走的宝物的总重量为 m ( m 不超过 50 )。请

    2024年02月06日
    浏览(18)
  • 算法设计与分析—动态规划作业一(头歌实验)

    任务描述 本关任务:求一个序列的最长上升子序列。 相关知识 最长上升子序列问题 当一个序列 Bi 满足 B1 B2 ... Bs 的时候,我们称这个序列是 上升 的。对于给定的一个序列 a1, a2, ..., aN ,我们可以在其中找到一些上升的子序列。 现在给你一个序列,请你求出其中 最长 的上

    2024年02月04日
    浏览(63)
  • 算法设计与分析实验二:动态规划法求解TSP问题和01背包问题

    算法设计与分析实验二:动态规划法求解TSP问题和01背包问题

    【实验内容】 (1)tsp问题:利用动态规划算法编程求解TSP问题,并进行时间复杂性分析。 输入:n个城市,权值,任选一个城市出发; 输出:以表格形式输出结果,并给出向量解和最短路径长度。 (2)01背包问题:利用动态规划算法编程求解0-1背包问题,并进行时间复杂性分

    2024年02月03日
    浏览(9)
  • 算法设计与分析实验4 :利用动态规划的方法解决子集等和分割判断问题

    实验4  利用动态规划的方法解决子集等和分割判断问题 一、实验目的 1. 了解动态规划的主要思想。 2. 掌握背包问题解决方法用以解决该问题。 3. 分析核心代码的时间复杂度和空间复杂度。 二、实验内容和要求 题目:给定一个只包含正整数的非空数组。是否可以将这个数组

    2024年04月23日
    浏览(10)
  • (软考-软件设计师.下午)动态规划算法、回溯算法、贪心算法、分治算法的应用

    (软考-软件设计师.下午)动态规划算法、回溯算法、贪心算法、分治算法的应用

    :【递归技术】【二分查找】 分治法的设计思路: 将一个难以直接解决的 大问题 分解成一些 规模较小 的相同问题以便于 逐个击破,分而治之 。      由代码可以看出二分查找也属于分治法的一种,关于二分查找,这位博主总结的很详细。  :【查表】   动

    2024年02月06日
    浏览(58)
  • 【算法】动态规划 ⑦ ( LeetCode 55. 跳跃游戏 | 算法分析 | 代码示例 )

    LeetCode 55. 跳跃游戏 : https://leetcode.cn/problems/jump-game/ 给定一个 非负整数数组 nums ,你最初位于数组的 第一个下标 0 位置 。 数组中的每个元素 代表你在该位置可以 跳跃的最大长度。 判断你 是否能够到达最后一个下标。 给定一个一维数组 , 数组元素不能有负数 , 如 : {2, 2,

    2024年02月10日
    浏览(12)
  • 【LeetCode:1402. 做菜顺序 | 动态规划 + 贪心】

    【LeetCode:1402. 做菜顺序 | 动态规划 + 贪心】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月07日
    浏览(10)
  • 【动态规划】LeetCode 312. 戳气球 --区间DP问题

    【动态规划】LeetCode 312. 戳气球 --区间DP问题

      Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接       我会一直往里填充内容哒! 🌈LeetCode专栏:专栏链接       目前在刷初级算法的LeetBook 。若每日一题当中有力所能

    2023年04月16日
    浏览(10)
  • 算法分析与设计--动态规划

    算法分析与设计--动态规划

    一、动态规划简介 二、动态规划求解步骤 三、动态规划典型应用 数字三角形问题 最大子段和问题 0-1背包问题 四、最长公共子序列问题 动态规划求解 五、总结 算法语言--java语言 动态规划算法通常用于求解具有某种最优性质的问题。动态规划与分治算法类似,其基本思想也

    2024年02月07日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包