【题目记录】dp

目录

01背包问题

跳跃游戏

最长递增子序列

最长公共子序列

最长公共子串

最长连续序列

完全平方数

最低票价


01背包问题

有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。

接下来有 N行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

用一个二维数组记录第i件物品在容量为v的背包下能装下的最大价值(每行)(子问题是前i-1件物品在背包容量为v的情况下的最大价值,已经记录在表格里了)

f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]]+w[i])

/*
状态转移方程:表示考虑i件物品,背包容量为j的情况下,所能装下的最大价值
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
*/

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

#define MAX 1001
int f[MAX][MAX];
int main(void){
    
    int N,V;
    vector<int> v(MAX,0),w(MAX,0);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>v[i]>>w[i];
        
    for(int i=1;i<=N;i++)           //共i件物品
        for(int j=0;j<=V;j++){       //共j容量
            f[i][j]=f[i-1][j];
            if(v[i]<=j)              //走到第i件时,若此时背包还装的下这第i件物品
                f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
        }
    
    int res=0;
    for(int k=0;k<=V;k++)
        res=max(res,f[N][k]);
        
    cout<<res<<endl;
    return 0;
}

//或者
    for(int i=1;i<=N;i++)           //共i件物品
        for(int j=V;j>=0;j--){       //共j容量
            if(v[i]<=j)              //走到第i件时,若此时背包还装的下这第i件物品
                f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
            else
                f[i][j]=f[i-1][j];
        }
    return f[N][V];

优化空间:

使用一维数组,因为每次计算f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]]+w[i]) 时需要用到f[i-1][j]的情况,即上一行的计算结果,因此在这里j的遍历方向应该是从后往前进行的,这样才能保证用到的f[j-v[i]]是上一轮计算的结果。

/*
状态转移方程:表示考虑i件物品,背包容量为j的情况下,所能装下的最大价值
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
*/

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

#define MAX 1001
int f[MAX];
int main(void){
    
    int N,V;
    vector<int> v(MAX,0),w(MAX,0);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>v[i]>>w[i];
        
    for(int i=1;i<=N;i++)           //共i件物品
        for(int j=V;j>=v[i];j--)      //共j容量     j>=v[i]实际上就是把装得下这个判断条件放到for里面去了  //走到第i件时,若此时背包还装的下这第i件物品
                f[j]=max(f[j],f[j-v[i]]+w[i]);
        
        
    cout<<f[V]<<endl;
    return 0;
}

 

 

跳跃游戏

链接:https://www.nowcoder.com/questionTerminal/b7d9d79453bf43bf9594e91d24260605
来源:牛客网
 

给定数组arr,arr[i]==k代表可以从位置向右跳1~k个距离。比如,arr[2]==3,代表可以从位置2跳到位置3、位置4或位置5。如果从位置0出发,返回最少跳几次能跳到arr最后的位置上。

 

输入描述:

输入包括两行,第一行一个整数n(1≤n≤1e5)(1 \leq n \leq 1e5 )(1≤n≤1e5),代表arr数组长度,第二行n个整数代表数组arr[i](1≤arr[i]≤n)(1 \leq arr[i] \leq n)(1≤arr[i]≤n)。

输出描述:

输出一个整数,代表最少跳的次数。

解法1:暴力递归

每次走到第k位时,先看看能否跳到终点( k+a[k]>=n-1 ),若可以,直接返回step+1

若不行,则递归查看从当前位置出发到达终点的最少步数是多少,代码如下

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

int baoli(vector<int>& a, int k, int step) {
	if (k + a[k] >= a.size()-1) {
		return step + 1;
	}
	int ans = a.size();
	for (int i = 1; i <= a[k]; i++) {
		ans = min(ans, baoli(a, k + i, step + 1));
	}
	return ans;
}

int main() {
	int n;
	cin >> n;
	vector<int> a(n, 0);
	for (int i = 0; i < n; i++)
		cin >> a[i];

	int ans = baoli(a, 0, 0);
	cout << ans << endl;

	system("pause");
	return 0;
}

解法2:

暴力递归超时了

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin>>n;
    vector<int>arr(n);
    for(int i=0;i<n;i++)
        cin>>arr[i];
    int jm = 0, cur = 0, next = 0;
    for(int i=0;i<n;i++)
    {
        if(cur<i){
            jm++;
            cur=next;
        }
        next=max(next,i+arr[i]);
    }
    cout<<jm<<endl;
    return 0;
}

 

最长递增子序列

遍历到索引是 i 的数的时候,根据“状态”的定义,考虑把 i 之前的所有的数都看一遍,只要当前的数 nums[i] 严格大于之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列。因此,dp[i] 就是之前严格小于 nums[i] 的“状态”最大值加 1。

因此,状态转移方程是:

dp[i] = max{1 + dp[j] for j < i if nums[j] < nums[i]}

    int lengthOfLIS(vector<int>& nums) {
        int len=nums.size();
        vector<int> dp(len,1);
        
        for(int i=1;i<len;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        int ans=0;
        for(int i=0;i<len;i++)
            ans=max(ans,dp[i]);
        return ans;
    }

 

最长公共子序列

dp[i][j]表示text1[0~i] 与 text2[0~j] 的最长公共子序列长度

因此,若是text1的某一位与text2[0]相同,那么第一列的下方格子都标成1

同理,若是text2的某一位与text1[0]相同,那么第一行的右侧格子都标成1

dp[i][j]的取值在三个值里面取最大的 dp[i-1][j] , dp[i][j-1] 或者 当text1[i] == text2[j] 时 dp[i-1][j-1] + 1

    int longestCommonSubsequence(string text1, string text2) {
        int len1 = text1.size();
        int len2 = text2.size();
        vector<vector<int>>dp(len1,vector<int>(len2,0));
        dp[0][0] = text1[0] == text2[0] ? 1:0;
        for(int i=1;i<len1;i++){
            dp[i][0] = max(dp[i-1][0], text1[i] == text2[0] ? 1 : 0);
        }
        for(int j = 1; j<len2 ;j++){
            dp[0][j] = max(dp[0][j-1], text1[0] == text2[j] ? 1 : 0);
        }
        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                if(text1[i] == text2[j]){
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
                }
            }
        }
        return dp[len1-1][len2-1];
    }

 

 

最长公共子串

dp[i][j] 表示以s1[i] 和 s2[j] 作为公共子串最后一个字符的情况下,公共子串最长能有多长

dp[i][j] 的取值只有 0:s1[i]!=s2[j]  或者 dp[i-1][j-1] + 1:s1[i] == s2[j]

    int longestCommonStr(string s1, string s2) {
        int len1 = s1.size();
        int len2 = s2.size();
        vector<vector<int>>dp(len1,vector<int>(len2,0));
        dp[0][0] = s1[0] == s2[0] ? 1:0;
        for(int i=1;i<len1;i++){
            dp[i][0] = s1[i] == s2[0] ? 1 : 0;
        }
        for(int j = 1; j<len2 ;j++){
            dp[0][j] = s1[0] == s2[j] ? 1 : 0;
        }
        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(s1[i] == s2[j]){
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
                }
            }
        }
        return dp[len1-1][len2-1];
    }

 

最长连续序列

给定一个未排序的整数数组,找出最长连续序列的长度。

要求算法的时间复杂度为 O(n)。

示例:

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

解法1:先将数组排序,然后逐个位置查看其所在连续序列的长度,若他后面没有连续序列,则长度为1

比较丑的代码

    int longestConsecutive(vector<int>& nums) {
        int len = nums.size();
        if(len<=0)
            return 0;
        sort(nums.begin(),nums.end());
        int ans=0;
        int count=0;
        for(int i=0;i<len;i++){
            count=1;
            int index = i + 1;
            while(judge(index,nums)){
                count++;
                index++;
            }
            ans = max(ans,count);
        }
        return ans;
    }
    
    bool judge(int& index,vector<int>& nums){
        while(index < nums.size() && nums[index] == nums[index-1])    //重复元素
            index++;
        if(index < nums.size() && (nums[index] == nums[index-1] + 1))
            return true;
        return false;
    }

比较美的代码:

    int longestConsecutive(vector<int>& nums) {
        int size = nums.size();
        if(size<=0)
            return 0;
        int ans=1;
        sort(nums.begin(),nums.end());
        for(int i = 1, len = 1;i < size; i++){
            if(nums[i] == nums[i-1] + 1)
                len++;
            else if(nums[i] == nums[i-1])
                continue;
            else
                len = 1;
            ans = max(ans,len);
        }
        return ans;
    }

解法2:用hash,时间复杂度O(n)

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if(nums.empty()){
            return 0;
        }
        unordered_set<int> myset(nums.begin(), nums.end());
        int res = 0;
        
        for(auto num : nums){
            if(myset.count(num-1)==0){
                int x = num + 1;
                while(myset.count(x)){
                    x ++;
                }
                res = max(res, x-num);
            }
        }
        return res;
    }
};

 

完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.
示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

先设dp数组每个元素为INT_MAX,在后续每次遍历时不断更新dp数组。

如i=4时,先看square_nums数组中的元素1,可有dp[4] = min(dp[4],dp[4-1]+1) = min(INT_MAX, 1 +1)=  2,然后再看square_nums数组中的元素4,可有dp[4] = min(dp[4],dp[4-4]+1) = min(2,0+1)=  1。不断迭代更新的。

dp[i] = min(dp[i],dp[i-square_nums[j]]+1);

    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0] = 0;
        int square = sqrt(n);
        vector<int> square_nums(square+1,0);
        for(int i = 1;i<=square;i++){
            square_nums[i] = i * i;
        }
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=square;j++){
                if(i<square_nums[j])
                    break;
                dp[i] = min(dp[i],dp[i-square_nums[j]]+1);
            }
        }
        return dp[n];
    }

用bfs解:

BFS 的话,我们可以一层一层的算。第一层依次减去一个平方数得到第二层,第二层依次减去一个平方数得到第三层。直到某一层出现了 0,此时的层数就是我们要找到平方数和的最小个数。

举个例子,n = 12,每层的话每个节点依次减 1, 4, 9...。如下图,灰色表示当前层重复的节点,不需要处理。

 

  如上图,当出现 0 的时候遍历就可以停止,此时是第 3 层(从 0 计数),所以最终答案就是 3 。实现的话当然离不开队列,此外我们需要一个 set 来记录重复的解。

    int numSquares(int n) {
        //bfs
        if(n<=0)
            return 0;
        queue<int> q;
        set<int> s;
        q.push(n);
        int level = 0;
        while(!q.empty()){
            int _size = q.size();
            level++;
            for(int i = 0;i<_size;i++){
                int cur = q.front();
                q.pop();
                for(int j = 1; j*j <= cur; j++){
                    int next = cur - j*j;
                    if(next == 0){        //本层出现了答案
                        return level;
                    }

                    if(s.count(next) == 0){
                        s.insert(next);
                        q.push(next);
                    }
                }
            }
        }
        return level;
    }

 

最低票价

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

  • 一张为期一天的通行证售价为 costs[0] 美元;
  • 一张为期七天的通行证售价为 costs[1] 美元;
  • 一张为期三十天的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

 

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 
你总共花了 $17,并完成了你计划的每一天旅行。

 

提示:

  1. 1 <= days.length <= 365
  2. 1 <= days[i] <= 365
  3. days 按顺序严格递增
  4. costs.length == 3
  5. 1 <= costs[i] <= 1000
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int days_len = days.size();
        vector<int> dp(days[days_len-1] + 1,0);     //长度是出行的天数
        for(int day : days){
            dp[day] = INT_MAX;              //标记一下出行的日子
        }

        for(int i = 1;i<dp.size();i++){
            if(dp[i] == 0){             //今天不出行,花的钱与前一天一样
                dp[i] = dp[i-1];
                continue;
            }

            int m1 = dp[i-1] + costs[0];            //今天出行,花钱买一天票的情况
            int m2 = i > 7 ? dp[i-7] + costs[1] : costs[1]; //今天出行,7天前就买了一张7天票的情况
            int m3 = i > 30 ? dp[i-30] + costs[2] : costs[2];   //同理,30天
            dp[i] = min(m1,min(m2,m3));                     //看哪种情况花钱最少
        }
        return dp.back();
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值