面试经典算法系列之双指针6 -- 三数之和

面试经典算法题6 – 三数之和

LeetCode.15
公众号:阿Q技术站

问题描述

给你一个整数数组 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

思路

  1. 首先,我们对输入的整数数组进行排序。这有助于我们后面的双指针操作。排序后的数组可以使我们更容易找到解决方案,因为它们将有序排列在一起,我们可以在有限的步骤内找到所有解决方案。
  2. 对于每个元素 nums[i],我们都将它作为固定的第一个元素,并将问题转化为在剩余数组中寻找两数之和等于 0 - nums[i] 的问题。
  3. 对于每个固定的元素 nums[i],我们使用双指针技巧来找到剩余数组中满足条件的两个数。我们将左指针 left 指向剩余数组的起始位置,右指针 right 指向剩余数组的末尾位置。
  4. 我们计算当前三个数的和 sum,如果 sum 等于 0,则找到一个满足条件的三元组 [nums[i], nums[left], nums[right]],将其添加到结果数组中。然后,我们移动左右指针来寻找下一个解。如果 sum 小于 0,说明左边的数太小,左指针右移;如果 sum 大于 0,说明右边的数太大,右指针左移。
  5. 在移动指针时,我们要注意跳过重复的元素,以避免得到重复的解。例如,如果 nums[left]nums[left + 1] 相等,我们将左指针右移一位;如果 nums[right]nums[right - 1] 相等,我们将右指针左移一位。
  6. 最后,我们返回包含所有满足条件的三元组的结果数组。

演示过程

使用示例1来给大家做个演示,根据题目要求,我们首先需要将数组进行排序。得到nums = [-4,-1,-1,0,1,2]

  1. 先固定首个元素,然后将第i+1初始化为left,nums.size()-1初始化为right,这样就将问题转换为两数之和,继续看看。

image-20240124201829070

此时,sum = nums[i] + nums[left] + nums[right] = -3 < 0

  1. 将左指针右移

image-20240124202632797

此时,sum = nums[i] + nums[left] + nums[right] = -3 < 0

  1. 将左指针右移

image-20240124202923740

此时,sum = nums[i] + nums[left] + nums[right] = -2 < 0

  1. 将左指针右移

image-20240124203023113

此时,sum = nums[i] + nums[left] + nums[right] = -1 < 0

  1. 将左指针右移

image-20240124203137491

跳出循环。

  1. 继续将固定的向右移动

image-20240124204137752

此时,sum = nums[i] + nums[left] + nums[right] = 0,将结果加进结果数组,即result.push_back(-1,-1,2)

  1. nums[left] != nums[left+1],nums[right] != nums[right-1],将左指针右移一位,将右指针左移一位

image-20240124204619507

此时,sum = nums[i] + nums[left] + nums[right] = 0,将结果加进结果数组,即result.push_back(-1,0,1)

继续移动左右指针,跳出循环。

演示到这儿就可以了,大家应该也可以根据我给的步骤继续完成后边的。

参考代码

C++
#include <iostream>
#include <vector>
#include <algorithm>class Solution {
public:std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {std::vector<std::vector<int>> result; // 存放结果的二维数组std::sort(nums.begin(), nums.end()); // 对数组进行排序,方便后续的双指针操作int n = nums.size();for (int i = 0; i < n - 2; ++i) { // 固定第一个数,转化为求两数之和的问题if (i > 0 && nums[i] == nums[i - 1]) {continue; // 跳过重复的数字,避免重复解}int left = i + 1, right = n - 1; // 双指针分别指向剩余数组的两端while (left < right) { // 双指针遍历剩余数组int sum = nums[i] + nums[left] + nums[right]; // 计算当前三个数的和if (sum == 0) { // 如果和为0,找到一个满足条件的三元组result.push_back({nums[i], nums[left], nums[right]}); // 将满足条件的三元组加入结果数组while (left < right && nums[left] == nums[left + 1]) {left++; // 跳过重复的数字,避免重复解}while (left < right && nums[right] == nums[right - 1]) {right--; // 跳过重复的数字,避免重复解}left++; // 移动左指针,继续寻找下一个解right--; // 移动右指针,继续寻找下一个解} else if (sum < 0) { // 如果和小于0,说明左边的数太小,左指针右移left++;} else { // 如果和大于0,说明右边的数太大,右指针左移right--;}}}return result; // 返回结果数组}
};int main() {Solution solution;std::vector<int> nums = {-1, 0, 1, 2, -1, -4};std::vector<std::vector<int>> result = solution.threeSum(nums);for (const auto& triplet : result) { // 遍历结果数组并输出每个满足条件的三元组std::cout << "[";for (int num : triplet) {std::cout << num << ", ";}std::cout << "]" << std::endl;}return 0;
}
Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> result = new ArrayList<>();Arrays.sort(nums); // 对数组进行排序,方便后续的双指针操作int n = nums.length;for (int i = 0; i < n - 2; ++i) { // 固定第一个数,转化为求两数之和的问题if (i > 0 && nums[i] == nums[i - 1]) {continue; // 跳过重复的数字,避免重复解}int left = i + 1, right = n - 1; // 双指针分别指向剩余数组的两端while (left < right) { // 双指针遍历剩余数组int sum = nums[i] + nums[left] + nums[right]; // 计算当前三个数的和if (sum == 0) { // 如果和为0,找到一个满足条件的三元组result.add(Arrays.asList(nums[i], nums[left], nums[right])); // 将满足条件的三元组加入结果数组while (left < right && nums[left] == nums[left + 1]) {left++; // 跳过重复的数字,避免重复解}while (left < right && nums[right] == nums[right - 1]) {right--; // 跳过重复的数字,避免重复解}left++; // 移动左指针,继续寻找下一个解right--; // 移动右指针,继续寻找下一个解} else if (sum < 0) { // 如果和小于0,说明左边的数太小,左指针右移left++;} else { // 如果和大于0,说明右边的数太大,右指针左移right--;}}}return result; // 返回结果数组}
}public class Main {public static void main(String[] args) {Solution solution = new Solution();int[] nums = {-1, 0, 1, 2, -1, -4};List<List<Integer>> result = solution.threeSum(nums);for (List<Integer> triplet : result) { // 遍历结果数组并输出每个满足条件的三元组System.out.print("[");for (int num : triplet) {System.out.print(num + ", ");}System.out.println("]");}}
}
Python
class Solution:def threeSum(self, nums):""":type nums: List[int]:rtype: List[List[int]]"""result = [] # 存放结果的列表nums.sort() # 对数组进行排序,方便后续的双指针操作n = len(nums)for i in range(n - 2): # 固定第一个数,转化为求两数之和的问题if i > 0 and nums[i] == nums[i - 1]:continue # 跳过重复的数字,避免重复解left, right = i + 1, n - 1 # 双指针分别指向剩余数组的两端while left < right: # 双指针遍历剩余数组total = nums[i] + nums[left] + nums[right] # 计算当前三个数的和if total == 0: # 如果和为0,找到一个满足条件的三元组result.append([nums[i], nums[left], nums[right]]) # 将满足条件的三元组加入结果数组while left < right and nums[left] == nums[left + 1]:left += 1 # 跳过重复的数字,避免重复解while left < right and nums[right] == nums[right - 1]:right -= 1 # 跳过重复的数字,避免重复解left += 1 # 移动左指针,继续寻找下一个解right -= 1 # 移动右指针,继续寻找下一个解elif total < 0: # 如果和小于0,说明左边的数太小,左指针右移left += 1else: # 如果和大于0,说明右边的数太大,右指针左移right -= 1return result # 返回结果列表if __name__ == "__main__":solution = Solution()nums = [-1, 0, 1, 2, -1, -4]result = solution.threeSum(nums)for triplet in result: # 遍历结果列表并输出每个满足条件的三元组print(triplet)

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

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

相关文章

密码学与密码安全:理论与实践

title: 密码学与密码安全&#xff1a;理论与实践 date: 2024/4/10 21:22:31 updated: 2024/4/10 21:22:31 tags: 密码学加密算法安全协议密码分析密码安全实际应用未来发展 第一章&#xff1a;密码学基础 1.1 密码学概述 密码学是研究如何保护信息安全的学科&#xff0c;旨在…

OSPF数据报文格式

OSPF协议是跨层封装的协议&#xff0c;跨四层封装&#xff0c;直接将应用层的数据封装在网络层协议后面&#xff0c;IP协议包中协议号字段对应的数值为——89 OSPF的头部信息&#xff1a; ——所有数据包公有的信息 版本&#xff1a;OSPF版本 在IPV4中一般使用OSPFV2&#xf…

配置启动nacos,保姆级教程

下载nacos 下载链接 https://github.com/alibaba/nacos/releases进去下拉&#xff0c;找到下载版本信息。 下载后如图所示。 配置数据库 在我们的conf文件夹中有一个nacos-mysql的数据库文件 我们需要导入数据库&#xff0c;可通过工具Navicat等进行导入。 会有一下几张表…

计算机进制

进制 进制也就是进位制&#xff0c;是人们规定的一种进位方法对于任何一种进制—X进制&#xff0c;就表示某一位置上的数运算时是逢X进一位 十进制是逢十进一&#xff0c;十六进制是逢十六进一&#xff0c;二进制就是逢二进一&#xff0c;以此类推&#xff0c;x进制就是逢x进…

WebGPU vs. 像素流

在构建 Bzar 之前&#xff0c;我们讨论过我们的技术栈是基于在云上渲染内容的像素流&#xff0c;还是基于使用设备自身计算能力的本地渲染技术。 由于这种选择会极大地影响项目的成本、可扩展性和用户体验&#xff0c;因此在开始编写一行代码之前&#xff0c;从一开始就采取正确…

在B站看课的进度助手

效果 代码 BilibiliVideoDurationCrawler import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; imp…

KVM+GFS分布式存储系统构建KVM高可用

概述 本章利用KVM 及 GlusterFS 技术&#xff0c;结合起来从而实现 KVM 高可用。利用 GlusterFS 分布式复制卷&#xff0c;对 KVM 虚拟机文件进行分布存储和冗余。分布式复制卷主要用于需要冗余的情况下把一个文件存放在两个或两个以上的节点&#xff0c;当其中一个节点数据丢失…

雪花飘,购物抛物线,进度条等四个案列,带入走进 CSS transition

前言 今天从四个案例&#xff0c;我们一起走进 CSS Transition。 源码 以及 在线演示地址 源码地址&#xff1a; 四个案例&#xff0c; CSS Transition 源码 在线演示地址&#xff1a;(兼容移动端) 贝塞尔曲线运动进度条雪花飘飘效果购物车抛物线效果 案例演示 内置贝塞…

windows server 2019 -DNS服务器搭建

前面是有关DNS的相关理论知识&#xff0c;懂了的可以直接跳到第五点。 说明一下&#xff1a;作为服务器ip最好固定下来&#xff0c;以DNS服务器为例子&#xff0c;如果客户机的填写DNS信息的之后&#xff0c;服务器的ip如果变动了的话&#xff0c;客户机都得跟着改&#xff0c…

深入浅出Redis(九):Redis的发布订阅模式

引言 Redis是一款基于内存的键值对数据库&#xff0c;提供了多种数据结构存储数据&#xff0c;存取数据的速度还非常快&#xff0c;除了这些优点它还提供了其他特色功能&#xff0c;比如&#xff1a;管道、lua脚本、发布订阅模型 本篇文章主要描述发布订阅模型&#xff0c;将…

linux基础篇:Linux中磁盘的管理(分区、格式化、挂载)

Linux中磁盘的管理&#xff08;分区、格式化、挂载&#xff09; 一、认识磁盘 1.1 什么是磁盘 磁盘是一种计算机的外部存储器设备&#xff0c;由一个或多个覆盖有磁性材料的铝制或玻璃制的碟片组成&#xff0c;用来存储用户的信息&#xff0c;这种信息可以反复地被读取和改写…

Leetcode算法训练日记 | day22

一、二叉搜索树的最近公共祖先 1.题目 Leetcode&#xff1a;第 235 题 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足…

Python 发送邮件 (含代码)

通过Python代码来发送邮件。下面是步骤 先在某一个邮箱页面 开启 POP3/SMTP服务 获取授权码&#xff0c;这样免密码登录 授权码会用在代码里 获得 SMTP 服务器地址 代码 import smtplib from email.mime.text import MIMEText from email.utils import formataddr ms…

一例简单的文件夹病毒的分析

概述 这是一个典型的文件夹病毒&#xff0c;使用xp时代的文件夹图标&#xff0c;通过可移动存储介质传播&#xff0c;会向http://fionades.com/ABIUS/setup.exe下载恶意载荷执行。 其病毒母体只是一个加载器&#xff0c;会在内存是解密加载一个反射型的dll&#xff0c;主要的…

OOM三大场景和解决方案

目录 首先&#xff0c;说说什么是OOM&#xff1f; Java OOM的三大核心场景 场景一、堆内存OOM 类型一&#xff1a;在线OOM分析&#xff0c;这个属于轻量级的分析&#xff1a; 类型二&#xff1a;离线OOM分析&#xff0c;这个属于轻量级的分析&#xff1a; 场景二&#xf…

Ant Design Vue

Ant Design Vue是一个由阿里巴巴团队打造的Vue组件库&#xff0c;它以其优雅的设计和丰富的功能集成而被广泛使用。以下是对Ant Design Vue的简单介绍&#xff1a; 首先&#xff0c;Ant Design Vue采用了精良的设计风格&#xff0c;为用户提供了简约、美观的界面&#xff0c;符…

类和对象—初阶

目录 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 【面试题】 4.2 封装 【面试题】 5.类的作用域 6.类的实例化 7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式 7.3 结构体内存对齐规则 【面试题】…

LMDoply部署实战

使用LMDeoply部署各类开源大模型&#xff0c;进行推理实践。 一. 环境准备 1. 创建Conda环境 studio-conda -t lmdeploy -o pytorch-2.1.2 2. 安装LMDeploy 激活刚刚创建的虚拟环境。 conda activate lmdeploy 安装0.3.0版本的lmdeploy。 pip install lmdeploy[all]0.3.…

Chrome谷歌下载入口

​hello&#xff0c;我是小索奇 发现好多人说谷歌浏览器在哪里下载呀&#xff0c;哪里可以找到&#xff1f; 你可能会心想&#xff0c;一个浏览器你还不会下载啊&#xff1f; 还真是&#xff0c;有很多伙伴找不到下载入口&#xff0c;为什么呢&#xff1f; Bing进行搜索&am…

4.进程相关 2

8.内存映射 8.1 内存映射相关定义 创建一个文件&#xff0c;将保存在磁盘中的文件映射到内存中&#xff0c;后期两个进程之间对内存中的数据进行操作&#xff0c;大大减少了访问磁盘的时间&#xff0c;也是一种最快的 IPC &#xff0c;因为进程之间可以直接对内存进行存取 8.…