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

news2024/9/21 16:31:10

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

理论基础点这里


三数之和

分析题意

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

题意转化

求三元组, 满足每个元素相加为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:
    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,期待与你共同进步!

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

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

相关文章

微信私域运营工具CRM

为什么要做微信私域&#xff1f; 客户在哪里&#xff1f;微信&#xff01;在中国&#xff0c;不论男女老少&#xff0c;90%的人每天使用微信至少5次&#xff0c;每次使用时间超过90分钟&#xff0c;已经成为像吃饭穿衣一样的生活必需品。因此&#xff0c;我们的目标客户就在微…

4-5学生分数对应的成绩

![#include<stdio.h> int main(){float score;char grade;for(int i0;i<7;i){printf("请输入成绩&#xff1a;");scanf("%f",&score);while(score>100||score<0){printf("\n输入的成绩有误&#xff0c;请重新输入&#xff1a;&quo…

进程管理(四)

管程概念及实现要旨 管程引入了条件变量condition。 wait操作,把当前进程挂到条件变量对应的阻塞队列上去,signal把条件队列上的对手进程唤醒。 注意:条件变量的signal操作和信号量的signal是有区别的。条件变量的signal可能啥都不干,如果有阻塞的进程唤醒,没有啥事都不做…

【ARM Trace32(劳特巴赫) 使用介绍 10 -- Trace32 scan dump 详细介绍】

文章目录 1.1 JTAG 测试逻辑架构1.2 D型扫描触发器1.2.1 全扫描介绍1.3 IR 寄存器1.4 TDR(Test data registers)1.4.1 TDR 的实现1.4.1.1 Bypass Register1.4.1.2 Boundary-scan register1.5 Scan Dump1.5.1 soft fusion1.1 JTAG 测试逻辑架构 图 1-1 片上测试逻辑概念图 如前面…

京东小程序:无代码开发实现API集成,连接电商平台、CRM和客服系统

无需复杂API开发&#xff0c;京东小程序连接电商平台 京东小程序平台以其全开放的生态模式&#xff0c;让商家享有京东系APP流量福利、海量SKU和开放能力&#xff0c;提升用户体验&#xff0c;同时也带来了新商机。京东小程序的最大优势在于&#xff0c;商家无需进行复杂的API…

传统词嵌入方法的千层套路

诸神缄默不语-个人CSDN博文目录 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;词嵌入是一种将词语转换为数值形式的方法&#xff0c;使计算机能够理解和处理语言数据。 词嵌入word embedding也叫文本向量化/文本表征。 本文将介绍几种流行的传统词嵌入方法。 文…

SpringBoot2—运维实用篇

目录 打包与运行 • 程序打包与运行&#xff08;Windows版&#xff09; • 程序运行&#xff08;Linux版&#xff09; 配置高级 • 临时属性设置 • 配置文件分类 • 自定义配置文件 多环境开发 多环境开发&#xff08;yaml单一文件版&#xff09; 多环境开发&am…

『亚马逊云科技产品测评』活动征文|通过lightsail一键搭建Drupal VS 手动部署

『亚马逊云科技产品测评』活动征文&#xff5c;通过lightsail一键搭建Drupal 提示&#xff1a;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚…

Debian系列的Linux发行版上部署wvp

Debian系列的Linux发行版上部署wvp 环境搭建1.Debian系列的Linux发行版上安装nginx2.安装mysql设置mysql密码修改权限sudo mysql ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)配置相关navicat 连接不上 报错 10061navicat 连接报错 1130 -…

【考研数学】正交变换后如果不是标准型怎么办?| 关于二次型标准化的一些思考

文章目录 引言一、回顾二次型的定义是什么&#xff1f;什么叫标准二次型&#xff1f;怎么化为标准型&#xff1f; 二、思考写在最后 引言 前阵子做了下 20 年真题&#xff0c;问题大大的&#xff0c;现在订正到矩阵的第一个大题&#xff0c;是关于二次型正交变换的。和常规不同…

从硬件到软件:揭秘磁盘结构和文件系统组织

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了从磁盘的硬件结构&#xff0c;再到操作系统内部是…

STM32获取最大堆栈空间

参考 stackflow相关讨论 原理 通过参考链接&#xff0c;可知探测Stack的最大深度是先在stack中填充不常用的特定值&#xff0c;然后实时检测这些值哪些发生了变化&#xff0c;变化的表示使用到了这个空间&#xff0c;如果程序完全遍历后&#xff0c;有些值还是没变&#xff…

Linux下安装两个版本python

1 python下载&#xff1a; 官网地址&#xff1a;Download Python | Python.org 第一&#xff1a;点击下载如下图&#xff1a; 第二&#xff1a;找到对应的python版本源码包&#xff1a; 点击右键复制下载地址&#xff0c;如下图 例如我的是&#xff1a;https://www.python.org/…

openGauss学习笔记-128 openGauss 数据库管理-设置透明数据加密(TDE)

文章目录 openGauss学习笔记-128 openGauss 数据库管理-设置透明数据加密&#xff08;TDE&#xff09;128.1 概述128.2 前提条件128.3 背景信息128.4 密钥管理机制128.5 表级加密方案128.6 创建加密表128.7 切换加密表加密开关128.8 对加密表进行密钥轮转 openGauss学习笔记-12…

docker更换国内源

docker更换国内源 1、编辑Docker配置文件 在终端中执行以下命令&#xff0c;编辑Docker配置文件&#xff1a; vi /etc/docker/daemon.json2、添加更新源 在打开的配置文件中&#xff0c;添加以下内容&#xff1a; {"registry-mirrors": ["https://hub-mirror…

MAC上修改mysql的密码(每一步都图文解释哦)

当你想要连接本机数据库时&#xff0c;是不是有可能突然忘记了自己的数据库密码? 在此文中&#xff0c;我们来详细解决一下如何去修改自己的数据库密码&#xff0c;并使用Navicat来连接测试 1.停止mysql服务 打开终端&#xff0c;键入命令,将mysql服务先停止掉&#xff0c;…

OpenAI内斗剧情反转!微软力保ChatGPT之父回归?

美东时间11月17日下午&#xff0c;全球最热门的聊天机器人ChatGPT开发商OpenAI宣布了一项重磅管理层调整&#xff0c;Sam Altman将辞去CEO一职&#xff0c;并离开公司董事会。 Altman被踢出的消息除了让业界担心会影响该公司未来发展外&#xff0c;OpenAI另一位共同创办人暨总裁…

百分点科技|怎样做数据运营,才能让数据中台真正用起来?

导读&#xff1a;大多数企业用户已完成数据平台初步建设工作&#xff0c;但数据在业务运营和管理中没有发挥应有价值。数据开发工作繁重&#xff0c;数据质量问题严重&#xff0c;IT、数据和业务协作不畅&#xff0c;诸多问题依然困扰着企业用户的IT部门和数据部门。数据运营成…

Apache POI(Java)

一、Apache POI介绍 Apache POI是Apache组织提供的开源的工具包&#xff08;jar包&#xff09;。大多数中小规模的应用程序开发主要依赖于Apache POI&#xff08;HSSF XSSF&#xff09;。它支持Excel 库的所有基本功能; 文本的导入和导出是它的主要特点。 我们可以使用 POI 在…

5分钟制作可直接导入GPTs知识库中的自动爬虫

它能从一个或多个网址爬取网站内容&#xff0c;然后生成JSON文件格式。这样爬取的内容可以直接导入到GPTs知识库中&#xff0c;方便你创建自定义知识库的GPTs。比如你有自己的网站或者资料库&#xff0c;但是整理起来太麻烦&#xff0c;就可以使用这个工具。 主要功能&#xf…