Leetcode 2140. 解决智力问题

题目链接

第276场周赛第三题
https://leetcode-cn.com/problems/solving-questions-with-brainpower/

题目内容

给你一个下标从0开始的二维整数数组questions,其中questions[i] = [pointsi, brainpoweri]

这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题i将让你 获得pointsi的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri 个问题)。如果你跳过问题i,你可以对下一个问题决定使用哪种操作。

比方说,给你questions = [[3, 2], [4, 3], [4, 4], [2, 5]]
如果问题0被解决了, 那么你可以获得 3分,但你不能解决问题 12
如果你跳过问题 0 ,且解决问题1,你将获得4分但是不能解决问题23
请你返回这场考试里你能获得的 最高 分数。

解决思路

裸dfs(超时)

纯dfs写法就是设置边界是题目号超过了题目数返回,否则,在第i题时,要不跳过这道题,进入下一题;要不就做这道题,跳过questions[i][1]题。

class Solution {
public:
    int ans;
    Solution()
    {
        ans=0;
    }
    void dfs(vector<vector<int>>& questions,int step,int score)
    {
        if(step>=questions.size())//当前题目号超过了题目数,就表示已经枚举完一种做题方式,查询此种方案的答案
        {
            if(score>ans)
            {
                ans=score;
                return;
            }
        }
        else
        {
            dfs(questions,step+questions[step][1]+1,score+questions[step][0]);//做当前的题目
            dfs(questions,step+1,score);//跳过这道题目
        }
    }
    long long mostPoints(vector<vector<int>>& questions) {
        dfs(questions,0,0);
        return ans;
    }
};

这种写法,会在第21个测试用例超时,原因是这种递归方式,包含了许多重复的情况计算,因此我们需要对其进行优化。
在这里插入图片描述

记忆化搜索

上述方法包含了大量重复的情况,因此我们采取记忆化搜索的方式,将每一次得到的值进行一个保存,以便于后续dfs的时候直接返回。

typedef long long ll;
class Solution {
public:
    int n;
    vector<ll>dp;
    void dp_init(int n)
    {
        dp.resize(n+1);
    }
    ll dfs(vector<vector<int>>& questions,int i)
    {
        if(i>=n)
            return 0;
        else if(dp[i])
            return dp[i];//如果值得到保存,直接返回,不用往下做重复的递归
        else
            return dp[i]=max(dfs(questions,i+1),dfs(questions,i+questions[i][1]+1)+questions[i][0]);//取两种情况的最大值
    }
    long long mostPoints(vector<vector<int>>& questions) {
        n = questions.size();
        dp_init(n);
        return dfs(questions,0);
    }
};

在这里插入图片描述
用此方法可以通过所有测试用例

动态规划

但是,递归的方法由于涉及到了堆栈的操作,效率相对递推法而言总是会慢一些,因此我们是否能够采用递归(动态规划)的方式进行求解呢?
利用动态规划,我们可以设置dp[i]为解决第i到第n-1个问题的最大分数,而在每种状态的选择当中,我们有或者不选两种状态。因此,当选中时,dp[i]=questions[i][0]+dp[i+questions[i][1]+1];不选时,dp[i]=dp[i+1]。我们只要将这两种答案比较下,取最大值即可。
另外,需要注意的是,由于此时的最优子结构大小是从后往前的顺序的,i越小,dp[i]所包含的结构规模就越大!因此我们遍历的顺序也需要从后往前倒序进行dp填表!

typedef long long ll;
class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {//反向dp
        int n = questions.size();
        vector<ll>dp(n+1);
        int i;
        for(i=n-1;i>=0;i--)
        {
            if(i+ll(questions[i][1])+1>=n)//如果越界了,那就直接返回当前的问题分数即可。
            {
                dp[i] = max(ll(questions[i][0]),dp[i+1]);
            }
            else
            {
                dp[i] = max(questions[i][0]+dp[i+questions[i][1]+1],dp[i+1]);//否则,则比较选或者不选两种情况
            }
        }
        return dp[0];//最开始的解包含了整个规模
    }
};

在这里插入图片描述
由此可见,相比记忆化的方法,时空规模有了一定的减小。

注意点

这道题最重要的点就是反向的动态规划,以往的传统动态规划题都是i0n进行遍历的,而这道题刚好反了过来,这说明我们的遍历顺序,在很大程度上和最优子结构之间的包含覆盖关系有紧密的关联。我们在选择i的遍历方向上,一定要根据子结构的覆盖关系来进行选取,后遍历的结构规模,一般都是大于前面的!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值