算法力扣刷题记录 十九【15. 三数之和】

前言

哈希篇,继续。
记录 十九【15. 三数之和】


一、题目阅读

给你一个整数数组 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 。	//这两行虽然i,j,k不一样,但是3个元素是:-1,-1,0,如果都输出,认为[-1,-1,0]重复
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000
-105 <= nums[i] <= 105

二、尝试实现

思路

(陷入哈希法使用,方法单一)
(1)从提示:数组长度不算小,每个元素最大为105,求和之后,值也不小。从两数之和那道题思路,第一步,确定使用哈希法。(这是一种思路陷井,没有反应出来别的方法(参考所学的双指针法))

  • 确定哈希法,接下来选择哈希结构。

(2)刚刚完成“四数相加”的题目,所以考虑求a+b作为key,对应元素对作为value,使用map结构。然后遍历nums,找到c的相反数。

  • 因为不是找下标索引,也不是计数,题目还不让重复。value的结构用二维数组?但是push_back还要判断重复。很复杂,value是二维数组行不通
  • 不想让元素对重复,用一个大的unordered_map,map中每个类型是vector< int >,可是[1,0]和[0,1]算不同的vector,都能放到unordered_map中,也没有做到不重复。行不通
  • 换一种key-value思路,看(3)。

(3)先把整个nums重复的元素去掉,但是重复元素可以求和,所以得记录重复次数。所以用一个map结构(还没有说顺序),key是nums元素,value是元素出现次数。key不能重复,所以排除multimap。那么用unordered_map还是map?继续(4)分析。

(4)怎么在一个map中找到 三个元素和为0?肯定得遍历。

  • 如果用unorder_map无序,无法知道下一个元素是什么?下一个元素是变大?还是变小?很杂乱。所以确定结构是map:有序(元素从小到大排过,能知道下一个元素一定是增大的),不重复。定义:map<int,int> nums_nondup;
  • 解决遍历:
    • 最外层是i,在nums_nondup中元素从小到大依次往后;
    • 第二层是j:j从i的下一个开始吗?(不完全是)
      • 当元素只出现一次,j从i的下一个开始。
      • 当元素只出现多次,j从i开始。因为重复元素得有个和。示例一中:-1重复,和为-2时有[-1,-1]。
      • 所以j指向的nums一定 大于/等于 i指向的nums
    • 第三层是it指针,也就是第三个元素。
      • 得找it = nums_nondup.find(0- i->first - j->first);

      • 如果it == nums.end(),肯定下一轮;

      • 当it != nums.end(),找到元素(还要去重):

          1.如果it指向的元素 > j指向的元素,肯定没重复,因为没遍历到那里。可以push到返回值中;
          2.在it指向的元素 <= j指向的元素下:
          		A.如果(it指向的元素 = j指向的元素)&& (i指向的nums != j指向的nums):得看j元素有没有多出现?至少出现2次,才能nums[j] = nums[k];可以push到返回值中;
          		B.如果(it指向的元素 = j指向的元素)&& (i指向的nums == j指向的nums):如示例3,得看元素是不是至少出现3次。才能nums[i]=nums[j] = nums[k];可以push到返回值中;
          		C.剩下情况:不可以push因为已经遍历过。
        

所以如何不重复是一个难点。判断可不可以push到返回值中,也有情况讨论。

代码实现(测试通过)

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        map<int,int> nums_nondup;//元素值作为key,出现次数作为value,按顺序
        map<int,int>::iterator j;
        //去重,记录出现的次数。
        for(int x: nums){
            nums_nondup[x]++;
        }
        
        //开始遍历,i指针是最外层循环
        for(auto i = nums_nondup.begin();i != nums_nondup.end();++i){
            if(i->second > 1){ //出现多次
                j = i;
            }else{
                j = next(i,1);
            }
			//第二层,nums[j]
            for(j;j != nums_nondup.end();++j){  
                auto it = nums_nondup.find(0- i->first - j->first);	//找nums[k]
                if(it != nums_nondup.end()){	//能不能push_back,也要分情况判断
                    if((it->first > j->first)){
                        result.push_back({i->first,j->first,it->first});
                    }else if(it == j && j != i && it->second >= 2){
                        result.push_back({i->first,j->first,it->first});
                    }else if(j == i && it == j && it->second >= 3){
                        result.push_back({i->first,j->first,it->first});
                    }
                }
            }
            
        }
        return result;
    }

};

       

弯路

(1)因为“四数相加”的题目,是把nums1和nums2的和作为key,对应下标对;在思路(3)没有nums去重之前的思路,在去重之后,还想尝试。可是仍然有重复。

  • 解释:以示例一nums = [-1,0,1,2,-1,-4],

      思路(3)之后得到nums_nondup内的元素:<-4,1>、<-1,2>、<0,1>、<1,1>、<2,1>。
      用另一个unordered_map<int,vector<vector<int>>>  sum;中key是不重复元素之和,value对应元素对。(每个key对应的元素对不可能重复,因为nums_nondup去重过)
      在sum结构中:
      	和为0:对应元素对有[-1,1];
      	和为1:对应元素对有[0,1];
      当在nums_nondup遍历nums[k]第三个元素时:遍历到0,找到和为0,0加入到[-1,1]中。遍历到-1,找到和为1,-1加入到[0,1]。如果push到返回数组中,仍然得到重复三元组。(因为输出顺序不重要,[-1,1,0]和[0,1,-1]认为是一样的。)
    

总结:这一步不成功,所以回归到思路(4)直接遍历。


三、代码随想录学习

学习内容

双指针法

回归题目,传的参数一个数组nums,三个元素来源于同一个数组。也就是在一个数组中找、进行操作;数组操作方法在数组篇学到双指针法

双指针法:组成一个区间,逐步移动区间大小或者位置。

双指针思路

  • 最外层肯定是nums遍历,从第一个到最后一个。需要排序:从小到大。有了顺序,才知道求和比0大,指针怎么移;比0小指针怎么移。
  • 内层j和k,分别是j = i+1;k = nums.size()-1;如果nums[i] + nums[j] + nums[k] > 0,k - -;如果nums[i] + nums[j] + nums[k] < 0,j++;
  • 如何去重?i,j,k都得去掉重复的值。
    • i去重:nums[i] == nums[i-1],当前i和前一个比。
    • j去重:num[j] == nums[j-1],当前j和前一个比。
    • k去重:num[k] == nums[k+1],当前k和后一个比。

代码实现

//根据新思路再次实现
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> result;

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

            int j = i+1;
            int k = nums.size()-1;
            while(j < k){
                if(nums[i] +nums[j] +nums[k] > 0){
                    k--;
                }else if(nums[i] +nums[j] +nums[k] < 0){
                    j++;
                }else{
                    result.push_back({nums[i],nums[j],nums[k]});    //放的是元素值
                    j++;	//先移动,再去重。
                    k--;
                    while(j < k && nums[k] == nums[k+1]){
                        k--;
                    }
                    while(j < k && nums[j] == nums[j-1]){
                        j++;
                    }
                }
            }   
        }
        return result;
    }
};

实现区别:

else部分,是先移动还是先去重?
如果是先移动,k和后一个元素比;j和前一个元素比。(个人实现)
如果是先去重,k和前一个元素比,直到前一个和当前不一样,指向重复元素的第一个;j和后一个元素比,直到后一个和当前不一样,指向重复元素的最后一个。再同时k--;j++。(参考)

四、总结

(1)思路应该打开,只要是学过的方法,都可以尝试。可能相近题目“两数之和”、“四数之和”、“三叔之和”背后不是一种方法。

  • “两数之和”:哈希法,返回下标索引;结构unordered_map.
  • “四数之和”:哈希法,返回数量;结构unordered_map.
  • “三数之和”:双指针法优先;哈希法也可以。

(欢迎指正,转载表明出处)

  • 43
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们来进行一道综合练习。 假设我们正在设计一个游戏,游戏中有多种角色,每种角色都有自己的属性和行为。我们来考虑如何使用Java继承和多态来实现这个游戏。 首先,我们可以定义一个抽象类`Character`,表示所有角色的基类,其中包含角色的基本属性和方法: ```java public abstract class Character { protected String name; // 角色名 protected int level; // 等级 protected int health; // 生命值 protected int mana; // 法力值 public Character(String name, int level, int health, int mana) { this.name = name; this.level = level; this.health = health; this.mana = mana; } public abstract void attack(); // 攻击方法 public abstract void defend(); // 防御方法 // getter和setter方法 // ... } ``` 然后,我们可以定义具体的角色类,例如战士`Warrior`和法师`Mage`,它们分别继承自`Character`类,并实现自己的攻击和防御方法: ```java public class Warrior extends Character { private int strength; // 力量属性 public Warrior(String name, int level, int health, int mana, int strength) { super(name, level, health, mana); this.strength = strength; } @Override public void attack() { System.out.println("战士" + name + "使用大剑攻击敌人!"); } @Override public void defend() { System.out.println("战士" + name + "使用盾牌防御敌人的攻击!"); } // getter和setter方法 // ... } public class Mage extends Character { private int intelligence; // 智力属性 public Mage(String name, int level, int health, int mana, int intelligence) { super(name, level, health, mana); this.intelligence = intelligence; } @Override public void attack() { System.out.println("法师" + name + "释放火球术攻击敌人!"); } @Override public void defend() { System.out.println("法师" + name + "使用魔法盾防御敌人的攻击!"); } // getter和setter方法 // ... } ``` 最后,我们可以在游戏中创建不同的角色对象,并进行攻击和防御操作: ```java public class Game { public static void main(String[] args) { Character warrior = new Warrior("张三", 10, 100, 50, 20); Character mage = new Mage("李四", 10, 80, 100, 30); warrior.attack(); mage.defend(); } } ``` 以上就是一个简单的继承和多态的综合练习,通过这个例子,我们可以发现继承和多态能够很好地实现代码的复用和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值