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具有直观的界面…

vue render 函数详解 (配参数详解)

vue render 函数详解 (配参数详解) 在 Vue 3 中&#xff0c;render 函数被用来代替 Vue 2 中的模板语法。 它接收一个 h 函数&#xff08;或者是 createElement 函数的别名&#xff09;&#xff0c;并且返回一个虚拟 DOM。 render 函数的语法结构如下&#xff1a; render(h) …

Modbus协议介绍

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

如何实现对节点尺寸动态变化的有效监测与检测

前言 近来有需求要做分页&#xff0c;听起来可能有点Low。 所以我要把Low的事情做得有点逼格。 分页本身没啥&#xff0c;但是数据量起来了&#xff0c;比如十万。 要是不做点处理&#xff0c; 那你的页面估计爽得很&#xff0c;机器也爽得很。 放心&#xff0c;我不会让你这么…

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

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

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

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

可控占比闪烁电路

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

[已解决]Unable to find a valid cUDNN algorithm to run convolution

问题描述&#xff1a;Unable to find a valid cUDNN algorithm to run convolution 可能原因&#xff1a; 1、cuDNN库的配置问题。 2、未指定训练使用的显卡或显卡占用率已满。 3、确定性算法。 解决方案&#xff1a;&#xff08;从下往上试&#xff09; 1、更新GPU驱动、cuD…

大数据面试英文自我介绍参考(万字长文)

发现有一个怪圈,如果码农年龄35+,除非非常匹配,不然在国内企业筛选可能就筛选不过。国外码农可以干到40+、50+。一些外企,对年龄35+的码农依然青睐。这些外企对英文是有要求,通常是要英文自我介绍,下面提供一些英文自我介绍参考。 参考1: Good morning/afternoon, I a…

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 和右外连…

Java研学-SpringBoot(五)

七 统一异常处理 1 框架自带方式 Spring Boot 默认会把所有错误都交给 BasicErrorController 类完成处理&#xff0c;错误的视图导向到 classpath:/static/error/ 和 classpath:/templates/error/路径上&#xff0c;HTTP 状态码就是默认视图的名称。如&#xff1a; // 出现 4…

JS中常用的几种事件

在js中分为多种事件&#xff0c;比如点击事件&#xff0c;焦点事件&#xff0c;加载事件&#xff0c;鼠标事件等等... ... 点击事件 onclick点击事件&#xff0c;ondblclick双击事件 焦点事件 onblur元素失去焦点&#xff0c;onfocus元素获取焦点 加载事件 onload一个页面…

windows本地访问局域网tensorboard

https://blog.csdn.net/m0_51268651/article/details/126524028 这个文章中说使用xshell配置ssh后可在windows访问 有一说一不太能理解为什么谷歌会做的那么麻烦 果然有更好的方案&#xff0c;如下&#xff1a; tensorboard --logdir/path/to/event --host 0.0.0.0 --port 6…

600条最强Linux命令总结

1. 基本命令 uname -m 显示机器的处理器架构 uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 (SMBIOS / DMI) hdparm -i /dev/hda 罗列一个磁盘的架构特性 hdparm -tT /dev/sda 在磁盘上执行测试性读取操作系统信息 arch 显示机器的处理器架构 uname -m 显示…

Linux 如何一键kill杀死某个被占用的端口

文章目录 一、例子&#xff1a;关闭8082端口二、查找被占用端口的PID三、 kill 掉这个进程总结 一、例子&#xff1a;关闭8082端口 [rootVM_0_59_centos root]# netstat -tln | grep 8082 tcp6 3 0 :::8082 :::* LISTEN二、查找…