LeetCode 热题 100 题解(二):双指针部分(1)

news2024/11/26 12:39:07

题目一:移动零(No. 283)

题目链接:https://leetcode.cn/problems/move-zeroes/description/?envType=study-plan-v2&envId=top-100-liked


给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums =[0,1,0,3,12]
输出:[1,3,12,0,0]

示例 2:

输入: nums =[0]
输出:[0]

提示:

  • 1 <= nums.length <= 104
  • 231 <= nums[i] <= 231 - 1

题解

本题要求是将所有的 0 移动到数组的末尾,同时要保证原本的顺序,直接顺着这个思路去实现是很困难的,此时可以换一个思路:将所有非零的数字都移到最前面就可以了,这样就可以很容易的保证移动之后的顺序,具体可以看下面的动画演示。

在这里插入图片描述

图片来源:王尼玛

首先声明两个指针,一个指针去遍历数组查找 非零 的数据,另一个指针去标明替换的位置,当指针 a 遍历到非零的元素,就与 b 指针进行替换的操作。

所以此时的思路就是两个指针分别去遍历,当 b 寻找到为 0 的节点就停住,等待 a 找到非零的节点进行替换的操作,写出代码是这样的:

class Solution {
    public void moveZeroes(int[] nums) {
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0 && nums[j] == 0) {
            // 如果找到了非零的节点就进行替换的操作
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
                j++;
            }
            if (nums[j] != 0) j++; // 如果不是 0,继续向后寻找
        }
    }
}

但其实不管 j 的位置是否为 0 其实都可以进行替换的操作,这里先给出代码:

class Solution {
    public void moveZeroes(int[] nums) {
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) { // 找到了非零的节点
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
                j++;
            }
        }
    }
}

这样的写法其实就是融合了上面的两种情况,如果 nums[j] != 0 的时候也执行替换的操作

  • nums[j] != 0nums[i] != 0 的情况下执行替换的操作的情况,只有一种可能 就是i == j 的情况,此时执行的就是自身和自身替换,然后同时后移一位

我们在 if 语句中加一个语句来验证这个结论:if (nums[j] != 0) System.*out*.println(i == j);
测试用例:1, 3, 4, 0, 0, 1, 0, 3, 12,此时输出的为 true true true

我们来多测试几组试一下:1, 4, 0, 0, 1, 0, 3, 12,此时输出的是 true true

那 1, 0, 0, 1, 0, 3, 12 呢?猜一下也能知道是 1

这些是执行自身替换的次数,这种行为会在 遇到第一个0的时候 停止,在遇到 0 之后,j 就会跟不上 i 的速度,**从而一定会留在为 0 的位置上,**这个大家举几个案例就可以很轻松的看出来,j 一定会留在为 0 的位置上;相较于第一种方法,第二种简化了判断的逻辑,执行速度比第一种快得多。

为了少执行替换操作,可以在方法上执行这个逻辑

  			int temp = 0;
        for(int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                temp = i;
                break;
            }
        } 
        int j = temp;

先找到为零的节点再执行下面的代码,但其实这样优化的意义不是特别大,但对理解这个解法比较有帮助。

题目二:盛水最多的容器(No. 11)

题目链接:https://leetcode.cn/problems/container-with-most-water/description/?envType=study-plan-v2&envId=top-100-liked


给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

**说明:**你不能倾斜容器。

示例 1:
在这里插入图片描述

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

提示:

  • n == height.length
  • 2 <= n <= 105
  • 0 <= height[i] <= 104

题解

为了求容器的容量,本题需要考虑到两个因素,分别是两个容器壁的最低高度,第二个是两个容器壁之间的距离,这两个限制条件很难去同时兼顾到,所以本题首先想到的方式就是穷举法,将所有可能出现的情况都列举出来,一定不要忽视穷举法,很多好的方法都是在穷举的基础上优化来的。

穷举的思路就是指定两个指针,left 和 right,left 首先指向 0,right 指针指向数组的末尾,然后对 right 进行递减的操作,知道 right == left 结束此次循环,然后执行 left++,再次从末尾去循环 right。

在遍历中不断去取得容量,直到遍历完成后输出最终的容量。

此时,思考一个问题,如果我发现 height[left] < height[right] ,还有必要去遍历整个数组嘛?

如果 height[left] < height[right] 那此时的容量就是 height[left] * (right - left) ,此时我去移动 right,那只有两种情况:

高度比刚刚的还高或者高度比刚刚的还低,无论是哪种情况,得到的结果 一定是小于最初的情况的;所以得出一个结论,如果我发现 height[left] < height[right] 就直接结束本次遍历即可

class Solution {
    public int maxArea(int[] height) {
        int res = 0;
        for (int left = 0; left < height.length - 1; left++) {
            int right = height.length - 1;
            while (right > left) { // 循环遍历 right
                int temp = (right - left) * Math.min(height[left], height[right]);
                res = Math.max(temp, res);
                if (height[left] < height[right]) break;
                right--;
            } 
        }
        return res;
    }
}

写出代码就是这样子的。

但是上面的解法时间复杂度仍然非常高,left 要遍历完整个数组,那有没有可能提前结束呢?

反过来去思考这道题目,其实我固定 right,然后去从头遍历 left 也是可以的,终止条件就变为了 height[right] < height[left]

那此时就有一个新的思路,两个指针分别指向开头和结尾,如果我发现左边的比较矮,我就移动左指针,反之则移动右指针,这样就同时运用到了固定 left 和固定 right 两种情况的限制条件,从而进一步的优化解法:

class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int res = 0;
        while (left < right) {
            int h = Math.min(height[left], height[right]);
            int temp = (right - left) * h;
            res = Math.max(temp, res);
            if (height[left] <= height[right]) {
            // 如果左边的低
                left++;
            } else {
            // 如果右边的低
                right--;
            }
        }
        return res;
    }
}

题目三:三数之和(No. 15)

题目链接:https://leetcode.cn/problems/3sum/description/?envType=study-plan-v2&envId=top-100-liked


给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • 105 <= nums[i] <= 105

题解

本题中给出一个数组,求数组中和为 0 的三元组,且要求 不能重复。

之前题解中我们讲解过 两数之和 这道题目,本题如果固定一个元素,然后本题就转化为在剩余的元素中,找两个数字,其和为 0 - 固定的数字 。这样就把三树之和转化为了两数之和来求解,这是求解本题的大致思路。

但是与两数之和不同的是,本题中要求了 去重

为了将相同的元素聚集在一起,就可以执行排序的方法,通过 Arrays.sort() 方法排序,可以将所有相同的元素聚集在一起,方便后续进行去重的操作。

首先来考虑第一个元素的选取,经过排序之后,数组是这样的:

在这里插入图片描述

当选定一个元素后,剩下两个元素的查找范围就是下一个一直到末尾(为了不出现重复的情况),在这种情况下选定的这个元素就 **一定是负数,**因为如果选定的是正数的话,在查找范围内无法形成和为 0 的情况。

那此时第一个元素的范围就确定了:

for (int i = 0; i < nums.length; i++) {
	if (nums[i] > 0) {
	    return res;
	}
	if (i > 0 && nums[i] == nums[i - 1]) {
	    continue;
	}
	// 其他的逻辑
}

上面代码展示的就是遍历第一个元素的逻辑,首先要保证 i 不会越界,其次要保证 nums[i] 是小于等于零的,当发现 nums[i] > 0 的时候就说明本次的查找已经结束了,直接将 res 返回;第二段 if 进行去重的,就是如果发现 nums[i] == nums[i - 1] (和上一个相同),此时就直接遍历下一个元素。

然后就是在剩余的部分中找到两个数,使得它们三个的和为 0,当然可以使用前面的哈希,但这里因为经过了排序,我们使用一个新的双指针方法来解决:

首先指定两个指针,来规范查找的范围:

int left = i + 1;
int right = nums.length - 1;

此时的和为: nums[left] + nums[right] + nums[i] ,然后去判断这个和是大于、小于还是等于 0。

  • 如果发现大于零,就说明取得值太大了,此时左移 right 指针
  • 如果发现小于零,则说明取得值太小了,此时右移 left 指针
  • 如果恰好等于零就将其存储下来
      while(left < right) {
          int temp = nums[left] + nums[right] + nums[i];
          if (temp > 0) {
              right--;
          } else if (temp < 0) {
              left++;
          } else {
		          res.add(Arrays.asList(nums[i], nums[left], nums[right])); 
              // 去重逻辑
              right--;
              left++;
          }
      }

在恰好等于的情况需要考虑去重的逻辑,否则就会出现重复的情况,此时就需要移动指针直到 nums[left] != nums[left - 1] 同时 nums[right] != nums[right + 1] ,可以通过 while 循环来解决:

      while (left < right && nums[left] == nums[left + 1]) {
          left++;
      }
      while (right > left &&  nums[right] == nums[right - 1]) {
          right--;
      }

此时整个流程就完成了,这里给出完整的代码:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) {
                return res;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 先固定一个,剩下和二维的比较类似了
            int left = i + 1;
            int right = nums.length - 1;
            while(left < right) {
                int temp = nums[left] + nums[right] + nums[i];
                if (temp > 0) {
                    right--;
                } else if (temp < 0) {
                    left++;
                } else {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right])); 
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (right > left &&  nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--;
                    left++;
                }
            }
        }
        return res;
    }
}

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

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

相关文章

(小红书平台)2024用户画像洞察报告

现今的小红书坐拥3亿月活用户&#xff0c;男女比例达到3:7&#xff0c;95后占比为50%&#xff0c;00后占比为35%&#xff0c;一二线城市用户占比50%。社区分享者超8000万&#xff0c;日均用户搜索渗透达到60%&#xff0c;UGC内容占比达90%。&#xff08;数据来源&#xff1a;小…

C语言_文件操作

文件基础 什么是文件 文件是在计算机中以实现某种功能、或某个软件的部分功能为目的而定义的一个单位。磁盘上的文件是文件。但是在程序设计中&#xff0c;我们一般谈的文件有两种&#xff1a;程序文件、数据文件&#xff08;从文件功能的角度来分的&#xff09;。 程序文件 …

一分钟了解机器人自由度

目录 自由度的定义 自由度的分类 自由度的影响 影响自由度的主要参数 关节类型和数量 机械结构 控制系统 自由度控制的硬件架构原理 传感器 执行器 控制器 通信接口 软件和算法 机器人的自由度是指机器人在空间中可以独立移动的方向和角度的数量&#xff0c;它是衡…

STL容器之unordered_set类

文章目录 STL容器之unordered_set类1、unordered系列关联式容器2、unordered_set2.1、unordered_set介绍2.2、unordered_set的使用2.2.1、unordered_set的常见构造2.2.2、unordered_set的迭代器2.2.3、unordered_set的容量2.2.4、unordered_set的增删查2.2.5、unordered_set的桶…

看看《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制山鸢尾萼片长度和萼片宽度的小提琴图

使用MATLAB绘制鸢尾花数据集&#xff08; fisheriris&#xff09;中山鸢尾&#xff08; Iris Setosa&#xff09;的萼片长度和 萼片宽度的小提琴图。这将帮助我们更好地了解山鸢尾的这两个特征的数据分布情况&#xff0c;包括它们的 中位数、四分位范围及密度估计。这种可视化工…

制造业、能源等传统行业进行数字化转型时要注意哪些问题?

制造业、能源等传统行业在进行数字化转型时需要注意以下几个关键问题&#xff1a; 1、明确转型目标和战略规划&#xff1a;企业需要根据自身的业务特点、市场需求和长远发展目标&#xff0c;制定清晰的数字化转型战略。包括确定转型的重点领域、预期成果、时间表和资源投入。 …

AI大模型探索之路-应用篇4:Langchain框架Memory模块—增强模型记忆与知识保留

目录 前言 一、概述 二、Conversation Buffer 三、Conversation Buffer Window 四、Conversation Summary 五、Conversation Summary Buffer 总结 前言 大模型技术在理解和生成自然语言方面表现出了惊人的能力。因此&#xff0c;为了实现长期的记忆保持和知识累积&#x…

【Java EE】获取Cookie和Session

文章目录 &#x1f38d;Cookie简介&#x1f340;理解Session&#x1f333;Cookie 和 Session 的区别&#x1f332;获取Cookie&#x1f338;传统获取Cookie&#x1f338;简洁获取Cookie &#x1f334;获取Session&#x1f338;Session存储&#x1f338;Session读取&#x1f33b;…

内网IP与外网IP关联关系连接过程

前言 我们每天都会访问各种各样的网站&#xff0c;比如淘宝&#xff0c;百度等等。不免会思考&#xff0c;我们的设备是如何连接上这些网址的呢&#xff1f;要想搞清楚这个问题&#xff0c;首先就得先搞清楚内网ip和外网ip的联系。 网络结构 如图&#xff0c;假设我们的计算机…

IP协议中的四大支柱:DHCP、NAT、ICMP和IGMP的功能剖析

DHCP动态获取 IP 地址 我们的电脑通常都是通过 DHCP 动态获取 IP 地址&#xff0c;大大省去了配 IP 信息繁琐的过程。 客户端首先发起 DHCP 发现报文&#xff08;DHCP DISCOVER&#xff09; 的 IP 数据报&#xff0c;由于客户端没有 IP 地址&#xff0c;也不知道 DHCP 服务器的…

短剧在线搜索PHP网站源码

源码简介 短剧在线搜索PHP网站源码&#xff0c;自带本地数据库500数据&#xff0c;共有6000短剧视频&#xff0c;与短剧猫一样。 搭建环境 PHP 7.3 Mysql 5.6 安装教程 1.上传源码到网站目录中 2.修改【admin.php】中&#xff0c; $username ‘后台登录账号’; $passwor…

Android 14.0 SystemUI修改状态栏电池图标样式为横屏显示

1.概述 在14.0的系统rom产品定制化开发中,对于原生系统中SystemUId 状态栏的电池图标是竖着显示的,一般手机的电池图标都是横屏显示的 可以觉得样式挺不错的,所以由于产品开发要求电池图标横着显示和手机的样式一样,所以就得重新更换SystemUI状态栏的电池样式了 如图: 2.S…

通信分类3G,4G,5G,通信专用名词

Generation: 2G: GSM全名为&#xff1a;Global System for Mobile Communications&#xff0c;中文为全球移动通信系统&#xff0c;俗称"全球通"&#xff0c;是一种起源于欧洲的移动通信技术标准&#xff0c;是第二代移动通信技术 3G&#xff1a;WCDMA 4G&#xff1a…

PaddleVideo:onnx模型导出

本文节介绍 PP-TSM 模型如何转化为 ONNX 模型&#xff0c;并基于 ONNX 引擎预测。 1&#xff1a;环境准备 安装 Paddle2ONNX python -m pip install paddle2onnx 安装 ONNXRuntime # 建议安装 1.9.0 版本&#xff0c;可根据环境更换版本号 python -m pip install onnxrunti…

flask 访问404

当你的项目有自己的蓝图&#xff0c;有添加自己的前缀&#xff0c;也注册了蓝图。 在访问的路由那里也使用了自己的蓝图&#xff0c;如下图 然后你访问的地址也没问题&#xff0c;但是不管怎么样访问就是返回404&#xff0c;这个时候不要怀疑你上面的哪里配置错误&#xff0c;…

彩虹聚合DNS管理系统源码

聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析&#xff0c;目前已支持的域名平台有&#xff1a;阿里云、腾讯云、华为云、西部数码、CloudFlare。本系统支持多用户&#xff0c;每个用户可分配不同的域名解析权限&#xff1b;支持API接口&#xff0c;支持获取域名…

标注平台工作流:如何提高训练数据质量与管理效率

世界发展日益依托数据的驱动&#xff0c;企业发现&#xff0c;管理不断增长的数据集却愈发困难。数据标注是诸多行业的一个关键过程&#xff0c;其中包括机器学习、计算机视觉和自然语言处理。对于大型语言模型&#xff08;LLM&#xff09;来说尤是如此&#xff0c;大型语言模型…

Spring之AOP的详细讲解

目录 一.SpringAOP是什么&#xff1f; 1.1理论知识点 1.2简单的AOP例子 二.SpringAOP的核心概念 2.1切点(Pointcut) 2.2通知&#xff08;Advice&#xff09; 2.3切⾯(Aspect) 2.4通知类型 2.5切⾯优先级 Order 2.6切点表达式 2.6.1 execution表达式 2.6.2annotati…

【绘图案例-开启图片类型的上下文withOptions Objective-C语言】

一、上午呢,我们讲了一下图片类型的上下文 1.开启图片类型的上下文:UIGraphicsBeginImageContext, 然后,我们在上边儿,画了一些东西, 然后呢,把它取出来了,通过UIGraphicsGetImageFromCurrentImageContext() 通过这个图片类型的上下文,取出来了一个image对象, …

邦火策划真的靠谱吗?餐饮品牌策划实例解析

邦火策划在餐饮品牌策划领域的表现是否靠谱&#xff0c;可以通过具体的实例来进行解析。以下是一些相关的实例分析&#xff0c;以探讨邦火策划在餐饮品牌策划方面的真实效果和专业性。 首先&#xff0c;从品牌塑造与传播的角度来看&#xff0c;邦火策划注重通过精准的市场定位…