LeetCode算法——数组/字符串篇

对刷过的算法进行总结,所用解法都是最符合我个人逻辑的,以后再刷的话就看这篇帖子了


# 代码随想录——数组理论基础

首先要知道数组在内存中的存储方式,这样才能真正理解数组相关的面试题

数组是存放在连续内存空间上的相同类型数据的集合

数组可以方便的通过下标索引的方式获取到下标下对应的数据。

举一个字符数组的例子,如图所示:

需要两点注意的是

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

大家如果使用C++的话,要注意 vector 和 array 的区别,vector 的底层实现是 array,严格来讲vector 是容器,不是数组。数组的元素是不能删的,只能覆盖。


1、买卖股票的最佳时机

题目描述:

解法:

        只用遍历一次数组,思路分为2部分:

  1. 使用数组中的首元素作为股票最低价格(minPrice),使用for循环遍历数组,找到真正的minPrice;
  2. 卡住数组中临时的一个 minPrice 时,进入 else 来获取当前的利润(profit),这样不断往复,最终会获取到题目要求的最大利润。这样的解法十分优美!
class Solution {
public:int maxProfit(vector<int>& prices) {int profit = 0;int minPrice = prices[0];for(int i = 1; i < prices.size(); ++i){// 找到数组中的最小元素if(prices[i] < minPrice){minPrice = prices[i];}// 找到最大利润else{if(prices[i] - minPrice > profit){profit = prices[i] - minPrice;}}}return profit;}
};

2、移除元素

题目描述:

解法:

        题目要求原地修改原数组,但数组的特性是:数组中的元素只能覆盖,不能删除。所以设一个新的索引 index 来重新排列修改后的数组。

        使用 for 循环遍历数组时,如果 nums[i]  == val, 则continue程序不往下继续进行,而是进入下一次循环。当下一个  nums[i] != val 时,则将当前元素的索引赋值给新的索引 nums[index], 并且 index++ , 准备好接收下一个 != val 的数组元素。

class Solution {
public:int removeElement(vector<int>& nums, int val) {int index = 0;for(int i = 0; i < nums.size(); ++i){if(nums[i] == val){// 跳过下面的赋值,进入下一次循环continue;}nums[index] = nums[i];index++;}return index;}
};

3、合并两个有序数组

题目描述:

解法:

       利用了2个数组非递减的特性,使用尾指针从后向前往 nums1 中加入元素。率先使用 resize 将 nums1 的长度扩大到 m + n,以便向 nums1 中加入新元素。

        设置这样的 while 循环判断条件是考虑到2个数组总会有一方的索引先 < 0, 在消耗完其中一方时,再接一个 while 循环将另一方数组中的剩余元素添加至新的数组中

class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {// 要将nums2合并到nums1中,nums1数组长度发生改变,定义新的长度以便定义新的尾指针nums1.resize(m + n);// 定义数组的尾指针int i = m - 1;int j = n - 1;int index = m + n - 1;while(i >= 0 && j >= 0){if(nums1[i] < nums2[j]){nums1[index] = nums2[j];index--;j--;}else{nums1[index] = nums1[i];index--;i--;}}while(i >= 0){nums1[index] = nums1[i];index--;i--;}while(j >= 0){nums1[index] = nums2[j];index--;j--;}}
};

4、删除有序数组中的重复项

题目描述:

解法:

       为数组首元素设置慢指针 slow,第二个元素设置快指针 fast,指针 fast 向后遍历,直至越界。指针 slow 只在指针 fast 遇到不重复项时向后移动一位,以此接收不重复项。最终返回 slow + 1即新数组的长度

        题目中提到,返回的是最终的数组长度 ,是一个整数。但为什么输出的答案是数组呢? 输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的,根据我们函数返回的长度,它会打印出该长度范围内所有的元素

class Solution {
public:int removeDuplicates(vector<int>& nums) {int slow = 0;int fast = 1;while(fast < nums.size()){if(nums[fast] != nums[slow]){// 出现不重复的元素,用++slow去接收++slow;nums[slow] = nums[fast];}else{// 出现重复元素,slow不动,fast往后走fast++;}}return slow + 1;}
};

5、删除有序数组中的重复项Ⅱ

题目描述:

解法:

        首先给出第一次 AC 了8%测试用例的版本,方便记录我忽略了这道题[最关键的信息]

class Solution {
public:int removeDuplicates(vector<int>& nums) {int slow = 1;int fast = 3;while(fast < nums.size()){if(nums[fast] == nums[slow] && nums[fast - 1] == nums[slow]){slow++;nums[slow] = nums[fast - 1];fast++;}else{slow++;nums[slow] = nums[fast];fast++;}}return slow + 1;}
};

        由于这段代码的逻辑完全建立在第一个测试用例上

nums = [1,1,1,2,2,3]

        可以处理前 3 个元素相同时的情况,但如果是下面这种情况:

nums = [0,0,1,1,1,2,2,3,3,4]

        会将第二个元素 0 覆盖掉,很愁啊,修改了判断条件后连第一个测试用例都 AC 不过了!

        那么关键是什么呢?答:“数组的前两个元素必然满足条件,无需处理”这一点官方解答中也提到了。

        所以慢指针 slow 设在索引为 2 的位置上,使用一个循环,从索引为 2的位置开始遍历数组。对于当前遍历的元素,如果它与慢指针 slow 前两个位置的元素不相同,则将其加入到新数组中,同时慢指针 slow 增加。

正解:

class Solution {
public:int removeDuplicates(vector<int>& nums) {int n = nums.size();// 打印 n 范围内所有的元素if(n <= 2) return n;int slow = 2;for(int fast = 2; fast < n; ++fast){// 当前元素要和它左边第 2 个元素进行比较if(nums[fast] != nums[slow - 2]){nums[slow] = nums[fast];++slow;}}return slow;}
};

        还有一点值得注意:自增符 ++ 可以放在 nums[slow++] 中去写

  •         nums[slow++] —— 先用 nums[slow] 接收新值, slow 再加 1;
  •           nums[++slow] —— slow 先加 1,再用 nums[slow + 1] 接收新值;

6、多数元素

题目描述:

解法:       

        使用哈希映射(HashMap)来存储每个元素以及出现的次数。对于哈希映射中的每个键值对,键表示一个元素值表示该元素出现的次数

        对于数组的遍历,使用 C++11 中新增的 增强型 for 循环 

for(int num: nums)

        num 会遍历容器 nums 中的所有元素,这样可以很方便地记录每个元素的值和它们出现的次数

class Solution {
public:int majorityElement(vector<int>& nums) {// 利用映射类型的哈希结构,键存元素,值存出现次数unordered_map<int, int> counts;// 用来记录当前出现次数最多的元素以及它的出现次数int Cur = 0;int CurCnt = 0;for(int num: nums){++counts[num];// 擂台赛if(counts[num] > CurCnt){Cur = num;  // 更新主要元素为当前元素CurCnt = counts[num];  // 更新出现次数最多的元素的出现次数}}return Cur;}
};

        使用 if 语句进行元素间的擂台赛,决出最终出现次数最多的元素,这个操作可以省去遍历哈希表找出值最大者这一过程

7、轮转数组

题目描述:

解法:

        首先给出第一次 AC 了99%测试用例的版本,方便记录我忽略了这道题[最关键的信息]。        

        这道题的思路是容易想到的,说是“轮转”数组,其实用[滚动]数组描述更为贴切,每次从末尾元素开始,末尾元素滚至 下标0 的位置,剩余所有元素都要向右移动。

        此时应注意的时,如果从头向尾移动,那么下标较大的元素会被下标较小的元素覆盖掉,所以应该从后向前移动每个元素,当除了原本的末尾元素还未就位,其余元素都已完成了位置的轮转,而且下标0 的位置被空出来了,把末尾元素插进去就好了,那么末尾元素为什么不会被覆盖掉呢?

        我们可以提前使用一个临时变量来存储末尾元素

class Solution {
public:void rotate(vector<int>& nums, int k) {int n = nums.size();while(k--){int tmp = nums[n - 1]; // 将尾元素放在临时变量中for(int i = n - 1; i > 0; i--){nums[i] = nums[i - 1];}nums[0] = tmp;}}
};

        但是这种方法的时间复杂度是 O(k*n),k是旋转步数,n是数组的长度,在最后一个测试用例上超时了。

正解:

        使用额外的等长数组来存储轮转后的元素,并将这个数组中的元素依次赋值回原数组,这个解法的时间复杂度只有O(n),因为只需要遍历1次数组

class Solution {
public:void rotate(vector<int>& nums, int k) {// 使用额外的等长数组int n = nums.size();vector<int> newNums(n);for(int i = 0; i < n; ++i){newNums[(i + k) % n] = nums[i];}for(int i = 0; i < n; ++i){nums[i] = newNums[i];}}
};

        只是在判断元素的新下标时的公式不好想

(i  + k) % n

        总觉得有点像哈希冲突时的解法,希望下次写这题时我还记得这个公式

8、罗马数字转整数

题目描述:

解法:

        这题的唬人程度根本不是简单题的难度!官方给的描述太冗杂了,实际上就是

        “如果当前字符的值小于其后面一个字符的值(即罗马数字规则中小数在大数左边表示减法),则将该值从结果中减去;否则将该值加到结果中。”

class Solution {
public:int romanToInt(string s) {// 标准的键值对形式,使用映射类型的哈希表结构存储unordered_map<char, int> Roma = {{'I', 1},{'V', 5},{'X', 10},{'L', 50},{'C', 100},{'D', 500}, {'M', 1000},};int n = s.length();int ans = 0;for(int i = 0; i < n; ++i){if(i < n - 1 && Roma[s[i]] < Roma[s[i + 1]]){ans -= Roma[s[i]];  // 小数在大数左边表示减法}else{ans += Roma[s[i]];}}return ans;}
};

        对于官方提供的罗马数字和阿拉伯数字之间的对应关系,应该很自然地想到使用哈希表来存储它

        if 语句的判断条件为:确保 i 不是最后一个元素,因为最后一个元素没有更后的元素和它比较了

        当前元素如果小于后一个元素,则进行减法,否则进行加法

9、买卖股票的最佳时机Ⅱ

题目描述:

解法:

        与“买卖股票的最佳时机”不同的是,此题可以买卖多支股票

        找到最小的数了,在什么时候卖掉呢?示例1中表示,想要获得最大利润未必要在后续数中挑一个最大的去卖掉,按这个思路想的话会有以下问题:

                1.如何判断买卖一支股票和买卖多支股票之间的利润大小?

                2.刚开始可以找到值最小的那支股票,并假设后面可以卖掉;那么第二支股票呢?而且还面临一个问题:对于第一支股票而言,如果选择后续利润最大的一天卖掉,那么这一天之前价格比较低的第二支股票会因为过了买卖时间而无法进行交易了

        总之想要用一般的方法去解决这个问题超出了我愚笨大脑的极限,所以总结了使用贪心解决问题的算法

        (代码随想录)贪心算法:

                假设以下数组

如果想到其实最终利润是可以分解的,那么本题就很容易了!

如何分解呢?

假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。

相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。

此时就是把利润分解为每天为单位的维度,而不是从 0 天到第 3 天整体去考虑!

那么根据 prices 可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])。

从图中可以发现,其实我们需要收集每天的正利润就可以,收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间

那么只收集正利润就是贪心所贪的地方!

局部最优:收集每天的正利润,全局最优:求得最大利润

局部最优可以推出全局最优,找不出反例,试一试贪心!

class Solution {
public:int maxProfit(vector<int>& prices) {int profit = 0;for(int i = 1; i < prices.size(); ++i){profit += max(prices[i] - prices[i - 1], 0);}return profit;}
};

10、最后一个单词的长度

题目描述:

        

解法:

         字符串是若干字符组成的有限序列,也可以理解为是一个字符数组。从后往前遍历,先着手于测试用例1和3,如代码中的第二个while循环,可以统计最后一个单词的长度。

        此时不加第一个while循环对串尾进行处理的话,index进不去第二个while循环,从而无法index--,会直接return cnt = 0。逻辑就是先处理正常情况下的字符串,而对于串尾是空格的字符串,再去执行处理方案。

class Solution {
public:int lengthOfLastWord(string s) {int cnt = 0;int index = s.size() - 1;// 处理串尾是空格时的情况while(s[index] == ' ') index--;while(index >= 0 && s[index] != ' '){cnt++;index--;}return cnt;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IntelliJ IDEA 2023 for Mac 好用的Java开发工具

IntelliJ IDEA 2023是一款由JetBrains开发的强大的集成开发环境&#xff08;IDE&#xff09;软件&#xff0c;适用于多个编程语言。它旨在提高开发人员的生产力和代码质量&#xff0c;具有以下多种特色功能&#xff1a; 软件下载&#xff1a;IntelliJ IDEA 2023 v2023.3.6中文激…

2d导入人物素材进行分割后设置图层

1、设置分辨率大小 2、相机调整大小&#xff0c;要符合场景 3、选择2D sprite 编辑器 或者 点击这个也行 4、分割图像 5、设置过滤模式 6、图层设置

CAJViewer8.1下载地址及安装教程

CAJViewer是中国学术期刊&#xff08;CAJ&#xff09;全文数据库的专用阅读软件。CAJViewer是中国知识资源总库&#xff08;CNKI&#xff09;开发的一款软件&#xff0c;旨在方便用户在线阅读和下载CAJ数据库中的学术论文、期刊和会议论文等文献资源。 CAJViewer具有直观的界面…

Modbus协议介绍

Modbus存储区 从机存储数据&#xff0c;那么肯定要有一个存储区&#xff0c;那就需要文件操作&#xff0c;我们都知道这文件可以分为只读(-r)和读写(-wr)两种类型 并且存储的数据类型可以分为 &#xff1a;布尔量 和 16位寄存器 布尔量比如IO口的电平高低&#xff0c;灯的开关…

金仓方案 | 全国高速联网收费系统国产化升级进行时

近年来&#xff0c;国家提出了交通强国建设战略&#xff0c;鼓励在交通领域加强自主创新&#xff0c;推动产业转型升级。高速公路收费系统作为高速公路行业的核心&#xff0c;也是交通领域信息技术应用创新的重中之重&#xff0c;自主可控问题成为系统建设和运营的关键难点。 2…

[机器学习]练习闵可斯基距离

闵可斯基距离&#xff08;Minkowski distance&#xff09;是一种用于衡量向量空间中两点之间距离的方法。它是曼哈顿距离和欧几里得距离的一般化形式。闵可斯基距离使用一个参数 p 来调整计算方法&#xff0c;其中 p 是一个大于 0 的实数值。 在二维空间中&#xff0c;闵可斯基…

可控占比闪烁电路

1启动Q0.0 亮一秒歇一秒循环执行 定时器设置为2秒 使用指令LD> T0 k10 控制亮和灭时间

mysql 用户管理-权限管理

学习了用户管理&#xff0c;再学习下权限管理。 3&#xff0c;权限管理 权限管理主要是对登录到MySQL的用户进行权限验证。所有用户的权限都存储在MySQL的权限表中&#xff0c;不合理的权限规划会给MySQL服务器带来安全隐患。数据库管理员要对所有用户的权限进行合理规…

腾讯2024实习生在线笔试-0331

Q1 小红的图上染色 小红拿到了一个无向图&#xff0c;其中一些边被染成了红色。 小红定义一个点是“好点”&#xff0c;当且仅当这个点的所有邻边都是红边。 现在请你求出这个无向图“好点”的数量。 注&#xff1a;如果一个节点没有任何邻边&#xff0c;那么它也是好点。 …

2、Cocos Creator 下载安装

Cocos Creator 从 v2.3.2 开始接入了全新的 Dashboard 系统&#xff0c;能够同时对多版本引擎和项目进行统一升级和管理&#xff01;Cocos Dashboard 将做为 Creator 各引擎统一的下载器和启动入口&#xff0c;方便升级和管理多个版本的 Creator。还集成了统一的项目管理及创建…

【MySQL】内外连接——内连接、外连接、左外连接、右外连接、内外连接的区别、左外连接和右外连接的区别

文章目录 MySQLMySQL表的内连接和外连接1. 内连接2. 外连接2.1 左外连接2.2 右外连接 3. 内外连接的区别4. 左外连接和右外连接的区别 MySQL MySQL表的内连接和外连接 MySQL 中的内连接&#xff08;INNER JOIN&#xff09;和外连接&#xff08;包括左外连接 LEFT JOIN 和右外连…

考研数学|听完一遍汤家凤基础,1800都没思路,怎么办?

看了我这篇回答&#xff0c;保证你可以顺利的做1800题&#xff01; 如果你听了汤家凤老师的课&#xff0c;但是做题没思路&#xff0c;请不要担心&#xff0c;也不要急着换老师&#xff0c;你很有可能是方法错了。 请你反思一下&#xff1a; 1、你是不是听完课立刻就去做题。…

配音虾助力视频文案提取,提升内容创作效率

配音虾作为一款智能语音技术产品&#xff0c;正逐渐在视频内容创作领域崭露头角。它以其独特的语音转文字功能&#xff0c;助力视频文案提取&#xff0c;极大地提升了内容创作的效率。对于广大内容创作者来说&#xff0c;这无疑是一项革命性的工具。目前上线的渠道只有微信小程…

MTMT 质押活动启动 ,坐享巨大BTCFi 市场红利

BTC是第一大加密货币&#xff0c;目前加密货币总市值为 2.6 万亿美元&#xff0c;BTC市值占比约为 51%。事实上&#xff0c;相对于开发程度更高的以太坊生态&#xff0c;BTC生态目前仍处于发展的极早期&#xff0c;这意味着仍旧巨大的BTC价值亟需释放&#xff0c;这也意味着 BT…

设计方案-定时任务接口数据存储及更新策略

前言 在没有使用ETL工具且不考虑多数据源的情况下&#xff0c;我们需要从别的系统获取数据时&#xff0c;一般会选择分页接口查询并存储。本文算是我对类似场景代码的提炼&#xff0c;旨在总结相关套路&#xff0c;提升自我对数据库和模块的设计能力。 ETL(英文 Extract-Trans…

详解CAS(Compare and swap)

一、什么是 CAS CAS: 全称Compare and swap&#xff0c;字⾯意思:”⽐较并交换“&#xff0c;⼀个 CAS 涉及到以下操作&#xff1a; 我们假设内存中的原数据V&#xff0c;旧的预期值A&#xff0c;需要修改的新值B。 比较 A 与 V 是否相等。&#xff08;⽐较&#xff09; 如果…

日志集中审计系列(3)--- LogAuditor接收UMA设备syslog日志

日志集中审计系列(3)--- LogAuditor接收UMA设备日志 前言拓扑图设备选型组网需求配置思路操作步骤结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支持别的安全产品,导致很多网络安全的方案和产品功能无法模拟练习,是否有真机操作的实验或…

PyCharm中出现Microsoft Defender配置建议

原因 Windows安全中心的病毒和威胁防护会自动扫描电脑中的文件夹&#xff0c;我们的项目文件夹和IDE文件夹也会被扫描&#xff0c;而PyCharm认为这会降低IDE性能。 解决方法 直接点击提示框里的自动。 或是手动给扫描添加排除项&#xff0c;步骤如下&#xff1a; 1、先打开…

k8s1.28.8版本配置Alertmanager报警方式(邮件,企业微信)

文章目录 总结部署流程 Alertmanager 三大核心1. 分组告警2. 告警抑制3. 告警静默 报警过滤静默通知方案一&#xff1a;方案二&#xff1a; 抑制报警规则案例一 参考文档 自定义路由告警&#xff0c;分来自不同路由的告警&#xff0c;艾特不同的人员进行区分修改 alertmanager …

预训练大模型最佳Llama开源社区中文版Llama2

Llama中文社区率先完成了国内首个真正意义上的中文版Llama2-13B大模型&#xff0c;从模型底层实现了Llama2中文能力的大幅优化和提升。毋庸置疑&#xff0c;中文版Llama2一经发布将开启国内大模型新时代。 作为AI领域最强大的开源大模型&#xff0c;Llama2基于2万亿token数据预…