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 不是最后一个元素,因为最后一个元素没有更后的元素和它比较了

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于java+springcloud的分布式架构网上商城

开发语言&#xff1a;Java 框架&#xff1a;springcloud JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Mave…

Gradle连接超时问题connect time out

出现这样的问题不要慌张&#xff0c;可能是你配置gradle的问题一步一步来解决就完事了 1. 出现这样的问题首先我们先检查配置 首先我们先看到的标出来的地址可以看到&#xff0c;我们需要下载的是这个链接里面的压缩包数据&#xff0c;查看版本以及这个链接是不是错误的 2. 第…

springboot核心注解示例详解

文章简介 本文主要介绍springboot框架学习和工作中常用的核心注解&#xff0c;对注解进行了清晰地分类&#xff0c;配以简易代码和易懂的解释&#xff0c;能够让你掌握每个核心注解的用法&#xff0c;并可以迁移到学习和工作中加以使用。本文注解偏向于实用性。 springboot一…

2013年认证杯SPSSPRO杯数学建模B题(第一阶段)流行音乐发展简史全过程文档及程序

2013年认证杯SPSSPRO杯数学建模 B题 流行音乐发展简史 原题再现&#xff1a; 随着互联网的发展&#xff0c;流行音乐的主要传播媒介从传统的电台和唱片逐渐过渡到网络下载和网络电台等。网络电台需要根据收听者的已知喜好&#xff0c;自动推荐并播放其它音乐。由于每个人喜好…

武汉星起航:助力跨境电商新手,打造高质量亚马逊产品评价新策略

在今日全球化与数字化浪潮的推动下&#xff0c;跨境电商已成为推动国际贸易发展的新动力。然而&#xff0c;随着市场竞争的日益激烈&#xff0c;如何让自己的产品在亚马逊平台上脱颖而出&#xff0c;成为了众多跨境电商新手面临的重要问题。武汉星起航电子商务有限公司&#xf…

Qt源码调试步骤记录

1.源码&#xff1a; 两种方式&#xff0c;要么安装qt时选择source&#xff0c;要么从官网下载源码&#xff0c;然后在qt creator中设置路径。二选一即可。我选的第二种。 1.1.第一种&#xff0c;安装时选择source&#xff1a; 1.2.第二种&#xff0c;下载源码设置路径&#x…

MySQL数据库(一)

文章目录 1.MySQL8.0安装配置1.安装教程2.启动方法3.启动注意事项4.Navicat使用5.Navicat演示 2.MySQL数据库基本介绍1.三层结构2.SQL语句分类 3.MySQL数据库基本操作1.创建数据库2.不区分大小写的校对规则3.查看、删除数据库4.备份和恢复数据库1.备份数据库db01和db02&#xf…

C++多线程:线程的创建、join、detach、joinable方法(二)

1、线程的开始与结束 程序运行起来&#xff0c;生成一个进程&#xff0c;该进程所持有的主线程开始自动运行&#xff0c;main主线程运行完所有的代码从main函数中返回表示整个进程运行完毕&#xff0c;标志着主线程和进程的死亡&#xff0c;等待操作系统回收资源&#xff0c;因…

简单了解策略模式

什么是策略模式&#xff1f; 策略模式提供生成某一种产品的不同方式 Strategy策略类定义了某个各种算法的公共方法&#xff0c;不同的算法类通过继承Strategy策略类&#xff0c;实现自己的算法 Context的作用是减少客户端和Strategy策略类之间的耦合&#xff0c;客户端只需要…

ubuntu 连接 校园网

​ 认证修改为 Protected EAP(PEAP) CA 证书 勾选 No CA certificate is required 输入用户名和密码 连接成功 ​

【火猫TV】西甲:巴萨中后场大洗牌,两位新人或被放弃!

巴萨本赛季已经来到了最关键的时刻,联赛中他们要想办法缩小与皇马的差距,欧冠联赛则要和大巴黎争夺四强名额。不过球队在转会市场上的操作非常频繁,在转会资金有限的情况下,他们已经准备了多套引援策略,其中对于中后场的打造可能会成为今夏的工作重心。比如后防核心阿劳霍就被多…

二维码门楼牌管理应用平台建设:智能匹配与高效管理

文章目录 前言一、二维码门楼牌管理应用平台的意义二、地址坐标校验的重要性三、对外采数据匹配校验的实现方式四、智能匹配与人工审核的结合五、二维码门楼牌管理应用平台的前景展望 前言 随着城市化进程的加速&#xff0c;门楼牌管理成为城市治理中不可或缺的一环。传统的门…

java电话号码的字母组合(力扣Leetcode17)

电话号码的字母组合 力扣原题链接 问题描述 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 示例 1&#xff1a;…

HarmonyOS 应用开发之启动/停止本地PageAbility

启动本地PageAbility PageAbility相关的能力通过featureAbility提供&#xff0c;启动本地Ability通过featureAbility中的startAbility接口实现。 表1 featureAbility接口说明 接口名接口描述startAbility(parameter: StartAbilityParameter)启动Ability。startAbilityForRes…

GPT提示词分享 —— 智能域名生成器

提示词&#x1f447; 我希望你能充当一个聪明的域名生成器。我将告诉你我的公司或想法是什么&#xff0c;你将根据我的提示回复我一份域名备选清单。你只需回复域名列表&#xff0c;而不是其他。域名应该是最多 7-8 个字母&#xff0c;应该简短但独特&#xff0c;可以是朗朗上口…

【物联网项目】基于ESP8266的家庭灯光与火情智能监测系统——文末完整工程资料源码

目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …

STM32H743驱动SSD1309(5)

接前一篇文章&#xff1a;STM32H743驱动SSD1309&#xff08;4&#xff09; 三、命令说明 10. 设置BANK0的对比度控制&#xff08;81h&#xff09; 此命令设置显示器的对比度设置。该芯片具有从00h到FFh的256个对比度阶跃。segment输出电流随着对比度阶跃值的增加而增加。 示例…

CMOS逻辑门电路主要技术参数

传输延迟时间 由于电极之间以及电极与衬底之间存在寄生电容&#xff0c;并且输出端通常也存在负载电容。当输入信号跳变时&#xff0c;由于电容的充放电&#xff0c;输出电压的变化必然滞后与输入电压的变化。 门电路传输延迟波形图如下&#xff1a; 通常CMOS逻辑门电路输出端…

哔哩哔哩直播姬有线投屏教程

1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 用usb连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电 不要更改usb的连接方式(不然电脑会死机需要重启),此时电脑识别不到该手机设备(因为电脑把它识别为投屏设备) 想要正常连接电脑进行文件传输就按…

日志集中审计系列(4)--- LogAuditor接收IPS设备日志

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