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

难度参考

        难度:中等

        分类:数组

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

题目

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

打卡

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

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

相关文章

视觉SLAM十四讲学习笔记(一)初识SLAM

目录 前言 一、传感器 1 传感器分类 2 相机 二、经典视觉 SLAM 框架 1 视觉里程计 2 后端优化 3 回环检测 4 建图 5 SLAM系统 三、SLAM 问题的数学表述 四、Ubuntu20.04配置SLAM十四讲 前言 SLAM: Simultaneous Localization and Mapping 同时定位与地图构建&#…

VScode+PlatformIO 物联网Iot开发平台环境搭建

1.vscode &#xff08;1&#xff09;安装platformIO插件 &#xff08;2&#xff09;新建项目或导入已有的arduino项目 Name&#xff1a;需要填写你项目的名称&#xff1b; Board&#xff1a;点开是一个下拉框&#xff0c;但是可以输入你想要的开发板&#xff0c;这里选择&quo…

24.Android中的列表--ListView

ListView 1.简单列表--ArrayAdapter <?xml version"1.0" encoding"utf-8"?> <ScrollView xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto"xmlns:tools&qu…

大数据分析|大数据分析的十大应用领域

有许多技术可用于分析大数据。这项工作介绍了BDA适用的各种分析技术领域如下。 &#xff08;1&#xff09;社会分析 社交分析是实时数据分析中一个重要且不断发展的分析方法。它分为社交网络(例如&#xff0c;Facebook和LinkedIn)&#xff0c;博客(例如&#xff0c;Blogger和W…

ERP 系统架构的设计与实践总结

企业资源计划&#xff08;ERP&#xff09;系统是一种集成多个业务功能的综合性软件解决方案。在设计和实践 ERP 系统架构时&#xff0c;需要考虑诸多因素&#xff0c;以确保系统能够满足企业的需求&#xff0c;并提供高效、可靠、安全的服务。本文将介绍一些关键的设计原则和实…

101 C++内存高级话题 内存池概念,代码实现和详细分析

零 为什么要用内存池&#xff1f; 从前面的知识我们知道&#xff0c;当new 或者 malloc 的时候&#xff0c;假设您想要malloc 10个字节&#xff0c; char * pchar new char[10]; char *pchar1 malloc(10); 实际上编译器为了 记录和管理这些数据&#xff0c;做了不少事情&…

vue中 日期选择--本日、本周、本月、本年选择器实现(基于elementui)

效果图&#xff1a; 由于项目需要图标统计展示&#xff0c;需要日期美观化选择如上图所示&#xff0c;代码如下&#xff1a; <template><div class"el-page body"><el-row><el-col class"statistic-analysis-report-style" :span&qu…

【Linux进程间通信】匿名管道

【Linux进程间通信】匿名管道 目录 【Linux进程间通信】匿名管道进程间通信介绍进程间通信目的进程间通信发展进程间通信分类 管道用fork来共享管道原理站在文件描述符角度——深度理解管道站在内核角度——管道本质 匿名管道在myshell中添加管道的实现&#xff1a;管道读写规则…

【iOS ARKit】环境反射

环境反射 在使用 iOS AR中 渲染虚拟物体时&#xff0c;RealityKit 默认使用了一个简单的天空盒&#xff08;Skybox&#xff0c;即IBL环境资源贴图&#xff09;&#xff0c;所有带反射材质的物体默认会对天空盒产生反射。 但在AR 中&#xff0c;使用IBL 技术实现的天空盒反射有一…

【快速上手QT】01-QWidgetQMainWindow QT中的窗口

总所周知&#xff0c;QT是一个跨平台的C图形用户界面应用程序开发框架。它既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;当然我们用到QT就是要做GUI的&#xff0c;所以我们快速上手QT的第一篇博文就讲QT的界面窗口。 我用的IDE是VS2019&#xff0c;使用QTc…

神经网络 | 基于 CNN 模型实现土壤湿度预测

Hi&#xff0c;大家好&#xff0c;我是半亩花海。在现代农业和环境监测中&#xff0c;了解土壤湿度的变化对于作物生长和水资源管理至关重要。通过深度学习技术&#xff0c;特别是卷积神经网络&#xff0c;我们可以利用过去的土壤湿度数据来预测未来的湿度趋势。本文将使用 Pad…

Postgresql体系结构

client连接PostgreSQL过程&#xff1a; 1、客户端发起请求 2、主服务postmaster进程负责服务器是否接受客户端的host通信认证&#xff0c;服务器对客户端进行身份鉴别 3、主服务进程为该客户端单独fork一个客户端工作进程postgres 4、客户端与postgres进程建立通信连接&#xf…

【算法与数据结构】647、516、LeetCode回文子串+最长回文子序列

文章目录 一、647、回文子串二、516、最长回文子序列三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、647、回文子串 思路分析&#xff1a;判断一个字符串是否为回文串那么必须确定回文串的所在区间&#xff0c;而一维…

SQL--DDL

全称 Structured Query Language&#xff0c;结构化查询语言。操作关系型数据库的编程语言&#xff0c;定义了 一套操作关系型数据库统一标准。 1 SQL通用语法 在学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的同于语法。 1). SQL语句可以单行或多行书写&#xff0…

处理SERVLET中的错误和异常

处理SERVLET中的错误和异常 应用服务器服务客户机请求时可能会遇到一些问题,如找不到所请求的资源或运行中的servlet引发异常。例如,在线购物门户中如果用户选择了当前缺货的物品要放入购物车中,就会出现问题, 这种情况下,浏览器窗口中将显示错误消息。您可以在servlet中…

Maven工程的配置及使用

一、Maven章节 Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具 1.1、maven的作用 1&#xff09;依赖管理&#xff1a; 方便快捷的管理项目依赖的资源包&#xff08;jar包&#xff09;避免版本冲突 2&#xff09;统一项目结构&…

STC系列单片机的中断系统

目录 一、中断系统的定义 二、STC15系列单片机的中断请求源及结构图 三、中断查询表以及触发方式 四、在keil c中如何声明中断函数 五、外部中断 六、基于STC15芯片实战中断系统的使用 &#xff08;1&#xff09;外部中断2/外部中断3来检测门的开关状态 &#xff08;2&a…

【C++】- 继承(继承定义!!基本格式!切片概念!!菱形继承详解!)

继承 了解继承继承的定义基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承和友元菱形继承和菱形虚拟继承 了解继承 继承机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&a…

优化 IT 支出和消除浪费的 8 种主要方法

不懈追求最佳 IT 支出对于任何组织的长期可持续发展和成功都至关重要。在这个技术快速进步的时代&#xff0c;您必须做出明智的决策&#xff0c;消除浪费&#xff0c;同时最大限度地提高技术投资的价值。 从进行 IT 成本分析到采用敏捷预算和技术标准化&#xff0c;这些策略对…

关于服务器解析A记录和CNAME记录的分析

内容提要: 大致讲下理解,dns域名解析这一块 0 . 问题来源 最近搞了一个七牛云上传,然后需要配置融合cdn加速,也就是可以加速域名,中间有一部需要CNAME 域名,也就是将七牛云提供的域名CNAME一下,查阅资料其实就是起一个别名,好访问而已. 方便我们访问云存储,达到加速的效果. …