nSum问题

113 阅读4分钟

参考: 一个函数秒杀 2Sum 3Sum 4Sum 问题

微信图片_20231205103740.jpg

问题

序号题目完成
1. 两数之和
15. 三数之和
18. 四数之和
167. 两数之和 II - 输入有序数组
剑指 Offer II 007. 数组中和为 0 的三个数
剑指 Offer II 006. 排序数组中两个数字之和

题解

1. 两数之和

解法

(1)利用hashmap缓存

正常的想法是把所有的元素都存到hashmap里,然后再遍历数组去但是又觉得这样做对重复元素处理的不对。不过后来又一想,如果有重复元素,正好后面的元素的位置覆盖了前面的。遍历到第一个的时候,拿到的target正好是第二个重复的。

(2)优化

看了官方题解,还是有点巧妙的,可以只遍历一次。

// 两次遍历
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> idxMap = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            idxMap.put(nums[i], i);
        }
        int[] ans = new int[2];
        for (int i = 0; i < nums.length; i++) {
            if (idxMap.containsKey(target - nums[i])) {
                int idx = idxMap.get(target - nums[i]);
                if (idx != i) {
                    return new int[]{i, idxMap.get(target - nums[i])};
                }
            }
        }
        return null;
    }
}
// 一次循环解决问题,还可以解决自身重复的问题
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> idxMap = new HashMap<>();
        int[] ans = new int[2];
        for (int i = 0; i < nums.length; i++) {
            int need = target - nums[i];
            if (idxMap.containsKey(need)) {
                return new int[]{i, idxMap.get(target - nums[i])};
            } else {
                idxMap.put(nums[i], i);
            }
        }
        return null;
    }
}

167. 两数之和 II - 输入有序数组

相同题目:剑指 Offer II 006. 排序数组中两个数字之和

解法

有序数组就比较好解决了,第一时间想到用滑动指针。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum == target) {
                return new int[]{left + 1, right + 1};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return null;
    }
}

15. 三数之和

相同题目: 剑指 Offer II 007. 数组中和为 0 的三个数

解法

  1. a+b+c=0,其实就是a+b=-c,这就是一个两数之和问题。
  2. 但是增加了一个维度,复杂性也随之增加:
  3. 怎么选取target(从0开始),选择了target,left(target后一个元素开始)和right怎么选择(right就是右边界,可以确定就是数组的右边界)。
  4. 怎么去重:排序+相邻判断(while(nums[left] == nums[left+1])

时空复杂度

时间复杂度:O(n^2)
空间复杂度:O(n)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        int len = nums.length;
        int left = 0;
        Arrays.sort(nums);
        while (left < len) {
            List<List<Integer>> res = twoSum(nums, left + 1, -nums[left]);
            ans.addAll(res);
            // 对元素头去重
            while ((left < len - 1) && (nums[left] == nums[left + 1])) {
                left++;
            }
            // 继续下一个元素
            left++;
        }
        return ans;
    }

    public List<List<Integer>> twoSum(int[] nums, int left, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        int right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                List<Integer> tmp = new ArrayList<>();
                tmp.add(-target);
                tmp.add(nums[left]);
                tmp.add(nums[right]);
                ans.add(tmp);

                // 把相等的元素都去除
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                left++;
            } else if (sum < target) {
                // 把相等的元素都去除
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                left++;
            } else {
                // 把相等的元素都去除
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                right--;
            }
        }
        return ans;
    }
}

18. 四数之和

解法

  1. 四数之和可以转化为三数和为0的问题。
  2. 整数问题案例需要考虑越界的问题,int的范围:-2147483648~2147483647
// 先自己写了一版,虽然通过了,但是时空复杂度有点高
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums);
        int len = nums.length;
        int left = 0;
        while (left < len) {
            List<List<Integer>> res = threeSum(nums, left + 1, nums[left], target);
            for (List<Integer> re : res) {
                re.add(nums[left]);
            }
            ans.addAll(res);
            // 对元素头去重
            while ((left < len - 1) && (nums[left] == nums[left + 1])) {
                left++;
            }
            // 继续下一个元素
            left++;
        }
        return ans;
    }

    public List<List<Integer>> threeSum(int[] nums, int left, long parentNum, long target) {
        List<List<Integer>> ans = new ArrayList<>();
        int len = nums.length;
        while (left < len) {
            List<List<Integer>> res = twoSum(nums, left + 1, nums[left], target - parentNum);
            ans.addAll(res);
            // 对元素头去重
            while ((left < len - 1) && (nums[left] == nums[left + 1])) {
                left++;
            }
            // 继续下一个元素
            left++;
        }
        return ans;
    }

    public List<List<Integer>> twoSum(int[] nums, int left, long parentNum, long target) {
        List<List<Integer>> ans = new ArrayList<>();
        int right = nums.length - 1;
        while (left < right) {
            long sum = nums[left] + nums[right] + parentNum;
            if (sum == target) {
                List<Integer> tmp = new ArrayList<>();
                tmp.add((int) parentNum);
                tmp.add(nums[left]);
                tmp.add(nums[right]);
                ans.add(tmp);

                // 把相等的元素都去除
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                left++;
            } else if (sum < target) {
                // 把相等的元素都去除
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                left++;
            } else {
                // 把相等的元素都去除
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                right--;
            }
        }
        return ans;
    }
}
//labuladong的写法,用了递归,大体意思是一样的
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        return nSum(nums, 4, 0, target);
    }
    // target可能会溢出
    public List<List<Integer>> nSum(int[] nums, int n, int left, long target) {
        List<List<Integer>> ans = new ArrayList<>();
        int len = nums.length;
        if (n == 2) {
            // 计算两数之和
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum == target) {
                    List<Integer> tmp = new ArrayList<>();
                    tmp.add(nums[left]);
                    tmp.add(nums[right]);
                    ans.add(tmp);

                    // 把相等的元素都去除
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++;
                } else if (sum < target) {
                    // 把相等的元素都去除
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++;
                } else {
                    // 把相等的元素都去除
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--;
                }
            }
            return ans;
        } else {
            // 计算多数之和
            while (left < len) {
                List<List<Integer>> res = nSum(nums, n - 1, left + 1, target - nums[left]);
                for (List<Integer> r : res) {
                    r.add(nums[left]);
                    ans.add(r);
                }
                // 对元素头去重
                while ((left < len - 1) && (nums[left] == nums[left + 1])) {
                    left++;
                }
                // 继续下一个元素
                left++;
            }
        }
        return ans;
    }
}
//leetcode submit region end(Prohibit modification and deletion)