1. 两数之和 15. 三数之和 18. 四数之和 16. 最接近的三数之和 (解法的共性和差异)

1. 两数之和

题目:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

题目要点:

1:数组无序。

2:因为是返回数的下标,排序再用双指针需要额外维护数的原索引。

解法1:穷遍历

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 2)
            return {};

        for (int i = 0; i < len-1; ++i)
            for (int j = i+1; j < len; ++j) {
                if (nums[i] + nums[j] == target) {
                    vector<int> res(2);
                    res[0] = i;
                    res[1] = j;
                    return res;
                }
            }

        return {};
    }
};

//go
func  abs(a, b int) int {
    if a > b {
        return a-b
    }

    return b-a
}

func twoSum(nums []int, target int) []int {
    lenth := len(nums)
    if lenth < 2 {
        return []int{}
    } else if lenth == 2 {
        return []int{0, 1}
    }

    for i := 0; i < lenth-1; i++ {
        for j := i+1; j < lenth; j++ {
           if nums[i] + nums[j] == target {
               return []int{i, j}
           }
       }
   }

   return []int{}
}

解法2:用迭代器穷遍历

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 2)
            return {};

        int another;
        for (auto pos = nums.begin(); pos != nums.end(); ++pos) {
            another = target - *pos;
            auto otherPos = find(pos+1, nums.end(), another);
            if (otherPos != nums.end()) {
                vector<int> res(2);
                res[0] = pos - nums.begin();
                res[1] = otherPos - nums.begin();
                return res;
            }
        }

        return {};
    }
};

解法3:用hash表记录已经判断过的数

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 2)
            return {};

        int another;
        unordered_map<int,int> myHash;
        for (int i = 0; i < len; ++i) {
            another = target - nums[i];
            if (myHash.count(another)) {           //myHash.find(another) != myHash.end()
                vector<int> res(2);
                res[0] = myHash[another];
                res[1] = i;
                return res;
            }
            myHash[nums[i]] = i;                   //当前值为key,index为key的val
        }

        return {};
    }
};


//go
func twoSum(nums []int, target int) []int {
    castMap := make(map[int]int, len(nums))

    for i, v := range nums {
        if j, ok := castMap[target - v]; ok {
            return []int{j, i}
        }
        castMap[v] = i
    }

    return []int{}
}

15. 三数之和

题目:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

题目要点:

1:数组无序。

2:三数之和为target,target = 0

3:结果不含重复三元组。

4:如果不排序,需额外处理重复三元组,时间空间会惨不忍睹

解法1:先固定一个数,再双指针

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int len = nums.size();
        if (len < 3)
            return {};
        
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int first = 0; first < len-2; first++) {
            if (nums[first] > 0)                                              //第一个数最小数不能大于0
                break;
            if (first>0 && nums[first-1] == nums[first])                      //第一个数去重
                continue;

            for (int left = first+1, right = len-1; left < right; left++) {
                if (first+1 < left && nums[left-1] == nums[left])             //第二个数去重
                    continue;
                while (left < right && nums[first]+nums[left]+nums[right] > 0)//第三个数太大
                    --right;
                if (left >= right)
                    break;

                if (nums[first]+nums[left]+nums[right] == 0) {
                    vector<int> tmp(3);
                    tmp[0] = nums[first];
                    tmp[1] = nums[left];
                    tmp[2] = nums[right];
                    res.push_back(tmp);
                }
            }
        }

        return res;
    }
};

          另一种编写方法如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        int len = nums.size();
        if (len < 3)
            return res;
        
        sort(nums.begin(), nums.end());                                 //排序
        for (int i = 0; i < len-2; ++i) {
            int left = i+1, right = len-1;
            if (i && nums[i-1] == nums[i])                              //第一个数去重
                continue;
            if (nums[i] > 0 || nums[right] < 0)                         //条件短路
                break;
            while (left < right) {                                      //二分法
                if (i+1 < left && nums[left-1] == nums[left]) {         //第二个数去重
                    ++left;
                    continue;
                }
                int sum = nums[i]+nums[left]+nums[right];
                if (sum == 0) {
                    res.push_back({nums[i], nums[left], nums[right]});  //符合同时移动两指针
                    ++left;
                    --right;
                }
                else if (sum < 0)
                    ++left;
                else
                    --right;
            }
        }

        return res;
    }
};

           此解法改成先固定一个数,再求两数之和的格式,程序参考如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int len = nums.size();
        if (len < 3)
            return {};
        
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int first = 0; first < len-2; first++) {
            if (nums[first] > 0)                                             //第一个数最小数不能大于0
                break;
            if (first>0 && nums[first-1] == nums[first])                     //第一个数去重
                continue;
            int targetTwo = -nums[first];                                    //剩余两数之和
            for (int left = first+1, right = len-1; left < right; left++) {
                if (nums[left] > targetTwo)                                  //此数不能大于两数之和
                    break;
                if (first+1 < left && nums[left-1] == nums[left])            //第二个数去重
                    continue;
                while (left < right && targetTwo < nums[left]+nums[right])   //第三个数太大
                    --right;
                if (left >= right)
                    break;

                if (targetTwo == nums[left]+nums[right]) {
                    vector<int> tmp(3);
                    tmp[0] = nums[first];
                    tmp[1] = nums[left];
                    tmp[2] = nums[right];
                    res.push_back(tmp);
                }
            }
        }

        return res;
    }
};

         综上,能够总结一个模式

1:先固定前n-2个数。

         1)如果其值大小明显不符合要求,排除此数。

         2)若此数是已处理过的重复数,再排除。

2:然后再用双指针求剩余两数。

         1)2)与上述一致,然后比较现有和和target的大小,若和小,移动左指针,若大移动右指针,否则计入结果。

       需要指出的一点是,只有target非负时需要1中1),若target正负未知,1中1)是不该有的。因为当target为负时,第一个最小数minest和target的大小是无法比较的,例如minest(-8)< target(-6),minest(-6) = target (-6),minest(-2)> target (-6)都可以成立,但此时minest可以是合法的,例如-8会遇上正数,-6会遇上0,-2会遇上负数。这一点在如下四数之和可以明显地表现出来。

18. 四数之和

题目:

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

题目要点:

1:和三数之和不同的是,target未知。

解法:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 4)
            return {};
        
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for (int first = 0; first < len-3; first++) {
            //if (nums[first] > target)
            //    break;
            if (first>0 && nums[first-1] == nums[first])                            //第一个数去重
                continue;
            int targetThree = target - nums[first];                                 //剩余三数之和
            for (int second = first+1; second < len-2; second++) {
                //if (nums[second] > targetThree)
                //    break;
                if (second > first+1 && nums[second-1] == nums[second])            //第二个数去重
                    continue;
                int targetTwo = targetThree - nums[second];                        //剩余两数之和
                for (int third = second+1, fouth = len-1; third < fouth; third++) {
                    //if (nums[third] > targetTwo)
                    //    break;
                    if (third > second+1 && nums[third-1] == nums[third])          //第三个数去重
                        continue;
                    while (third < fouth && targetTwo < nums[third] + nums[fouth]) //第四个数太大
                        --fouth;
                    if (third >= fouth)
                        break;

                    if (targetTwo == nums[third] + nums[fouth]) {
                        vector<int> tmp(4);
                        tmp[0] = nums[first];
                        tmp[1] = nums[second];
                        tmp[2] = nums[third];
                        tmp[3] = nums[fouth];
                        res.push_back(tmp);
                    }
                }
            }
        }

        return res;
    }
};

         按照三数之和的解法思路得到上述解法中,与之不同的是注释部分,因为target正负未知,应该注掉,这一点已在三数之和部分解释过,不理解的同学可以去掉//跑一边测试例,然后分析下某些测试例通不过的原因。

        综上,解决此类n数之和的思路就是,排序数组,然后对于固定的数去重,就最后两个数利用和大小有效缩小区间。此外,排除明显不满足的情况,避免不必要的计算和比较。

16. 最接近的三数之和

题目:

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

解法1:穷遍历

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 3)
            return -1;

        int minDiff = INT_MAX, diff;
        int res = 0;
        for (int first = 0; first < len-2; first++) {
            for (int second = first+1; second < len-1; second++) {
                for (int third = second+1; third < len; third++) {
                    int sum = nums[first] + nums[second] + nums[third];
                    diff = abs(target - sum);
                    if (diff < minDiff) {
                        minDiff = diff;
                        res = sum;
                    }
                }
            }
        }

        return res;
    }
};

解法2:类似三数之和

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size();
        if (len < 3)
            return -1;
        
        sort(nums.begin(), nums.end());
        int minDiff = INT_MAX, diff;
        int res = 0;
        for (int first = 0; first < len-2; first++) {
            if (first>0 && nums[first-1] == nums[first])                 //第一个数去重
                continue;
            for (int second = first+1, third = len-1; second < third;) {
                if (second>first+1 && nums[second-1] == nums[second]) {  //第二个数去重
                    second++;
                    continue;
                }

                int sum = nums[first] + nums[second] + nums[third];
                diff = abs(target - sum);
                if (diff < minDiff) {
                    minDiff = diff;
                    res = sum;
                    if (0 == diff)
                        return res;
                }

                if (sum < target)                                   //利用sum的大小左右缩小范围
                    second++;
                else if (sum > target)
                    third--;
            }
        }

        return res;
    }
};

附录:

unordered_map - C++ Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值