[双指针|模拟] leetcode 15 三数之和

[双指针|模拟] leetcode 15 三数之和

1.题目

题目链接
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

2.分析

2.1.直观思路

直接利用三层for循环枚举a、b、c,然后观察a + b + c是否为0:

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    int n = nums.size();
    for(int i = 0; i < n; i++) {
        for(int j = i + 1; j < n; j++) {
            for(int k = j + 1; k < n; k++) {
                if(nums[i] + nums[j] + nums[k] == 0) {
                    res.push_back({nums[i], nums[j], nums[k]});
                }
            }
        }
    }
    return res;
}

这种做法在允许重复的情况下是没有问题的,然而题目要求不能有重复。例如对样例而言,上述代码的输出会多出一个[0,1,-1],与[-1,0,1]重复。
那么如何解决重复问题呢?

2.2.问题转化

三数之和问题可以转化为两数之和问题。即:

a + b + c = 0 =>
b + c = -a

也就是找到两数b、c的和为-a。因此,遍历a可能的n种情况即可将问题转化为相对简单两数之和问题:

//遍历a
for (int i = 0; i < n; i++) {
	//target = -a
    int target = -nums[i];
    //遍历b、c...
    for (int j = i + 1; j < n; j++) {

	}
}

2.3.重复问题

观察我们之前重复问题出现的原因。对于输入:

[-1, 0, 1, 2, -1, -4]

-1会被遍历到两次,因此带来了重复问题。
一种解决方案是利用数据结构set,这里我们采取另一种方法——排序。可以得到:

[-4, -1, -1, 0, 1, 2]

这样,重复的数必定相邻,我们只需要判断当前遍历到的数与上一个数是否相等即可。例如判断a是否重复:

//nums[i]与上一个数nums[i - 1]相等则直接跳过
//i > 0是为了防止i - 1 < 0 导致数组越界
if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

2.4.枚举策略

对于b、c的遍历,可以简单采用原来三层for循环的思路:

//遍历b
for (int j = i + 1; j < n; j++) {
	//遍历c
	for (int k = j + 1; k < n; k++) {
	
	}
}

这样做的时间复杂度是O(n2),有没有更好的做法?
一种方式是将b、c改为一左一右的双指针,即:

int k = n - 1;
//遍历b
for (int j = i + 1; j < n; j++) {
	//遍历c
	while(???){
		k--;
	}
}

这样做可以将遍历b、c的复杂度降至O(n)。
合理性证明:
令b = nums[j], c = nums[k]。如果当前b + c != -a,要想使b + c = a,有两种情况:

  • b + c < -a,此时若j右移,b增大,b + c有可能等于-a;若k左移,c减小,b + c不可能等于-a。
  • b + c > -a,此时若j右移,b增大,b + c不可能等于-a;若k左移,c减小,b + c有可能等于-a。

因此,在b + c < -a时使j右移,反之使k左移即可。这一双指针的思想与盛水最多的容器十分相似。

3.代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        //遍历a
        for (int i = 0; i < n; i++) {
            //如果nums[i]与nums[i-1]相同,则跳过,以防重复
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            //target = -a
            int target = -nums[i];
            int k = n - 1;     
            //遍历b
            for (int j = i + 1; j < n; j++) {
                //如果nums[j]与nums[j-1]相同,则跳过,以防重复
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                //如果b + c > -a,需要将c减小(即指针k左移))
                while (j < k && nums[j] + nums[k] > target) {
                    k--;
                }
                //左右指针相遇,退出
                if (j == k) {
                    break;
                }
                if (nums[j] + nums[k] == target) {
                    res.push_back({nums[i], nums[j], nums[k]});
                }
            }
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值