您的位置:首页 > 其它

LeetCode - 15. 3Sum

2016-04-07 22:58 555 查看
方法一:

这一道题目直接的想法就是Brute Force,通过三重循环来寻找目标,但是这样的时间复杂度非常高,同时又需要检测来避免重复,所以这种方法虽然能够得到正确的答案,但是在leetcode上会因为time limit exceeded而无法通过

public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> resultList = new ArrayList<List<Integer>>();

for(int i = 0; i < nums.length; i++){
for(int j = i + 1; j < nums.length; j++){
for(int k = j + 1; k < nums.length; k++){
if(nums[i] + nums[j] + nums[k] == 0){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);

if(!resultList.contains(list)){
resultList.add(list);
}
}
}
}
}
return resultList;
}
}
方法二:

回想前面所做的1. Two Sum这一道题目,在这一题目中采用了一种思想:先固定一个数,通过查找的方式找出第二个数,在Two Sum题目中这一思想得到了很好的应用。所以在思考更快速的方法来解决3Sum的时候,可以在外围使用两个二重循环分别固定两个数,而采用查找的方式来找出第三个数字,这样时间复杂度就从原来的O(n^3)变成了O(n^2logn)。可惜这种改进方法还是无法在leetcode上面通过,同样会出现time limit exceeded的错误

public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();

//sort array
java.util.Arrays.sort(nums);

//find complement
int complement;
for(int i = 0; i < nums.length; i++){
for(int j = i + 1; j < nums.length; j++){
complement = 0 - nums[i] - nums[j];

int index = java.util.Arrays.binarySearch(nums, complement);
if(index >= 0 && nums[index] > nums[j]){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[index]);

if(!result.contains(list)){
result.add(list);
}
}
}
}
return result;
}
方法三:

正在一筹莫展的时候,搜索到了这篇文章http://www.sigmainfy.com/blog/summary-of-ksum-problems.html,是对k-sum问题的一个总结,看下来之后又有了新的想法。首先,在2Sum问题当中,首先对数组进行排序,接着通过一个指向头部,一个指向尾部的指针向中间移动,来寻找目标数字,这样就成功地把2Sum问题的时间复杂度降低到了O(n)级别,非常有效。因此对于2Sum问题的变体-3Sum,也可以采用同样的思路,即通过一个外层的循环分别固定数组中的每一个数字,将3Sum问题变成多个2Sum问题,同时,题目要求没有重复,所以在添加结果之前,还要检测是否有重复

public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int pfront;
int pback;

//sort array
java.util.Arrays.sort(nums);

for(int i = 0; i < nums.length - 2; i++){
int complement = 0 - nums[i];

//set pointers
pfront = i + 1;
pback = nums.length - 1;

//search complement
while(pfront < pback){
if(nums[pfront] + nums[pback] == complement){
//construct answer, nondescending order
List<Integer> list = java.util.Arrays.asList(nums[i], nums[pfront], nums[pback]);
if(!result.contains(list)){
result.add(list);
}

/*
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[pfront]);
list.add(nums[pback]);
result.add(list);
*/

//move pointers and avoid duplicate
pfront++;
pback--;

}else if(nums[pfront] + nums[pback] < complement){
pfront++;
}else{
pback--;
}
}
}
return result;
}
}方法四:
方法三在leetcode上面得到的结果竟然还是time limit exceeded,此时我的内心几乎是崩溃的....还能有其他的方法减少时间吗?其实是有的,注意到在方法三中,检测重复时使用了list的内置函数contains,但是我们可以通过对指针的正确操作来避免对contains的使用。要了解怎么操作指针,首先我们应该想到在什么情况下会有重复,当然就是给出的目标数组中有相等数字的情况,而在这一算法中,外层循环中的i,内层循环中的两个指针pfront, pback可能会碰到相同的数字进而导致重复,所以在每次外层循环的时候,我们都要检测nums[i]和nums[i
- 1]是否一样,如果一样的话,很可能会造成重复;在内层循环中,当找到目标数字的时候,需要进行pfront++和pback--来跳过临近的相同数字。总的来说,第四种方法和第三种方法并没有本质上的区别,只是优化了检测重复的机制,这样就终于可以在leetcode上accept了

public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();

//Attention: alaways consider extreme situation
if(nums == null || nums.length < 3) return result;

int pfront;
int pback;
int complement;

//sort array
Arrays.sort(nums);

for(int i = 0; i < nums.length - 2; i++){
//avoid duplicate
if(i > 0 && nums[i] == nums[i - 1]) continue;

complement = 0 - nums[i];
//set pointers
pfront = i + 1;
pback = nums.length - 1;

while(pfront < pback){
if(nums[pfront] + nums[pback] == complement){
List<Integer> list = Arrays.asList(nums[i], nums[pfront], nums[pback]);
result.add(list);

//move pointers
while(pfront < pback && nums[pfront] == nums[pfront + 1]) pfront++;
while(pfront < pback && nums[pback] == nums[pback - 1]) pback--;
pfront++;
pback--;
}else if(nums[pfront] + nums[pback] < complement){
pfront++;
}else{
pback--;
}
}
}
return result;
}
}知识点:
1. List<List<Integer>>的正确实例化是new ArrayList<List<Integer>>()或new ArrayList<>(),而不是new ArrayList<ArrayList<Integer>>()或new List<List<Integer>>,前者的解释在http://stackoverflow.com/questions/5763750/why-we-cant-do-listparent-mylist-arraylistchild,而后者List是接口,不能实例化

2. Array与List的相互转换:java.util.Arrays.asList(nums[i], nums[pfront], nums[pback])

3. Array排序:Arrays.sort(nums)

4000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode