【算法-哈希表4】 三数之和(去重版)

今天,带来哈希相关算法的讲解。文中不足错漏之处望请斧正!

理论基础点这里


三数之和

分析题意

这就是三数之和去重版嘛。

题意转化

求三元组, 满足每个元素相加为0,其中每个元素下标不同;而得到的三元组不能重复。

  • 构成三元组的三个元素下标不同
  • 三元组和三元组之间不同
    怎么理解“三元组和三元组之间不同”:[-1, 0, 1][-1, 0, 1] 是相同的,尽管构成这两个三元组的元素不同, 但它俩是相同的。

解决思路

1. 哈希法

这道题如果用哈希法,和之前差别不大:两层for遍历nums,一层取a,一层取b,求和映射到哈希表。然后再遍历nums,看看是否有(0-(a+b))这样的元素。

但是这道题用哈希法是比较复杂的,因为去重操作比较麻烦。

2. 双指针法

大概的思路是这样:

  • 排序
  • 给三个指针
    • i,用for控制,固定往后走,取a
    • left,初始化为i后面的元素,取b
    • right,是最后一个元素,取c
  • nums[i] + nums[left] + nums[right] > 0:三个数的和太大了,我们需要让某个数变小,所以--right
  • nums[i] + nums[left] + nums[right] < 0:三个数的和太小了,我们需要让某个数变大,所以++left
  • nums[i] + nums[left] + nums[right] == 0:收集结果

再简单点说:

  • 排序
  • i固定取a
  • 固定了a后,移动left和right取b和c,收割符合条件的结果集

排序带来的有序性让我们拿取元素的时候做到有的放矢。

怎么去重?

对三元组去重,只需要保证我们每次取完结果集后,把已经取过的结果集跳过就行。或者说,只要保证所有三元组的第一、二、三个元素的值不等就行。

首先,a的去重:排序后的nums,只要a不和相邻元素相等,就不是重复的。

下面两种选哪种呢?

  1. if(nums[i] == nums[i + 1]) continue;
  2. if(nums[i] == nums[i - 1]) continue;

这不是一样的吗?不一样:第一种是判断num[i]和它后面的一个元素是否相等,但这有可能会把某个结果给去掉了。比如[-1, -1, 2],这是符合条件的结果集,但是当i=0,你判断nums[i] == nums[i + 1](-1 == -1),就直接跳过了,不对。
简单理解,如果有一个结果集是[-1, b, c],那往后就不能再出现其他三元组是以[-1]开头的。

其次,b和c的去重。看这个例子:

[0, -1, -1, -1, 1, 1, 1],这个例子只有一个结果集:[0, -1, 0]当我们收获了第一个结果集[0, -1, 1]后,left和right按理讲都往里面收缩一下。但不一定只是一下,如果只收缩一下,那就可能会重复收割相同的结果集。所以,当left和right收缩后还是重复,就继续收缩,直到不重复。

对a的去重保证:在同一轮循环中不添加相同的三元组。
对bc的去重保证:在整体结果中不添加已经存在的三元组。

编程实现

哈希

class Solution {
public:
    // 分析题意: 三数之和 去重版
    // 题意转化: 求三元组, 满足每个元素相加为0, 其中每个元素下标不同; 而得到的三元组不能重复(不能完全相同)
    // 编程实现
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;

        sort(nums.begin(), nums.end());

        for (int i = 0; i < nums.size(); ++i) { // 取a
            if (nums[i] > 0) break;
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            unordered_set<int> set; // 固定a, 取所有的b放入set
            for (int j = i + 1; j < nums.size(); ++j) {
                if (j > i + 2 && 
                    nums[j] == nums[j - 1] && 
                    nums[j - 1] == nums[j - 2]) continue; // 对b的去重(相邻元素只取第一次出现的)
                    
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) { // 找c
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c); // 对c的去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }

        return result;
    }
};

双指针

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int i = 0;
        int left = 0;
        int right = 0;
        vector<vector<int>> result;

        sort(nums.begin(), nums.end());
        for (auto &e : nums) cout << e << " ";
        cout << endl;

        for (i = 0; i < nums.size(); ++i) { // i 取 a
            if(nums[i] > 0) return result; //剪枝: 排序后,第一个数(min)都大于0,任何组合都不能等于0了
            if (i > 0 && nums[i] == nums[i - 1]) continue; // a的去重

            left = i + 1;
            right = nums.size() - 1;

            // 对于当前a, 不断用left, right 取 b, c, 看看能不能收割结果集
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) --right;
                else if (sum < 0) ++left;
                else {
                    while (left < right && nums[left] == nums[left + 1]) ++left; // b的去重
                    while (left < right && nums[right] == nums[right - 1]) --right; // c的去重

                    result.push_back({ nums[i], nums[left], nums[right] });
                    ++left;
                    --right;                    
                }
            }
        }

        return result;
    }
};

不对a去重:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int i = 0;
        int left = 0;
        int right = 0;
        vector<vector<int>> result;

        sort(nums.begin(), nums.end());
        for (auto &e : nums) cout << e << " ";
        cout << endl;

        for (i = 0; i < nums.size(); ++i) { // i 取 a
            // if (i > 0 && nums[i] == nums[i - 1]) continue; 

            left = i + 1;
            right = nums.size() - 1;

            // 对于当前a, 不断用left, right 取 b, c, 看看能不能收割结果集
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) --right;
                else if (sum < 0) ++left;
                else {
                    while (left < right && nums[left] == nums[left + 1]) ++left;
                    while (left < right && nums[right] == nums[right - 1]) --right;

                    result.push_back({ nums[i], nums[left], nums[right] });
                    printf("new result: i=%d left=%d right=%d\n\t\
                            nums[i]=%d nums[left]=%d nums[right]=%d\n", 
                            i, left, right, nums[i], nums[left], nums[right]);
                    ++left;
                    --right;                    
                }
            }
        }

        return result;
    }
};

在这里插入图片描述

不对bc去重:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int i = 0;
        int left = 0;
        int right = 0;
        vector<vector<int>> result;

        sort(nums.begin(), nums.end());
        for (auto &e : nums) cout << e << " ";
        cout << endl;

        for (i = 0; i < nums.size(); ++i) { // i 取 a
            if (i > 0 && nums[i] == nums[i - 1]) continue; 

            left = i + 1;
            right = nums.size() - 1;

            // 对于当前a, 不断用left, right 取 b, c, 看看能不能收割结果集
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) --right;
                else if (sum < 0) ++left;
                else {
                    // while (left < right && nums[left] == nums[left + 1]) ++left;
                    // while (left < right && nums[right] == nums[right - 1]) --right;

                    result.push_back({ nums[i], nums[left], nums[right] });
                    printf("new result: i=%d left=%d right=%d\n\t\
                            nums[i]=%d nums[left]=%d nums[right]=%d\n", 
                            i, left, right, nums[i], nums[left], nums[right]);
                    ++left;
                    --right;                    
                }
            }
        }

        return result;
    }
};

在这里插入图片描述


今天的分享就到这里了,感谢您能看到这里。

这里是培根的blog,期待与你共同进步!

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

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

相关文章

【20年扬大真题】删除字符串s中的所有空格

【20年扬大真题】 删除字符串s中的所有空格 代码思路&#xff1a; 可以定义一个辅助的字符数组tmp&#xff0c;一边遍历字符串s&#xff0c;一边用tmp暂存s中的非空格元素。 遍历完s之后&#xff0c;再把tmp中的元素赋给字符串s即可 #include<stdio.h> #define MaxSize…

栈和队列【详解】

目录 一、栈 1.栈的定义 2.栈的初始化 3.入栈 4.出栈 5.获取栈顶元素 6.获取栈元素的个数 7.判断栈是否为空 8.销毁栈 二、队列 1.队列的定义 2.入队 3.出队 4.获取队头元素 5.获取队尾元素 6.判断队列是否为空 7.获取队列的元素个数 8.销毁队列 前言&#xf…

el-input限制输入整数等分析

文章目录 前言1、在 Vue 中&#xff0c;可以使用以下几种方式来限制 el-input 只能输入整数1.1 设置input 的 type为number1.2 使用inputmode1.3 使用自定义指令1.4 使用计算属性1.5 使用 onafterpaste ,onkeyup1.6 el-input-number 的precision属性 总结 前言 input 限制输入…

python命令行交互 引导用户输入一个数字

代码 以下代码将在命令行中&#xff0c;引导用户选择一个数字&#xff0c;并反馈用户输入的值 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/22 15:51 file: 引导用户输入一个数字.py desc: xxxxxx """#…

D. Absolute Beauty - 思维

题面 分析 补题。配上题解的图&#xff0c;理解了很长时间&#xff0c;思维还需要提高。 对于每一对 a i a_i ai​和 b i b_i bi​&#xff0c;可以看作一个线段的左右端点&#xff0c;这是关键的一步&#xff0c;那么他们的绝对值就是线段的长度&#xff0c;对于线段相对位…

C++ MiniZip实现目录压缩与解压

Zlib是一个开源的数据压缩库&#xff0c;提供了一种通用的数据压缩和解压缩算法。它最初由Jean-Loup Gailly和Mark Adler开发&#xff0c;旨在成为一个高效、轻量级的压缩库&#xff0c;其被广泛应用于许多领域&#xff0c;包括网络通信、文件压缩、数据库系统等。其压缩算法是…

Doris数据模型的选择建议(十三)

Doris 的数据模型主要分为 3 类&#xff1a;Aggregate、Uniq、Duplicate Aggregate: Doris 数据模型-Aggregate 模型 Uniq&#xff1a;Doris 数据模型-Uniq 模型 Duplicate&#xff1a;Doris 数据模型-Duplicate 模型 因为数据模型在建表时就已经确定&#xff0c;且无法修改…

焦炉加热系统简述

烟道吸力 焦炉负压烘炉分烟道的吸力会影响立火道温度&#xff0c;具体影响因素如下&#xff1a; 烟道吸力过大会导致热量被抽走&#xff0c;使立火道温度降低。烟道吸力不足会导致烟气在烘炉内停留时间过长&#xff0c;使热量无法充分利用&#xff0c;也会导致立火道温度降低…

安防监控视频融合平台EasyCVR定制化页面开发

安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索…

元素的点击操作

元素的点击操作 .click 语法 // 单击某个元素 .click()// 带参数的单击 .click(options)// 在某个位置点击 .click(position)// 在某个位置点击&#xff0c;且带参数 .click(position, options)// 根据页面坐标点击 .click(x, y)// 根据页面坐标点击&#xff0c;且带参数 .c…

冷链运输车辆GPS定位及温湿度管理案例

1.项目背景 项目名称&#xff1a;山西冷链运输车辆GPS定位及温湿度管理案例 项目需求&#xff1a;随着经济发展带动物流行业快速发展&#xff0c;运输规模逐步扩大&#xff0c;集团为了适应高速发展的行业现象&#xff0c;物流管理系统的完善成了现阶段发展的重中之重。因此&…

Elasticsearch:FMA 风格的向量相似度计算

作者&#xff1a;Chris Hegarty 在 Lucene 9.7.0 中&#xff0c;我们添加了利用 SIMD 指令执行向量相似性计算的数据并行化的支持。 现在&#xff0c;我们通过使用融合乘加 (Fused Mulitply-Add - FMA) 进一步推动这一点。 什么是 FMA 乘法和加法是一种常见的运算&#xff0c;…

echarts实现如下图功能代码

这里写自定义目录标题 const option {tooltip: {trigger: axis},legend: {left: "-1px",top:15px,type: "scroll",icon:rect,data: [{name:1, textStyle:{color: theme?"#E5EAF3":#303133,fontSize:14}}, {name: 2, textStyle:{color: theme…

超详细 | 实验室linux服务器非root账号 | 安装pip | 安装conda

登录实验室公用服务器&#xff0c;个人账号下&#xff08;非root&#xff09;是空的&#xff0c;啥也没有&#xff0c;想安装下pip和conda。 转了一圈&#xff0c;好像没太有针对这个需求写具体博客的&#xff0c;但有挺多讲直接在root下安的&#xff08;用的应该是个人虚拟机&…

【面试HOT300】滑动窗口篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot300】进行的&#xff0c;每个知识点的修正和深入主要参…

【Vue】生命周期一文详解

目录 前言 生命周期 钩子函数使用方法 ​编辑 周期-----创建阶段 创建阶段做了些什么事 该阶段可以干什么 周期----挂载阶段 挂载阶段做了什么事 该阶段适合干什么 周期----更新阶段 更新阶段做了什么事 该阶段适合做什么 周期----销毁阶段 销毁阶段做了什么事 …

图解系列--密钥,随机数,应用技术

密钥 1.生成密钥 1.1.用随机数生成密钥 密码学用途的伪随机数生成器必须是专门针对密码学用途而设计的。 1.2.用口令生成密钥 一般都是将口令输入单向散列函数&#xff0c;然后将得到的散列值作为密钥使用。 在使用口令生成密钥时&#xff0c;为了防止字典攻击&#xff0c;需要…

【追求卓越13】算法--深度和广度优先算法

引导 前面的几个章节&#xff0c;我们介绍了树这种数据结构&#xff0c;二叉搜索树在进行查找方面比较高效&#xff1b;有二叉树演变来的堆数据结构在处理优先级队列&#xff0c;top K&#xff0c;中位数等问题比较优秀&#xff1b;今天我们继续介绍新的数据结构——图。它在解…

【20年扬大真题】编写程序,功能是给a数组输入30个数据,并以每行5个数据的形式输出

【20年扬大真题】编写程序&#xff0c;功能是给a数组输入30个数据&#xff0c;并以每行5个数据的形式输出 #include<stdio.h> int main() {int arr[30] { 0 };int i 0;printf("请输入30个数据&#xff1a;");for (i 0;i < 30;i){scanf("%d", …