算法练习-三数之和(思路+流程图+代码)

news2024/11/27 12:48:06

难度参考

        难度:中等

        分类:数组

        难度与分类由我所参与的培训课程提供,但需要注意的是,难度与分类仅供参考。且所在课程未提供测试平台,故实现代码主要为自行测试的那种,以下内容均为个人笔记,旨在督促自己认真学习。

题目

        给你一个整数数组nums,判断是否存在三元组[nums[i],nums[j],nums[k]]满足i!=j、i!=k且j!=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]。
        注意,输出的顺序和三元组的顺序并不重要。

        额外要求:
        ·答案中不可以包含重复的三元组

思路

        可以使用排序加双指针的方法来解决这个问题,先对数组排序,然后遍历数组,对于每个元素,使用双指针指向该元素之后的开始位置和结束位置,然后根据三个数字之和与0的比较结果来移动指针。具体步骤如下:

  1. 对数组进行排序。
  2. 遍历排序后的数组,对于索引 i 的元素,设置左指针 left 在 i+1 的位置,设置右指针 right 在数组末尾的位置。
  3. 如果 nums[i] 大于 0,由于数组已经排序,nums[i] 之后的元素都会大于0,它们的和不可能为0,结束循环。
  4. 如果当前索引 i 与前一个索引相同,则跳过当前循环,防止出现重复的三元组。
  5. 当左指针小于右指针时,计算 nums[i]nums[left] 和 nums[right] 的和。
  6. 如果和为 0,添加到结果中,并且移动左右指针跳过相同的元素,防止重复。
  7. 如果和小于 0,说明需要增加数值,左指针右移。
  8. 如果和大于 0,说明需要减小数值,右指针左移。
  9. 遍历完成后,返回结果。

示例

        假设我们有一组数字:[-1, 0, 1, 2, -1, -4],我们希望找到所有唯一的三个数字组合,使得这三个数字的和为 0。

        我们按升序排序,得到:[-4, -1, -1, 0, 1, 2]。这是为了方便找到结果并避免重复。这样一来,在未来的时候,假设前一个left指向头一个出现的 -1,那么现在left 指向的 -1 与前一个 -1 是相同的。如果我们用这个 -1 作为三元组的一部分,它就会产生与前一个 left 指针位置相同的三元组。为了避免记录重复的三元组,我们只用检查临近的元素,判断之后继续移动 left,跳过所有相同的元素即可避免元素的重复。

  1. 第一轮遍历

    • 遍历的第一个数字是 -4,这是当前“基准数”。
    • left 指针在 -1(基准数的右边第一个位置)。
    • right 指针在 2(数组的最右端)。
              [-4, -1, -1, 0, 1, 2]
      基准数 :  ^
      left   :      ^
      right  :                   ^

            我们的目标是找到使得 -4 + (A[left]) + (A[right]) = 0 的A[left]A[right]数值。当前 -4 + (-1) + (2) = -3,和小于0,我们需要一个更大的数,所以将 left 右移。

    • left 移动到第二个 -1-4 + (-1) + (2) = -3,仍然小于0,我们再次移动 left

              [-4, -1, -1, 0, 1, 2]
      基准数 :  ^
      left   :          ^
      right  :                   ^
    • left 指向 0 时,和为 -2,我们继续右移 left

              [-4, -1, -1, 0, 1, 2]
      基准数 :  ^
      left   :             ^
      right  :                   ^
    • left 指向 1 时,和为 -1,继续。

              [-4, -1, -1, 0, 1, 2]
      基准数 :  ^
      left   :                ^
      right  :                   ^
    • 现在 left 和 right 相遇了,这一轮结束。由于 -4 的值太小,我们无法在不使用它的情况下得到和为0的三个数。

              [-4, -1, -1, 0, 1, 2]
      基准数 :  ^
      left   :                   ^
      right  :                   ^
  2. 第二轮遍历

    • 基准数 移动到第一个 -1
    • left 指向第二个 -1
    • right 仍然指向 2
              [-4, -1, -1, 0, 1, 2]
      基准数 :      ^
      left   :          ^
      right  :                   ^

            我们同样寻找和为0的组合。现在有 -1 + (-1) + (2) = 0,我们找到了第一个有效的三数组合。

    • 记录这个组合,在上面的数组中为[-1, -1, 2]

              [-4, -1, -1, 0, 1, 2]        记录: [-1, -1, 2]
      基准数 :      ^
      left   :          ^
      right  :                   ^
    • left 移动到下一位,为了避免重复需要跳过相同的值。但是因为后面立即就是 0,所以我们检查这个组合:

              [-4, -1, -1, 0, 1, 2]        记录: [-1, -1, 2]
      基准数 :      ^
      left   :             ^
      right  :                   ^
    • -1 + (0) + (2) = 1,总和大于0,不符合条件,我们需要减少数值。所以移动 right 指针向左。

              [-4, -1, -1, 0, 1, 2]        记录: [-1, -1, 2]
      基准数 :      ^
      left   :             ^
      right  :                ^
    • right 移动到 1,现在 -1 + (0) + (1) = 0,我们找到第二组和为0的三数组合。

    • 记录这个组合,在我们的数组中为[-1, 0, 1]

              [-4, -1, -1, 0, 1, 2]        记录: [-1, -1, 2]
      基准数 :      ^                             [-1, 0, 1]
      left   :             ^
      right  :                ^
  3. 接下来的遍历
            由于我们开始的基准数是数组中第二个数,其实是第一个-1的重复值,我们也可以简单跳过它,避免重复工作。但是,如果我们继续操作,基准数将会移动到第二个 -1,然后重复上面步骤2的过程,并最终移动到 0,继续寻找组合,但是从0开始,我们不可能找到两个更小的数使它们的和为0,因为所有剩下的数都不够小。

                因此遍历结束,我们有了两组符合要求的组合:

  • [-1, -1, 2]
  • [-1, 0, 1]

        这些就是通过具体移动leftright指针,我们在例子数组中找到的所有唯一的三数之和等于0的组合。

梳理

        在三数之和的问题中,我们通常首先对数组进行排序,然后用一个循环遍历每个元素,将每个元素作为潜在的“基准数”来寻找其他两个数,使得它们的和为零。因为数组是排序过的,所以当我们在进行双指针查找其他两个数时,我们可以很容易地跳过重复的数字,以避免发现重复的三元组。

        当我们遇到一个和前一个数字相同的“基准数”时,意味着使用这个作为基准数的所有潜在的组合在前一个基准数中已经被检查过。因此,以这个重复的数字作为基准数去寻找新的配对没有意义,因为它只会给出与前一轮基准数相同的结果。为了避免重复工作,我们直接跳过重复的基准数。

        在这个例子中,我们有两个 -1 作为潜在的基准数。当我们使用第一个 -1 作为基准数时,我们已经找到了所有可能的有效组合,那么第二个 -1 就没有必要再次作为基准数进行同样的工作。

        关于基准数为 0的情况,我们知道两个数之和为零的情况只存在于它们互为正负的情况下。如果基准数为 0,要找到两个其他的数使得三数之和为零,那么这两个数必须是相等且符号相反的。但如果我们的基准数是第一个非负数(在排序的数组中为 0 或 正数),再往后(即右侧的元素)不可能找到两个和为负数的元素与其组合成和为零。因此,当遍历到 0 或更大的数作为基准数时,我们可以停止搜索。

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    
    for (int i = 0; i < nums.size() && nums[i] <= 0; ++i) {
        if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复元素
        
        int left = i + 1, right = nums.size() - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum < 0) {
                ++left; // 需要增加数值,左指针右移
            } else if (sum > 0) {
                --right; // 需要减小数值,右指针左移
            } else {
                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;
            }
        }
    }
    return result;
}

int main() {
    vector<int> nums = {-1, 0, 1, 2, -1, -4};
    vector<vector<int>> triples = threeSum(nums);
    
    for (const auto& triple : triples) {
        cout << "[" << triple[0] << "," << triple[1] << "," << triple[2] << "]" << endl;
    }

    return 0;
}

        时间复杂度:O(n^2)
        空间复杂度:O(1)

        push_back 正在向 result 向量的末尾添加一个新元素,这个新元素是由 nums[i]nums[left]nums[right] 构成的一个 vector<int>

打卡

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

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

相关文章

openGauss学习笔记-214 openGauss 性能调优-确定性能调优范围

文章目录 openGauss学习笔记-214 openGauss 性能调优-确定性能调优范围214.1 性能因素214.2 调优范围确定 openGauss学习笔记-214 openGauss 性能调优-确定性能调优范围 数据库性能调优通常发生在用户对业务的执行效率不满意&#xff0c;期望通过调优加快业务执行的情况下。正…

机器学习的整个流程

机器学习的整个流程定义了数据科学团队执行以创建和交付机器学习模型的工作流。此外&#xff0c;机器学习流程还定义了团队如何协作合作&#xff0c;以创建最有用的预测模型。 机器学习high level的流程 机器学习流程的关键步骤包括问题探索&#xff08;Problem Exploration&a…

《dx12 龙书》第四部分学习笔记——预备知识(下)

7、多重采样技术的原理 由于屏幕中显示的像素不可能是无穷小的&#xff0c;所以并不是任意一条直线都能在显示器上“平滑”而完美地呈现出来。即为以像素矩阵 &#xff08;matrix of pixels&#xff0c; 可以理解为“像素2D数组”&#xff09;逼近直线的方法所产生的“阶梯” &…

C# 使用 MailKit 接收邮件(附demo)

C# 使用 MailKit 接收邮件&#xff08;附demo&#xff09; 介绍安装包&#xff08;依赖&#xff09;案例简单代码 获取附件核心代码完整代码 介绍一下POP3 介绍 MailKit 是一个开源的 C# 邮件处理库&#xff0c;用于在应用程序中发送和接收电子邮件。它提供了一个强大且易于使…

linux实时调度

面对陌生的知识体系&#xff0c;应该从什么角度来 简介 一、进程管理基本概念 在单处理器系统上&#xff0c;在给定时刻只有一个程序可以运行&#xff0c;在多处理器系统上&#xff0c;可以真正并行运行的进程数据&#xff0c;取决于物理CPU的数目&#xff1b; 进程优先级 …

jss/css/html 相关的技术栈有哪些?

js 的技术组件有哪些&#xff1f;比如 jQuery vue 等 常见的JavaScript技术组件&#xff1a; jQuery&#xff1a; jQuery是一个快速、小巧且功能丰富的JavaScript库&#xff0c;用于简化DOM操作、事件处理、动画效果等任务。 React&#xff1a; React是由Facebook开发的用于构…

FPGA高端项目:IMX327 MIPI 视频解码 USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构 6、vivado工程详解FPGA逻辑设计 7、工…

docker 网络模型

一、docker的网络模型分为四种 【1】Host(与宿主机共享一个网络)&#xff0c;宿主机的localhost 及 容器内的localhost 【2】Bridge(与宿主机共享一个局域网&#xff0c;有自己的网络&#xff1b;docker运行默认Bridge)&#xff1b;容器内localhost不是宿主机localhost 【3】…

PyTorch 2.2 中文官方教程(五)

对抗性示例生成 原文&#xff1a;pytorch.org/tutorials/beginner/fgsm_tutorial.html 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 注意 点击这里下载完整的示例代码 作者&#xff1a; Nathan Inkawhich 如果您正在阅读本文&#xff0c;希望您能欣赏一些机器学习…

MySQL | DDL 里 TEXT、JSON 类型字段不设置默认值

JSON、BLOB、TEXT 等类型字段的默认值需要设置为表达式&#xff0c;但在旧版本&#xff08;8.0.13&#xff09;前只支持设置字面量&#xff0c;参考官方文档&#xff1a;MySQL :: MySQL 8.0 Reference Manual :: 11.6 Data Type Default Values。 PS&#xff1a;还有说是考虑减…

【刷题题解】编辑距离

给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 这道题也是&#xff0c;一眼动态规划&#xff0c;乍一看感觉很复杂&#xff0c;仔细思考…

4.0 HDFS 配置与使用

之前提到过的 Hadoop 三种模式&#xff1a;单机模式、伪集群模式和集群模式。 单机模式&#xff1a;Hadoop 仅作为库存在&#xff0c;可以在单计算机上执行 MapReduce 任务&#xff0c;仅用于开发者搭建学习和试验环境。 伪集群模式&#xff1a;此模式 Hadoop 将以守护进程的…

2020年通信工程师初级专业实务真题

文章目录 一、第1章 现代通信网概述&#xff1a;信令网、同步网、管理网。第10章 通信业务&#xff1a;通信产业链&#xff0c;通信终端的分类&#xff0c;通信业务的定义及分类二、第3章 接入网&#xff1a;无线接入网的优点&#xff0c;接入网的接口&#xff08;UNI&#xff…

tar包部署nginx

理论部分 1.基本概念​ Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理Web服务器&#xff0c;同时也提供了IMAP、POP3、SMTP服务。中国大陆使用Nginx网站用户有百度、京东、新浪、网易、腾讯、淘宝等。 2.主要用途​ 在连接高并发的情况下&#xff0c;N…

JavaWeb之HTML-CSS --黑马笔记

什么是HTML ? 标记语言&#xff1a;由标签构成的语言。 注意&#xff1a;HTML标签都是预定义好的&#xff0c;HTML代码直接在浏览器中运行&#xff0c;HTML标签由浏览器解析。 什么是CSS ? 开发工具 VS Code --安装文档和安装包都在网盘中 链接&#xff1a;https://p…

全新 鸿蒙系统

一&#xff0c; 开发框架 基础 二&#xff0c; 官网地址 文档开发&#xff1a;华为HarmonyOS智能终端操作系统官网 | 应用设备分布式开发者生态 三&#xff0c;基础了解 鸿蒙系统是基于 js 和 ts 衍生出来的一个东西 要学 arkts 就要学习 js 和 ts 语法 四&#xff0c…

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(二)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第三章&#xff1a;分类 在第一章中&#xff0c;我提到最常见的监督学习任务是回归&#xff08;预测值&#xff09;和分类&#…

仓储物流系统架构平台的设计与实践

随着电子商务和供应链管理的迅速发展&#xff0c;仓储物流系统在现代商业中扮演着至关重要的角色。一个高效、稳定的仓储物流系统能够极大地提升企业的运营效率和客户满意度。本篇博客将探讨仓储物流系统架构平台的设计与实践&#xff0c;介绍其核心组成和关键考虑因素。 ### …

[技术杂谈]如何下载vscode历史版本

网站模板&#xff1a; https://code.visualstudio.com/updates/v1_85 如果你想下载1.84系列可以访问https://code.visualstudio.com/updates/v1_84​​​​​​ 然后看到&#xff1a; 选择对应版本下载即可&#xff0c;我是windows x64系统选择x64即可开始下载

开源节点框架STNodeEditor使用

节点&#xff0c;一般都为树形Tree结构&#xff0c;如TreeNode&#xff0c;XmlNode。 树形结构有其关键属性Parent【父节点】&#xff0c;Children【子节点】 LinkedListNode为链表线性结构&#xff0c;有其关键属性Next【下一个】&#xff0c;Previous【上一个】&#xff0c…