java 3sum_[LeetCode] 15. 3Sum 三数之和

Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)

The solution set must not contain duplicate triplets.

For example, given array S = {-1 0 1 2 -1 -4},

A solution set is:

(-1, 0, 1)

(-1, -1, 2)

这道题让我们求三数之和,比之前那道 Two Sum 要复杂一些,博主考虑过先 fix 一个数,然后另外两个数使用 Two Sum 那种 HashMap 的解法,但是会有重复结果出现,就算使用 TreeSet 来去除重复也不行,会 TLE,看来此题并不是考 Two Sum 的解法。来分析一下这道题的特点,要找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,还是要先 fix 一个数,然后去找另外两个数,只要找到两个数且和为第一个 fix 数的相反数就行了,既然另外两个数不能使用 Two Sum 的那种解法来找,如何能更有效的定位呢?我们肯定不希望遍历所有两个数的组合吧,所以如果数组是有序的,那么就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。

对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。这里可以先做个剪枝优化,就是当遍历到正数的时候就 break,为啥呢,因为数组现在是有序的了,如果第一个要 fix 的数就是正数了,则后面的数字就都是正数,就永远不会出现和为0的情况了。然后还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为不想把相同的数字fix两次。对于遍历到的数,用0减去这个 fix 的数得到一个 target,然后只需要再之后找到两个数之和等于 target 即可。用两个指针分别指向 fix 数字之后开始的数组首尾两个数,如果两个数和正好为 target,则将这两个数和 fix 的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于 target,则将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于 target,则将右边那个指针j左移一位,使得指向的数字减小一些,代码如下:

解法一:

classSolution {public:

vector> threeSum(vector&nums) {

vector>res;

sort(nums.begin(), nums.end());if (nums.empty() || nums.back() < 0 || nums.front() > 0) return{};for (int k = 0; k < (int)nums.size() - 2; ++k) {if (nums[k] > 0) break;if (k > 0 && nums[k] == nums[k - 1]) continue;int target = 0 - nums[k], i = k + 1, j = (int)nums.size() - 1;while (i

res.push_back({nums[k], nums[i], nums[j]});while (i < j && nums[i] == nums[i + 1]) ++i;while (i < j && nums[j] == nums[j - 1]) --j;++i; --j;

}else if (nums[i] + nums[j] < target) ++i;else --j;

}

}returnres;

}

};

或者我们也可以利用 TreeSet 的不能包含重复项的特点来防止重复项的产生,那么就不需要检测数字是否被 fix 过两次,不过个人觉得还是前面那种解法更好一些,参见代码如下:

解法二:

classSolution {public:

vector> threeSum(vector&nums) {set>res;

sort(nums.begin(), nums.end());if (nums.empty() || nums.back() < 0 || nums.front() > 0) return{};for (int k = 0; k < (int)nums.size() - 2; ++k) {if (nums[k] > 0) break;int target = 0 - nums[k], i = k + 1, j = (int)nums.size() - 1;while (i

res.insert({nums[k], nums[i], nums[j]});while (i < j && nums[i] == nums[i + 1]) ++i;while (i < j && nums[j] == nums[j - 1]) --j;++i; --j;

}else if (nums[i] + nums[j] < target) ++i;else --j;

}

}return vector>(res.begin(), res.end());

}

};

Github 同步地址:

类似题目:

参考资料:

题目描述: 给定一个只包含正整的非空组,是否可以将这个组分成两个子集,使得两个子集的元素和相等。 示例: 输入:[1, 5, 11, 5] 输出:true 解释:组可以分割成 [1, 5, 5] 和 [11]。 解题思路: 这是一道经典的 0-1 背包问题,可以使用动态规划或者回溯算法解决。 使用回溯算法,需要定义一个 backtrack 函,该函三个: - 组 nums; - 当前处理到的组下标 index; - 当前已经选择的元素和 leftSum。 回溯过程中,如果 leftSum 等于组元素和的一半,那么就可以直接返回 true。如果 leftSum 大于组元素和的一半,那么就可以直接返回 false。如果 index 到达组末尾,那么就可以直接返回 false。 否则,就对于当前元素,有选择和不选择两种情况。如果选择当前元素,那么 leftSum 就加上当前元素的值,index 就加 1。如果不选择当前元素,那么 leftSum 不变,index 也加 1。最终返回所有可能性的结果中是否有 true。 Java 代码实现: class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 != 0) { return false; } Arrays.sort(nums); return backtrack(nums, nums.length - 1, sum / 2); } private boolean backtrack(int[] nums, int index, int leftSum) { if (leftSum == 0) { return true; } if (leftSum < 0 || index < 0 || leftSum < nums[index]) { return false; } return backtrack(nums, index - 1, leftSum - nums[index]) || backtrack(nums, index - 1, leftSum); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值