算法设计与分析-Dynamic Programming「国科大」卜东波老师

这篇具有很好参考价值的文章主要介绍了算法设计与分析-Dynamic Programming「国科大」卜东波老师。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.Question Number 1: Money Robbing

A robber is planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

(a) Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

(b) What if all houses are arranged in a circle?

针对“Money Robbing”问题,可分为线性街道和环形街道的情况。

1.1 问题 (a): 线性街道

1.1.1 算法描述
  • 自然语言描述: 使用动态规划来解决这个问题。对于每个房子,强盗有两个选择:抢或不抢。如果他决定抢劫第i个房子,那么他不能抢劫第i-1个房子;如果他不抢劫第i个房子,那么他可以从前i-1个房子中获得的最大金额。

  • 伪代码:

rob(houses):
    # 如果没有房子,返回0
    if houses is empty:
        return 0

    # 如果只有一个房子,返回该房子的金额
    if length of houses is 1:
        return houses[0]

    # 初始化动态规划数组
    dp[0] = houses[0]  # 只有一个房子时的最大金额
    dp[1] = max(houses[0], houses[1])  # 前两个房子中的最大金额

    # 从第三个房子开始,计算每个房子的最大抢劫金额
    for i from 2 to length of houses - 1:
        # dp[i] 是前i个房子的最大金额,等于以下两者中的较大者:
        # 1. 不抢第i个房子,金额为dp[i-1]
        # 2. 抢第i个房子,金额为dp[i-2]加上第i个房子的金额
        dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])

    # 返回最后一个房子的最大抢劫金额
    return dp[length of houses - 1]
1.1.2 最优子结构和DP方程
  • 最优子结构: 对于第i个房子,最大抢劫金额是基于前i-1个房子的最大抢劫金额决定的。
  • DP方程: dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])
1.1.3 算法正确性证明
  • 基本情况: 当只有一个或两个房子时,选择金额最大的房子。
  • 归纳步骤: 假设对于前i-1个房子的最大金额我们已经知道,那么对于第i个房子,我们可以选择不抢(保持dp[i-1]),或者抢(dp[i-2] + houses[i])。这两种选择保证了我们总是得到最大金额。
1.1.4 算法复杂度分析
  • 时间复杂度: O(n),其中n是房子的数量。
  • 空间复杂度: O(n),用于存储每个房子的最大抢劫金额。

1.2问题 (b): 环形街道

1.2.1 算法描述
  • 自然语言描述: 在环形街道的情况下,问题变成了不能同时抢第一个和最后一个房子。我们可以把问题分成两个子问题:一个不包括第一个房子,另一个不包括最后一个房子。对这两个子问题分别使用上面的动态规划算法,然后取两者的最大值。

  • 伪代码:

    rob_circular(houses):
        if houses is empty:
            return 0
        if length of houses is 1:
            return houses[0]
        return max(rob(houses[1:]), rob(houses[:-1]))
    
1.2.2 最优子结构和DP方程
  • 最优子结构: 和线性街道一样,但需要考虑环状结构,将问题分为两个子问题。
  • DP方程: 与线性街道相同。
1.2.3 算法正确性证明
  • 算法将问题分为两个子问题:一个包括第一个房子但不包括最后一个,另一个包括最后一个房子但不包括第一个。由于这两个子问题都是线性的,因此它们的解决方案有效,并且取两者的最大值可以保证总体解决方案的有效性。
1.2.4 算法复杂度分析
  • 时间复杂度: O(n),其中n是房

一个具体的例子并用python实现:

假设有一个街道,房屋中的金额分别为 [1, 2, 3, 1]。在这种情况下,强盗应该选择第二个和第三个房子来抢劫,以获得最大金额 2 + 3 = 5。

def rob_linear(houses):
    if not houses:
        return 0
    if len(houses) == 1:
        return houses[0]

    dp = [0] * len(houses)
    dp[0] = houses[0]
    dp[1] = max(houses[0], houses[1])

    for i in range(2, len(houses)):
        dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])

    return dp[-1]

def rob_circular(houses):
    if not houses:
        return 0
    if len(houses) == 1:
        return houses[0]

    return max(rob_linear(houses[:-1]), rob_linear(houses[1:]))

# 测试例子
houses = [1, 2, 3, 1]
print("Max amount in linear street:", rob_linear(houses))
print("Max amount in circular street:", rob_circular(houses))

2.Question Number 2: Ugly Number

An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5. Given an integer
n, return the nth ugly number.
(a) Using a brute-force algorithm to solve this problem, analyze the time complexity of your implemented brute-force algorithm and explain why the algorithm’s time complexity is O(n2), where n is the number of points.
(b) Propose an improved algorithm to solve this problem with a time complexity better than the brute-force algorithm. Describe the algorithm’s idea and analyze its time complexity.

问题:丑数(Ugly Number)

丑数是只包含质因数2、3和5的正整数。给定一个整数n,返回第n个丑数。

(a) 暴力算法
1. 算法描述
  • 自然语言描述:从1开始,对每个整数检查它是否为丑数。检查方法是不断地除以2、3和5,如果最终结果为1,则该数为丑数。重复这个过程直到找到第n个丑数。
  • 伪代码:
    find_nth_ugly_number(n):
        count = 0
        number = 1
        while count < n:
            if is_ugly(number):
                count += 1
            number += 1
        return number - 1
    
    is_ugly(num):
        for i in [2, 3, 5]:
            while num % i == 0:
                num /= i
        return num == 1
    
2. 最优子结构和DP方程
  • 这个暴力算法没有使用动态规划,因此没有最优子结构和DP方程。
3. 算法正确性证明
  • 每个数通过除以2、3和5来检查是否是丑数。如果一个数只由这三个质因数组成,最终结果将是1。
4. 算法复杂度分析
  • 时间复杂度:O(n^2),其中n是目标丑数的索引。对于每个数,我们需要O(log n)的时间来判断它是否为丑数,而我们需要检查大约n个数。
  • 空间复杂度:O(1),不需要额外的空间。
(b) 改进算法
1. 算法描述
  • 自然语言描述:使用三个队列,分别乘以2、3和5。每次从这三个队列的头部选取最小的数作为新的丑数,并将其乘以2、3和5加入相应的队列中。这样可以确保按顺序生成丑数。
  • 伪代码:
    improved_find_nth_ugly_number(n):
        ugly_numbers = [1]
        i2, i3, i5 = 0, 0, 0
        while len(ugly_numbers) < n:
            next2, next3, next5 = ugly_numbers[i2] * 2, ugly_numbers[i3] * 3, ugly_numbers[i5] * 5
            next_ugly = min(next2, next3, next5)
            ugly_numbers.append(next_ugly)
            if next_ugly == next2:
                i2 += 1
            if next_ugly == next3:
                i3 += 1
            if next_ugly == next5:
                i5 += 1
        return ugly_numbers[-1]
    
2. 最优子结构和DP方程
  • 最优子结构:第n个丑数依赖于之前的丑数。
  • DP方程ugly_numbers[n] = min(ugly_numbers[i2] * 2, ugly_numbers[i3] * 3, ugly_numbers[i5] * 5)
3. 算法正确性证明
  • 该算法确保每次添加

到列表的丑数是当前最小的丑数。通过乘以2、3和5并比较,我们可以保证按顺序生成丑数,并且不会遗漏任何丑数。

4. 算法复杂度分析
  • 时间复杂度:O(n),其中n是目标丑数的索引。每个丑数只需要常数时间就能计算出来,因此总的时间复杂度为线性。
  • 空间复杂度:O(n),用于存储前n个丑数。

小结

暴力算法简单直接,但效率低下,因为它需要为每个数都检查是否为丑数。改进的算法则通过维护三个队列,每次只计算可能成为下一个丑数的数,大大提高了效率。通过这种方法,我们可以按顺序生成丑数,并且保证每次添加的都是当前最小的丑数,从而有效地解决了这个问题。

3.Question Number 3: Unique Binary Search Trees

Given n, how many structurally unique BST’s (binary search trees) that store values 1…n? Note: Given n = 3, there are a total of 5 unique BST’s:

问题:唯一的二叉搜索树(Unique Binary Search Trees)

给定一个整数n,计算存储值1到n的结构上唯一的二叉搜索树(BST)的数量。

1. 算法描述
  • 自然语言描述:使用动态规划来解决这个问题。对于给定的n,我们可以将每个数i(1 到 n)作为根节点,然后左子树由小于i的数构成,右子树由大于i的数构成。对于每个i,其唯一的BST数量等于左子树的BST数量乘以右子树的BST数量。我们可以计算从1到n的所有可能性,并将它们相加得到总数。
  • 伪代码:
unique_bst(n):
    # 如果n小于等于1,直接返回1,因为没有或只有一个节点时只有一种BST
    if n <= 1:
        return 1

    # 初始化动态规划数组,长度为n+1,初始值都设为0
    dp = [0] * (n + 1)

    # dp[0]和dp[1]都是1,因为没有节点或只有一个节点时只有一种情况
    dp[0] = 1
    dp[1] = 1

    # 从2到n遍历,计算每个数i的唯一BST数量
    for i in range(2, n + 1):
        # 遍历所有可能的根节点j
        for j in range(1, i + 1):
            # dp[i]是以j为根节点的BST数量,等于左子树的BST数量乘以右子树的BST数量
            # dp[j-1]是左子树的BST数量,dp[i-j]是右子树的BST数量
            dp[i] += dp[j - 1] * dp[i - j]

    # 返回存储值1到n的唯一BST的总数量
    return dp[n]
2. 最优子结构和DP方程
  • 最优子结构:一个结构上唯一的BST的数量可以由其左右子树的唯一BST数量决定。
  • DP方程dp[i] += dp[j - 1] * dp[i - j],其中dp[i]是存储值1到i的唯一BST的数量,j是根节点的值。
3. 算法正确性证明
  • 由二叉搜索树的性质,任何一个节点将树分为左右两个子树,且左子树的所有值都小于根,右子树的所有值都大于根。
  • 对于根节点j,其左子树由`[1,

j-1]的数构成,右子树由[j+1, i]`的数构成。

  • 左子树的唯一BST数量为dp[j-1],右子树的唯一BST数量为dp[i-j]。因此,以j为根节点的唯一BST的总数为dp[j-1] * dp[i-j]
  • 通过累加每个可能的根节点j的唯一BST数量,我们得到了dp[i]的值。
4. 算法复杂度分析
  • 时间复杂度:O(n

^2),其中n是给定的数字。这是因为我们需要两层循环来计算每个数i(从2到n)的唯一BST数量,内层循环对于每个i遍历从1到i的所有可能的根节点j

  • 空间复杂度:O(n),用于存储动态规划数组dp,其中dp[i]表示存储值1到i的唯一BST的数量。

总体而言,这个动态规划算法通过计算较小问题的解来有效地构建更大问题的解,从而避免了重复的计算工作,并能够准确地计算出给定n的唯一二叉搜索树的数量。

4.Question Number 4

Largest Divisible Subset
Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of elements in this subset satisfies: Si%Sj = 0 or Sj%Si = 0. Please return the largest size of the subset.
Note: Si%Sj = 0 means that Si is divisible by Sj.

问题:最大可整除子集(Largest Divisible Subset)

给定一组不同的正整数,找出最大的子集,使得该子集中的每一对元素(Si, Sj)满足:Si%Sj = 0或Sj%Si = 0。返回这个子集的最大大小。

1. 算法描述
  • 自然语言描述:首先,将数组排序。排序后,任何两个相邻的元素,较小的数能整除较大的数的可能性更大。然后,使用动态规划来找到最大可整除子集。对于每个元素,检查它能整除哪些之前的元素,并更新它能构成的最大子集的大小。最后,返回所有元素中最大子集的大小。

  • 伪代码:

    largest_divisible_subset(nums):
        if not nums:
            return 0
    
        # 对数组进行排序
        nums.sort()
    
        # 初始化dp数组,每个位置表示以当前元素结尾的最大子集大小
        dp = [1 for _ in nums]
    
        # 遍历数组,更新每个位置的最大子集大小
        for i in range(1, len(nums)):
            for j in range(i):
                if nums[i] % nums[j] == 0:
                    dp[i] = max(dp[i], dp[j] + 1)
    
        # 返回最大的子集大小
        return max(dp)
    
2. 最优子结构和DP方程
  • 最优子结构:对于数组中的每个元素nums[i],它能构成的最大可整除子集取决于它之前的元素nums[j](j < i)所能构成的最大子集,并且nums[i]能整除nums[j]
  • DP方程dp[i] = max(dp[i], dp[j] + 1),其中j < inums[i] % nums[j] == 0
3. 算法正确性证明
  • 通过对数组进行排序,我们确保了如果nums[j]能整除nums[i],那么对于任何k < jnums[k]也能整除nums[i]
  • 使用动态规划,我们考虑了所有可能的元素对,并更新了以每个元素为结尾的最大子集的大小,确保了考虑了所有可能的组合。
4. 算法复杂度分析
  • 时间复杂度:O(n^2),其中n是数组的长度。这是因为有两层嵌套循环,每层循环都遍历整个数组。
  • 空间复杂度:O(n),用于存储dp数组,其中dp[i]表示以第i个元素结尾的最大子集的大小。

5.Question Number 5

You are given an integer array nums and an integer target.
You want to build an expression out of nums by adding one of the symbols ’+’ and ’-’ before each integer in nums and then concatenate all the integers.
For example, if nums = [2, 1], you can add a ’+’ before 2 and a ’-’ before 1 and concatenate them to build the expression ”+2-1”.
Return the number of different expressions that you can build, which evaluates to target. Example:

Input: nums = [1,1,1,1,1], target = 3

Output: 5

Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.

-1 + 1 + 1 + 1 + 1 = 3

+1 - 1 + 1 + 1 + 1 = 3

+1 + 1 - 1 + 1 + 1 = 3

+1 + 1 + 1 - 1 + 1 = 3

+1 + 1 + 1 + 1 - 1 = 3

问题:构建表达式以达到目标和

给定一个整数数组nums和一个整数目标target。你想通过在nums中的每个整数前添加符号’+‘或’-',然后将所有整数串联起来来构建一个表达式。返回你可以构建的、计算结果为target的不同表达式的数量。

1. 算法描述
  • 自然语言描述:这个问题可以通过动态规划解决。我们可以将问题转换为子集求和问题。计算数组中元素能组成的所有可能的和,并统计其中等于target的数量。对于数组中的每个元素,我们可以选择加上它或减去它。我们使用一个字典来存储中间结果,键为可能的和,值为达到这个和的方法数量。

  • 伪代码:

    find_target_sum_ways(nums, target):
        dp = {0: 1}  # 初始化,和为0的方法有1种
    
        # 遍历数组中的每个元素
        for num in nums:
            temp = {}
            # 遍历当前可能的和及其对应的方法数
            for sum in dp:
                # 计算加上或减去当前元素后的和
                temp[sum + num] = temp.get(sum + num, 0) + dp[sum]
                temp[sum - num] = temp.get(sum - num, 0) + dp[sum]
            dp = temp
    
        # 返回达到目标和的方法数
        return dp.get(target, 0)
    
2. 最优子结构和DP方程
  • 最优子结构:对于每个元素,它可以加上或减去,这影响了达到最终目标和的方法数。
  • DP方程dp[sum + num] += dp[sum]dp[sum - num] += dp[sum],这里dp[sum]是在没有考虑当前元素时达到和为sum的方法数。
3. 算法正确性证明
  • 算法基于这样一个事实:每添加一个元素,都会基于前一个状态的和创建新的和。即对于每个元素,我们都在之前所有可能和的基础上加上或减去这个元素。
  • 由于每个元素都独立考虑加和减的情况,所有可能的和都会被考虑到。因此,最终得到的和为target的方法数就是所有可能性的总和。
4. 算法复杂度分析
  • 时间复杂度:O(n * m),其中n是数组nums的长度,m是所有可能的和的数量。对于每个元素,我们都需要遍历目前所有可能的和并更新它们,所以时间复杂度取决于这两个因素的乘积。
  • 空间复杂度:O(m),其中m是所有可能的和的数量。我们需要一个字典来存储每种可能和的计数,其空间复杂度与所有可能的和的数量成正比。

综上所述,这个动态规划算法通过考虑数组中每个元素对可能和的影响,有效地找到了达到目标和target的所有可能方法的数量。文章来源地址https://www.toymoban.com/news/detail-834961.html

到了这里,关于算法设计与分析-Dynamic Programming「国科大」卜东波老师的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 动态规划Dynamic Programming

     上篇文章我们简单入门了动态规划(一般都是简单的上楼梯,分析数据等问题)点我跳转,今天给大家带来的是路径问题,相对于上一篇在一维中摸爬滚打,这次就要上升到二维解决问题,但都用的是动态规划思想嘛,所以大差不差,且听我慢慢道来。 还是用一样的方法,

    2024年03月27日
    浏览(35)
  • 动态规划(Dynamic Programming)详解

    引言: 动态规划(Dynamic Programming,简称DP)是计算机科学与数学领域中的一个经典算法设计策略,用于解决具有重叠子问题和最优子结构特性的复杂问题。它通过将问题分解为更小的子问题来避免重复计算,从而提高效率。本文旨在详细介绍动态规划的基本概念、原理、实现

    2024年04月13日
    浏览(26)
  • 【Chapter 5】Dynamic Programming(上)

    考前最后一节课明确提到这一部分会考矩阵链乘问题(Matrix Chain)或是最长公共子序列问题(Longest Common Subsequence, LCS),考察的形式是填写DP的Table,因此以blog的方式对复习的过程进行记录,并查缺补漏。 问题描述: 给定 n n n 个矩阵的序列 A 1 , A 2 , . . . , A n A_1, A_2, ..., A_

    2024年02月03日
    浏览(36)
  • 浅析动态规划(Dynamic Programming,DP)

    动态规划可以理解为递归,只不过递归是通过函数实现,动态规划通过循环实现! 动态规划有多好用我就不过多介绍,写这篇文章的时候我也不是熟练掌握,只是单纯记录一下我的学习经历并分享一些我的心得体会,仅此而已。 推荐看一下这个视频,对你的理解应该会有所

    2024年04月27日
    浏览(25)
  • 【数据结构】动态规划(Dynamic Programming)

    求解决策过程(decision process)最优化的数学方法。 将多阶段决策过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。 与分治法类似,将待求解问题 分解成若干个子问题 。 但是经分解得到的子问题往往 不是相互独立 的。 如果使用分治法求解问题,有些子问

    2024年02月03日
    浏览(35)
  • Speeding Up Dynamic Programming Computation: Tips and

    作者:禅与计算机程序设计艺术 动态规划(Dynamic programming)是一种解决最优化问题的关键算法。它通过将子问题的解重复计算而节省时间。对于多种问题都可以用动态规划求解。动态规划算法经过几十年的发展,已经成为计算机科学中一个重要的研究领域。然而,如何高效地实

    2024年02月07日
    浏览(82)
  • Dynamic-Programming(动态规划)最细解题思路+代码详解(1)

    案例二:二维数组的 DP 我做了几十道 DP 的算法题,可以说,80% 的题,都是要用二维数组的,所以下面的题主要以二维数组为主,当然有人可能会说,要用一维还是二维,我怎么知道?这个问题不大,接着往下看。 问题描述 一个机器人位于一个 m x n 网格的左上角 (起始点在

    2024年04月26日
    浏览(27)
  • 国科大《自然语言处理》复习(宗成庆老师)

    NLP的主要挑战: 1、歧义(词法、词性、结构、语义、语音…) 2、大量未知语言现象(新词、人名、地名、术语、新含义、新用法…) 3、语义表示和计算困难(知识表示复杂性高) 4、始终面临数据不充分… 三大语系: 屈折语:(fusional language)词的形态变化表示语法关系

    2024年01月16日
    浏览(26)
  • Policy Iteration Adaptive Dynamic Programming Algorithm for Discrete-Time Nonlinear Systems

    本文是第一次对离散非线性系统采用策略迭代的方法分析收敛性和稳定性。反复实验获得 初始的可容许控制策略 ,迭代值函数是单调不增,收敛到HJB方程的最优值。证明任意迭代控制策略使非线性系统稳定。神经网络近似值函数和求最优控制,且分析权重矩阵的收敛性。 根

    2024年03月22日
    浏览(34)
  • Dynamic-Programming(动态规划)最细解题思路+代码详解,Android布局优化之include、merge、ViewStub的使用

    dp[0…m-1] [0] = 1; // 相当于最左面一列,机器人只能一直往下走 撸代码 三个步骤都写出来了,直接看代码 public static int uniquePaths(int m, int n) { if (m = 0 || n = 0) { return 0; } int[][] dp = new int[m][n]; // // 初始化 for(int i = 0; i m; i++){ dp[i][0] = 1; } for(int i = 0; i n; i++){ dp[0][i] = 1; } // 推导出 d

    2024年04月26日
    浏览(30)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包