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

2023-11-21 06:12
文章标签 算法 哈希 三数 重版

本文主要是介绍【算法-哈希表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: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 取 aif(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 取 aif (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,期待与你共同进步!

这篇关于【算法-哈希表4】 三数之和(去重版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/400381

相关文章

Java如何将文件内容转换为MD5哈希值

《Java如何将文件内容转换为MD5哈希值》:本文主要介绍Java如何将文件内容转换为MD5哈希值的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java文件内容转换为MD5哈希值一个完整的Java示例代码代码解释注意事项总结Java文件内容转换为MD5

使用雪花算法产生id导致前端精度缺失问题解决方案

《使用雪花算法产生id导致前端精度缺失问题解决方案》雪花算法由Twitter提出,设计目的是生成唯一的、递增的ID,下面:本文主要介绍使用雪花算法产生id导致前端精度缺失问题的解决方案,文中通过代... 目录一、问题根源二、解决方案1. 全局配置Jackson序列化规则2. 实体类必须使用Long封装类3.

Springboot实现推荐系统的协同过滤算法

《Springboot实现推荐系统的协同过滤算法》协同过滤算法是一种在推荐系统中广泛使用的算法,用于预测用户对物品(如商品、电影、音乐等)的偏好,从而实现个性化推荐,下面给大家介绍Springboot... 目录前言基本原理 算法分类 计算方法应用场景 代码实现 前言协同过滤算法(Collaborativ

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1