【记忆化搜索】【超详细】力扣3186. 施咒的最大总伤害

一个魔法师有许多不同的咒语。

给你一个数组 power ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。

已知魔法师使用伤害值为 power[i] 的咒语时,他们就 不能 使用伤害为 power[i] - 2 ,power[i] - 1 ,power[i] + 1 或者 power[i] + 2 的咒语。

每个咒语最多只能被使用 一次 。

请你返回这个魔法师可以达到的伤害值之和的 最大值 。

示例 1:
输入:power = [1,1,3,4]

输出:6

解释:

可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。

示例 2:
输入:power = [7,1,6,6]

输出:13

解释:

可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。

在这里插入图片描述

代码(超内存,完成度:537/554)

class Solution {
public:long long maximumTotalDamage(vector<int>& power) {int count = 0;unordered_map<int, int> group;for(int num : power){group[num]++;count = max(count, num);}vector<pair<int,int>> freq(count + 1);for (const auto& entry : group) {freq[entry.first] = {entry.first, entry.second};}if (freq.size() == 0) return 0;if (freq.size() == 1) return freq[0].first * freq[0].second;if (freq.size() == 2) return max(freq[0].first * freq[0].second, freq[1].first * freq[1].second);vector<int> dp(freq.size());dp[0] = freq[0].first * freq[0].second;dp[1] = max(dp[0], freq[1].first * freq[1].second);dp[2] = max(dp[1],freq[2].first * freq[2].second);for(int i = 3; i < freq.size();i++){dp[i] = max(dp[i-1], dp[i-3] + freq[i].first * freq[i].second);}return dp[freq.size() - 1];}
};

在做这一题的时候,很像打家劫舍的问题,一开始的想法是构造一个初始化为0的数组group,然后列出状态转移方程max(dp[i-1], dp[i-3] + freq[i].first * freq[i].second)来解决问题,这个思路本身没有错,但是在提交的时候发现超出内存限制,原因是构造的freq有许多空间没有用到(都是0),所以需要用其他方法来避免对这些多余空间的占用。

记忆化搜索

class Solution {
public:long long maximumTotalDamage(vector<int>& power) {unordered_map<int, int> group;for(int num : power){group[num]++;}vector<pair<int, int>> a(group.begin(), group.end());sort(a.begin(), a.end(), [](const auto& a, const auto& b) {return a.first < b.first;});int n = a.size();vector<long long> memo(n, -1);auto dfs = [&](auto&& dfs, int i) -> long long{if(i < 0){return 0;}long long& res = memo[i];if(res != -1){return res;}int j = i;auto& [x,y] = a[i];while(j && a[j-1].first >= x-2){j--;}return res = max(dfs(dfs,i-1), dfs(dfs,j-1) + (long long) x * y);};return dfs(dfs, n-1);}
};

记忆化搜索运用了递归的思想,也运用了动态规划的思想,自上而下进行计算。
第一个要点是auto dfs = [&](auto&& dfs, int i) -> long long 中的&为什么要这样放。

捕获列表中的&:
& 表示按引用捕获外部作用域中的变量。这允许lambda函数在外部作用域中修改这些变量。
在这个上下文中,&捕获列表确保lambda函数能够访问和修改所有在其外部定义的变量,例如memo和a。

参数列表中的auto&& dfs:
这里的auto&& dfs是一个递归lambda函数的参数,表示该lambda函数自身的引用。
auto&&是一种通用引用(也叫转发引用),它可以绑定到任何类型(左值或右值引用)。
auto&& dfs允许我们定义一个递归lambda函数,因为在递归调用中,我们需要一个对自身的引用。

第二个是记忆话搜索可以用递归树来表示,如图:
在这里插入图片描述

在示例1中:power = [1,1,3,4],答案输出为6。


return dfs(dfs, n-1);

上面这一行代码返回的是{4,1},也就是最顶层的根节点。


auto dfs = [&](auto&& dfs, int i) -> long long{if(i < 0){return 0;}long long& res = memo[i];if(res != -1){return res;}int j = i;auto& [x,y] = a[i];while(j && a[j-1].first >= x-2){j--;}return res = max(dfs(dfs,i-1), dfs(dfs,j-1) + (long long) x * y);};

memo的作用
在dfs这个lambda函数内部,此时传进来的参数 i = 2,这时候首先要做的事情就是,将res指向memo[i], long long& res = memo[i]; 。设立memo这个数组的目的就是,储存在第 i 个元素之前的时候,可以最多造成的伤害。当memo[i]中的值被更改的时候,由于&res是对memo[i]的引用,也就是说,memo[i]被更改也就是res被更改,这时候就直接返回res。

	if(res != -1){return res;}

直接返回res有什么意义呢?原因是为了避免重复运算,在后面会提到。
接着auto& [x,y] = a[i];代表[x,y]是对a[i]的引用,[x,y]指向{4,1},x=4,y=1。这时候设立一个整型 j,他的目的是如果这时候选择了伤害为4的咒语,那么他就会自动搜索刚好满足a[j-1].first >= x-2这个伤害的咒语,然后再进行j--,j - 1 一定会满足小于x-2,最后在return中返回 j - 1。

return res = max(dfs(dfs,i-1), dfs(dfs,j-1) + (long long) x * y)

最后返回两种情况(选择伤害为4的咒语或不选择)中的最大值,在递归树中,也就是所具有最大数值的分支。dfs(dfs,i-1) 代表不选,指向{3,1},dfs(dfs,j-1) + (long long) x * y代表选,指向{1,2}。那么如何判断每个分支的数值呢?

接下来请看上面的递归树进行理解
在实际递归的运算过程中,采用的是深度优先遍历,也就是计算{4,1}的res需要第二层的{3,1}和{1,2}都返回给它一个值,他会先看{3,1}有没有返回给它一个值,发现{3,1}没有返回给它一个值,他就会计算{3,1}的两个子节点的返回值来计算{3,1}的值是多少,他就会先看第三层{1,2}有没有返回给他一个值,发现也没有,就会去计算第三层{1,2}的两个子节点的返回值来计算{1,2}的值,由于第三层的{1,2}是第一个元素了,所以他的两个子节点都只能返回它0。

这时候我们计算第三层{1,2}的值,他的值(也就是res)的计算要比较两种情况的最大值,也就是要选伤害为 1 的咒语还是不选择?如果不选择的话,他的左边的字节点会返回给它一个0,也就是代码中的dfs(dfs,i-1)。如果选择的话呢,右边的字节点也会返回给它一个0,但是要注意的是,当选择的时候,{1,2}的res的计算需要加上它本身 1 * 2 = 2,为什么要加,因为它选择了。选择的情况对应代码dfs(dfs,j-1) + (long long) x * y。这时候不选择的话,第三层{1,2}的res为0,选择的话,res为 0 + 1 * 2 = 2,取两种情况最大值2,所以第三层{1,2}的res也就是2,这时候要储存memo[0] = 2。

这时候往上去计算{3,1}的res,这时候{3,1}不选择伤害为3的咒语伤害的情况也就是{1,2}给他的返回值,同样对应代码dfs(dfs,i-1),这时候还差选择的情况,也就是需要计算{3,1}右边的子节点返回给它的值,右边的子节点返回给它的值是0,但是因为选择了伤害为3的咒语,所以所以{3,1}的res也就是0 + 3 = 3,取不选和选两种情况的最大值,也就是{3,1}的res为3,这时候储存memo[1] = 3。

接下来{4,1}的左子节点返回了他的res = 3,也就是{4,1}不选择情况的res是3,这时候可以开始计算选择伤害为4的咒语时候的res是多少,也就是要计算右子节点返回的值 + 4 * 1 。右子节点也就是第二层的{1,2},这时候就不需要像刚刚一样计算左右子节点返回值是多少了,因为{1,2}的res我们在前面已经算出等于2,储存在了memo[0]中。即使你再算一遍,{1,2}无论如何就是两种情况,计算方式和前面的完全相同。这时候也就是为什么要使用memo来储存res,这样可以避免多余的运算,可以返回上面看加粗的memo的作用这个标题,就能理解了。回到我们刚刚问题,不选择伤害为4咒语的情况返回的res是3,选择的情况就是右子节点返回的res = 2,然后还要加上4,所以选择的情况的res是6。然后比较选择和不选择两种情况的最大值,所以{4,1}的res = 6,储存memo[2] = 6。

这时候就计算出了施咒的最大伤害是6。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3268529.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

记录安装android studio踩的坑 win7系统

最近在一台新电脑上安装android studio,报了很多错误&#xff0c;也是费了大劲才解决&#xff0c;发出来大家一起避免一些问题&#xff0c;找到解决方法。 安装时一定要先安装jdk&#xff0c;cmd命令行用java -version查当前的版本&#xff0c;没有的话&#xff0c;先安装jdk,g…

地形材质制作(能使地面湿润)

如图&#xff0c;创建一个材质并写以下逻辑 Landscape Layer Blend节点能使在地形模式绘制中有三个选择&#xff0c;根据以上逻辑&#xff0c;Red是原材质,Green是绿色材质也就是草&#xff0c;Blue为水&#xff08;这个我认为比较重要&#xff09; Blue的颜色最好为这个 这个节…

QEMU源码全解析 —— CPU虚拟化(11)

接前一篇文章: 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社 《深度探索Linux系统虚拟化原理与实现》—— 王柏生 谢广军, 机械工业出版社 特此致谢! 前边几回又再次讲了一下VMX,本回开始讲解VCPU…

docker安装部署elasticsearch7.15.2

docker安装部署elasticsearch7.15.2 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.2如果不想下载或者镜像拉去太慢可以直接下载文章上面的镜像压缩包 使用镜像解压命令 docker load -i elasticsearch-7-15-2.tar如下图所示就表示镜像解压成…

前端canvas——赛贝尔曲线

曲线之美&#xff0c;不在于曲线本身&#xff0c;而在于用的人。 所以就有了这期赛贝尔曲线。 新规矩&#xff0c;先上个GIT。 效果图 开局一张图&#xff0c;代码全靠编。 代码 画骨 先想着怎么画一个心形吧&#xff0c;等你想好了&#xff0c;就知道怎么画了。 首先就还…

HBuilder X中配置vue-cli项目和UI库

目录 一.前端项目结构 二.在HBuilder X中搭建vue-cli项目 1. 安装node.js前端环境 2. HBuilder X创建一个vue-cli项目 3. vue-cli项目结构 4. 如何运行前端项目 5. 创建组件 6. 组件路由(页面跳转) 6.1 创建router目录 6.2 使用路由 6.3 在main.js中配置路由 6.4 路…

Linux基础复习(二)

前言 本文介绍了一下Linux命令行基本操作及网络配置 一、 命令行提示含义 [当前用户主机名 工作目录]$ 若当前用户是root&#xff0c;则最后一个字符为# 否则&#xff0c;最后一个字符为$ 二、常用Linux命令及其解释 修改主机名 一般在创建一台主机后会使用hostname相关命…

分享4款国产好用的AI工具,提高工作学习效率

1.kimi 一款很多人都在夸的国产AI大模型&#xff0c;首先是免费使用的&#xff0c;其次它的智能化程度很高&#xff0c;也就是很“聪明”&#xff0c;亲测好用&#xff01; 它可以实时联网&#xff0c;通过网站实时访问并搜索信息。当你提出问题后&#xff0c;它能够立即检索…

一些关于颜色的网站

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 1、中国传统色 2、网页颜色选择器 3、渐变色网站 4、多风味色卡生成 5、波浪生成 6、半透明磨砂框 色卡组合

H264编码器实现-帧内预测之像素值预测

前言 本文所介绍的像素值预测&#xff0c;是指在帧内预测总体流程中的预测块每个像素值的推导过程。当我们已知向量像素的重建值的时候&#xff0c;我们就可以对当前预测块进行像素值预测。该过程得到的结果将与源像素值相减得到残差&#xff0c;为后续变换量化提供数据来源。…

大模型算法面试题(十二)

本系列收纳各种大模型面试题及答案。 1、领域模型Continue PreTrain数据如何选取 在领域模型的Continue PreTrain&#xff08;持续预训练&#xff09;过程中&#xff0c;数据选取是一个至关重要的步骤&#xff0c;它直接影响模型在特定领域上的性能和泛化能力。以下是一些关于…

探索Linux-1-虚拟机远程登陆XShell6远程传输文件Xftp6

Linux是什么&#xff1f; Linux是一个开源的操作系统内核&#xff0c;由林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;于1991年首次发布。它基于Unix操作系统&#xff0c;但提供了更多的自由和灵活性。Linux内核是操作系统的核心部分&#xff0c;负责管理系统资源、处理…

vue3利用父子传参将页面展示到另一个页面上

点击左下角传到右边 绑定点击事件&#xff0c;在点击事件里传入参数1&#xff0c;将参数赋值给父组件绑定的tag参数上 props获取父组件参数

【Git】不同区域撤销代码{reset、revert}

工作区【磁盘】 关于GIt&#xff0c;当你在工作区也就是硬盘中修改文件内容&#xff0c;也就是下图的状态。 若你需要撤销此次修改&#xff0c;用到的命令就是 git checkout <changed_file> git restore <changed_file> #推荐 因为checkout在分支中也是切换分…

电子签章-开放签应用

开放签电子签章系统开源工具版旨在将电子签章、电子合同系统开发中的前后端核心技术开源开放&#xff0c;适合有技术能力的个人 / 团队学习或自建电子签章 \ 电子合同功能或应用&#xff0c;避免研发同仁在工作过程中重复造轮子&#xff0c;降低电子签章技术研发要求&#xff0…

找工作准备刷题Day10 回溯算法 (卡尔41期训练营 7.24)

回溯算法今天这几个题目做过&#xff0c;晚上有面试&#xff0c;今天水一水。 第一题&#xff1a;Leetcode77. 组合 题目描述 解题思路 从题目示例来看&#xff0c;k个数是不能重合的&#xff0c;但是题目没有明确说明这一点。 使用回溯算法解决此问题&#xff0c;利用树形…

【iOS】——通知机制及底层原理

通知传值概要 通知传值可以跨越多个界面进行传值&#xff0c;一般用于后一个界面向前一个界面传值。 通知传值支持多个接收者&#xff0c;多个对象可以同时接收同一个通知并进行处理。这样可以实现一对多的通信&#xff0c;方便跨多个对象进行值传递。 使用步骤 1.在发送者中…

0726,没什么用的SELECT和没用的我

目录 select 可恶&#xff01;&#xff01;&#xff01; 一对多聊天室 select&#xff1a;&#xff08;抄抄抄 最怕人类开始思考 补一对一的 select 喵&#xff1a;&#xff08;抄抄抄 &#xff1f;&#xff1f;今天就这么结束了&#xff1f;&#xff1f;&#xff1f; …

CTF-NSSCTF[GKCTF 2021]

[GKCTF 2021]easycms 考察&#xff1a; 用扫描工具扫描目录&#xff0c;扫描到后台登录界面/admin.php 题目提示了密码是五位弱口令&#xff0c;试了试弱口令admin和12345直接成功了 任意文件下载 点击设计-->主题然后随便选择一个主题&#xff0c;点击自定义&#xff0…

队列--顺序队列的表示和实现

#include<stdio.h> #define MAXQSIZE 10 typedef int QElemType; typedef int Status; //顺序队列 (循环队列,有一个空间不用) typedef struct{QElemType *base;int rear;int front; }SqQueue; //初始化队列 Status InitQueue(SqQueue &Q){Q.basenew QElemType[MAX…