【算法】一维动态规划

目录

一、DP的概念

二、重复子问题(自顶向下与记忆化)

三、最优子结构(自底向上与制表递推)

四、例题(最低票价问题)

动态规划(DP)是一种算法技术,它将大问题分解为更简单的子问题,对整体问题的最优解决方案取决于子问题的最优解决方案。常用于求解计数问题(求方案数)和最值问题(最大价值、最小花费)等

一、DP的概念

常用于解决重叠子问题、最优子结构特征的问题

int fib(int n) {
	if (n == 1 || n == 2)
		return 1;
	return (fib(n - 1) + fib(n - 2));
}

在递归的过程中有重复的子过程被多次计算(计算fib(4)时展开为fib(2)+fib(3),再把fib(3)展开为fib(1)和fib(2),此时fib(2)被重复计算两次)

用DP可以高效处理的问题具有两个特征:重复子问题、最优子结构

二、重复子问题(自顶向下与记忆化)

一个子问题重复计算,消耗了大量时间,用DP解决重复子问题,使每个子问题只计算一次(在追求时间高效率的同时,可能消耗更多的空间)

#include <iostream>
#include <vector>
using namespace std;

// 动态规划计算斐波那契数列的第n项
int fib2(int n) {
    vector<int> dp(n + 1, -1); // 创建一个长度为n+1的向量,并初始化所有元素为-1
    return f2(n, dp); // 调用f2函数计算第n项
}

// 递归计算斐波那契数列的第i项,使用动态规划存储已计算的项
int f2(int i, vector<int>& dp) {
    if (i == 0) {
        return 0; // 斐波那契数列的第0项是0
    }
    if (i == 1) {
        return 1; // 斐波那契数列的第1项是1
    }
    if (dp[i] != -1) { // 如果已经计算过第i项,则直接返回结果
        return dp[i];
    }
    int ans = f2(i - 1, dp) + f2(i - 2, dp); // 递归计算第i-1项和第i-2项的和
    dp[i] = ans; // 存储计算结果
    return ans; // 返回第i项的斐波那契数
}

int main() {
    int n;
    cout << "请输入斐波那契数列的项数:";
    cin >> n;
    cout << "斐波那契数列的第" << n << "项为:" << fib2(n) << endl;
    return 0;
}

三、最优子结构(自底向上与制表递推)

大问题的最优解包含小问题的最优解,可以通过小问题的最优解推导出大问题的最优解

#include <iostream>
using namespace std;

// 计算斐波那契数列的第n项
int fib3(int n) {
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }

    vector<int> dp(n + 1);
    dp[1] = 1; 
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

int main() {
    int n;
    cout << "请输入斐波那契数列的项数:";
    cin >> n;
    cout << "斐波那契数列的第" << n << "项为:" << fib3(n) << endl;
    return 0;
}

任何动态规划问题都一定对应一个有重复行为的递归(从递归入手,逐渐实现动态规划的方法)

四、例题(最低票价问题)

暴力求解

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

using namespace std;

int minCostTickets(vector<int>& days, vector<int>& costs) {
    vector<int> dp(days.size(), 0);
    vector<int> travelDays(days.size(), 0);
    int day = 1;
    for (int i = 0; i < days.size(); ++i) {
        travelDays[i] = days[i];
        if (days[i] == day) {
            travelDays[i] = day++;
        }
    }

    for (int i = 1; i < travelDays.size(); ++i) {
        dp[i] = INT_MAX;
        for (int k = 0; k < 3; ++k) {
            int j = i;
            // Find the last day we can travel with the current pass
            while (j < travelDays.size() && travelDays[j] - travelDays[i] <= (k == 2 ? 30 : k == 1 ? 7 : 1)) {
                j++;
            }
            j--; // The last day we can travel with the current pass
            dp[i] = min(dp[i], costs[k] + (j < i ? 0 : dp[j]));
        }
    }

    return dp[travelDays.size() - 1];
}

int main() {
    vector<int> days = {1, 4, 6, 7, 8, 20};
    vector<int> costs = {2, 7, 15};
    cout << "The minimum cost is: " << minCostTickets(days, costs) << endl;
    return 0;
}

自顶向下

#include <iostream>
#include <vector>
#include <unordered_map>
#include <climits>

using namespace std;

int minCostTickets(vector<int>& days, vector<int>& costs) {
    unordered_map<int, int> memo; // 用于记忆化存储
    return dp(days, costs, 0, memo);
}

int dp(const vector<int>& days, const vector<int>& costs, int i, unordered_map<int, int>& memo) {
    if (i == days.size()) {
        return 0; // 没有更多旅行日,不需要成本
    }
    if (memo.find(i) != memo.end()) {
        return memo[i]; // 如果已经计算过,直接返回结果
    }

    int index = upper_bound(days.begin(), days.end(), days[i]) - days.begin(); // 找到下一个旅行日
    int ans = INT_MAX;
    for (int k = 0; k < 3; ++k) {
        int daysCovered = (k == 0) ? 1 : (k == 1) ? 7 : 30; // 计算通行证覆盖的天数
        int j = i;
        while (j < index && days[j] + daysCovered > days[i]) {
            j++;
        }
        ans = min(ans, costs[k] + dp(days, costs, j, memo));
    }

    memo[i] = ans; // 存储计算结果
    return ans;
}

int main() {
    vector<int> days = {1, 4, 6, 7, 8, 20};
    vector<int> costs = {2, 7, 15};
    cout << "The minimum cost is: " << minCostTickets(days, costs) << endl;
    return 0;
}

自底向上

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

using namespace std;

int minCostTickets(vector<int>& days, vector<int>& costs) {
    vector<int> dp(days.size(), 0); // dp[i] 表示在第 i 天旅行的最低成本
    vector<int> dayIndex; // 存储每个旅行日的索引
    for (int i = 0; i < days.size(); ++i) {
        dayIndex.push_back(i);
    }
    sort(dayIndex.begin(), dayIndex.end(), [&](int a, int b) { return days[a] < days[b]; });

    // 初始化dp数组,将所有旅行日的最低成本设置为一个较大的数
    for (int i = 0; i < days.size(); ++i) {
        dp[i] = INT_MAX;
    }

    // 从最后一天开始向前计算
    for (int i = 0; i < days.size(); ++i) {
        int day = days[dayIndex[i]];
        // 如果是第一天,成本为0
        if (day == 1) {
            dp[dayIndex[i]] = 0;
            continue;
        }

        // 尝试1天、7天和30天的通行证
        for (int k = 0; k < 3; ++k) {
            int prevDay = day - (k == 0 ? 1 : (k == 1 ? 7 : 30));
            if (prevDay <= 0) {
                prevDay = 1; // 确保不会越界
            }
            int prevIndex = find(days.begin(), days.end(), prevDay) - days.begin();
            dp[dayIndex[i]] = min(dp[dayIndex[i]], costs[k] + dp[prevIndex]);
        }
    }

    // 返回第一天的最低成本
    return dp[dayIndex[0]];
}

int main() {
    vector<int> days = {1, 4, 6, 7, 8, 20};
    vector<int> costs = {2, 7, 15};
    cout << "The minimum cost is: " << minCostTickets(days, costs) << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值