动态规划-解决优化问题的算法精髓

本文详细介绍了动态规划算法的概念、特点,以及解题的四个步骤,以最长上升子序列为例,提供了一个C++代码示例,帮助读者理解和应用动态规划解决实际问题。
摘要由CSDN通过智能技术生成

动态规划:解决优化问题的算法精髓

动态规划是一种强大的算法范式,在解决各类优化问题时发挥着重要作用。与其他算法不同,动态规划通过将复杂问题分解成更小的子问题,并存储这些子问题的解来避免重复计算,从而达到高效解决问题的目的。在本篇博客中,我们将深入探讨动态规划问题的解题方法和步骤,帮助大家掌握这一重要的算法思想。

1. 什么是动态规划?

动态规划(Dynamic Programming,DP)是一种解决最优化问题的算法方法。它的核心思想是将复杂的问题分解成更小的子问题,并通过有效地组合这些子问题的解来得到原问题的解。

动态规划问题通常具有两个重要特点:

  1. 重叠子问题: 问题的递归解会反复求解相同的子问题。动态规划通过记忆化或自底向上的方式避免了重复计算。

  2. 最优子结构: 问题的最优解可以由其子问题的最优解组合而成。这意味着我们可以通过解决子问题来构建原问题的最优解。

理解这两个特点是掌握动态规划的关键。

2. 动态规划问题的解题步骤

解决动态规划问题通常可以分为以下四个步骤:

步骤 1: 定义子问题

首先,我们需要定义子问题。这通常涉及将原问题分解成更小、更易于管理的部分。例如,在计算斐波那契数列的问题中,子问题可以是计算序列中的前一个和前两个数字。

步骤 2: 确定状态表示

状态是动态规划中的核心概念。一个状态代表了达到某个子问题解时的情况。我们需要找到一种方式来描述问题在给定步骤的状态,这通常涉及创建一个或多个数组来存储子问题的解。在斐波那契数列的例子中,状态可以通过一个数组dp[i]表示,其中dp[i]存储第i个斐波那契数。

步骤 3: 确定状态转移方程

状态转移方程(也称为DP方程)描述了如何从一个状态转移到另一个状态。它是解动态规划问题的核心,我们需要准确地定义如何从已解决的子问题得到当前问题的解。在斐波那契数列的例子中,状态转移方程是dp[i] = dp[i-1] + dp[i-2]

步骤 4: 解决问题

最后,我们需要根据定义的子问题、状态表示和状态转移方程来解决原问题。这通常涉及初始化状态数组的边界值,然后按顺序(自底向上)或递归地(自顶向下,通常需要记忆化)计算所有状态的值。对于斐波那契数列,我们需要初始化dp[0]dp[1],然后计算dp[2]dp[n]

3. 例子: 最长上升子序列

让我们通过最长上升子序列(LIS)问题来具体看看如何应用上述步骤。

问题描述: 给定一个未排序的整数数组,找到最长上升子序列的长度。

步骤 1: 定义子问题

对于数组nums中的每个元素nums[i],考虑以nums[i]结尾的最长上升子序列。

步骤 2: 确定状态表示

定义状态dp[i]表示以nums[i]结尾的最长上升子序列的长度。

步骤 3: 确定状态转移方程

对于状态dp[i],考虑所有j < inums[j] < nums[i]的位置。状态转移方程为:

dp[i] = max(dp[j]) + 1, for all j < i and nums[j] < nums[i]

步骤 4: 解决问题

首先,我们需要初始化所有dp[i]为1,因为每个元素至少可以自己构成一个长度为1的上升子序列。然后,我们遍历数组nums,对于每个i,计算以nums[i]结尾的最长上升子序列长度,并更新dp[i]。最后,我们返回dp数组中的最大值,即为最长上升子序列的长度。

代码示例

#include <iostream>
#include <vector>
#include <algorithm>

int lengthOfLIS(const std::vector<int>& nums) {
    if (nums.empty()) return 0;

    std::vector<int> dp(nums.size(), 1);  // dp 数组初始化为 1
    int max_length = 1;  // 最长上升子序列的最小长度为 1

    for (size_t i = 0; i < nums.size(); ++i) {
        for (size_t j = 0; j < i; ++j) {
            if (nums[j] < nums[i]) {
                dp[i] = std::max(dp[i], dp[j] + 1);
            }
        }
        max_length = std::max(max_length, dp[i]);
    }

    return max_length;
}

int main() {
    std::vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
    std::cout << "Length of LIS is " << lengthOfLIS(nums) << std::endl;  // 应输出 4
    return 0;
}

代码详解

在这段 C++ 代码中,我们定义了一个名为 lengthOfLIS 的函数,它接收一个整数型向量 nums 作为参数,并返回其中最长上升子序列的长度。我们使用一个名为 dp 的整数型向量来存储状态,其中 dp[i] 表示以 nums[i] 结尾的最长上升子序列的长度。我们还定义了一个名为 max_length 的变量来持续跟踪遇到的最长上升子序列的长度。

在函数体中,我们首先检查输入的向量是否为空。如果为空,我们直接返回 0。否则,我们初始化 dp 向量的所有元素为 1,并开始两层循环来填充 dp 向量。内层循环用于找到所有小于 nums[i] 的 nums[j],并更新 dp[i]。每次更新后,我们还会更新 max_length。

最后,函数返回 max_length 作为最终结果。在 main 函数中,我们创建了一个示例向量 nums,并调用 lengthOfLIS 函数来计算并输出最长上升子序列的长度

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

排骨炖粉条

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值