通关剑指Offer——关于查找数组元素之和的两道题

1. 数组中和为 0 的三个数

1.1 题目描述

剑指 Offer II 006. 排序数组中两个数字之和

给定一个已按照 升序排列  的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:

输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:

输入:numbers = [-1,0], target = -1
输出:[0,1]

1.2 解题思路与代码

1.2.1 解题思路

这道题是求数组中是否存在两个数字等于给定数字,可以考虑是有暴力解两层循环遍历数组依次求和,但是这一样时间复杂度是 O(n^2) 太高了。题目已经规定了数组是按升序排序的数组,我们便可以使用双指针从两边往中间移动计算两个指针指向数字之和,再与 target 进行比较移动。首先让左指针 l 指向角标 0 位置,右指针 r 指向数组最后一位,然后计算两个指针指向数字之和,此时计算的和会出现以下三种情况:

  • 如果和等于 target 退出循环并返回 l 和 r(因为题目规定有且仅有一个答案)
  • 如果和小于 target,因为数组从小到大排序,此时让左指针向右移动这样能够让两数之和变大
  • 同理如果和大于 target,此时让右指针向左移动,这样让两数之和变小

以题目示例 numbers = [1,2,4,6,10], target = 8 为例进行图解:
首先让 l 和 r 指向首位,此时 l 和 r 指向数之和为 11,大于 target ,因此让 r 左移

r 左移之后,此时 l=0,r=3,两数之和为 7 ,小于 target 于是让 l 右移

l 右移之后,此时 l=1,r=3,两数之和为 8 ,等于 target 满足题意返回 [1, 3]

1.2.2 代码

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int l = 0;
        int r = numbers.length - 1;
        int[] ans = new int[2];
        while (l < r) {
            if (numbers[l] + numbers[r] == target) {
                ans[0] = l;
                ans[1] = r;
                break;
            } else if (numbers[l] + numbers[r] < target) {
                l++;
            } else {
                r--;
            }
        }
        return ans;
    }
}

1.2.3 测试结果

通过测试
测试结果

2. 数组中和为 0 的三个数

2.1 题目描述

剑指 Offer II 007. 数组中和为 0 的三个数
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?请找出所有和为 0 且 不重复 的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:

输入:nums = []
输出:[]

示例 3:

输入:nums = [0]
输出:[]

2.2 解题思路与代码

2.2.1 解题思路

这道题和上面那道题其实是一样的,只不过从两数之和为 target 变成了三数之和为零。我们可以转换一下思想,如果我们抓住数组中一个数字 num,然后就编程在剩下的数字中找到两个数的和等于 -num。我们在进一步,对数组 nums 进行排序,然后我们从左到右遍历数组:

  • 遍历第一位 nums[0] 时,我们就需要在 [1, n-1] 的范围上寻找两个数的和等于 -num[0]
  • 遍历到第二位 nums[1] 时,我们就需要在 [2, n-1] 的范围上寻找两个数的和等于 -num[1]
  • 遍历到第三位 nums[2] 时,我们就需要在 [3, n-1] 的范围上寻找两个数的和等于 -num[2]
    ……
  • 依次类推

由于我们已经对数组进行排序,那么在后面查找范围内的数字均是按升序排序的,到这里就能够看出和上面那道题是一模一样的了,只不过 target 变成了 -nums[i],查找范围变成 [i+1, n-1]。从上面的分析,我们可以在外层循环遍历数组,循环内部使用双指针从 [i+1, n-1] 的范围上查找两数之和等于 -num[i] 的两个数。与上面题目相同,也有三种情况:

  • 当两数之和小于 -num[i] 时,l 右移
  • 当两数之和大于 -num[i] 时,l 左移
  • 当两数之和等于 -num[i] 时,i、l、r 三个位置上的数字放入结果列表中,然后 l 右移、r 左移。

这里需要注意,题目要求不能出现重复的数字,因此当 nums[i] 等于 nums[i-1] 时,跳过本次循环,同理 nums[l] 等于 nums[l-1]、 nums[r] 等于 nums[r+1] 时也需要跳过。下面是数组 [-1,0,1,2,-1,-4] 查找的过程,首先 i=0,我们使用 l 和 r 在 [1, 5] 的范围上查找两数之和等于 4 的两个数,当 l 等于 r 时推出内层循环。然后 i=1 此时在 [2, 5] 的范围上查找两数之和等于 1 此时 l=2 和 r=5 所指的两个数之和等于 1 满足条件,放入结果数组中。接下来重复上面过程,直到数组遍历完成。

2.2.2 代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        if (nums.length == 0) {
            return ans;
        }
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int target = -nums[i];
            int l = i + 1;
            int r = nums.length - 1;
            while (l < r) {
                if (nums[l] + nums[r] == target) {
                    ans.add(Arrays.asList(nums[i], nums[l], nums[r]));
                    do {
                        l++;
                    } while (l < r && nums[l] == nums[l - 1]);
                    do {
                        r--;
                    } while (l < r && nums[r] == nums[r + 1]);

                } else if (nums[l] + nums[r] < target) {
                    l++;
                } else {
                    r--;
                }
            }

        }
        return ans;
    }
}

2.2.3 测试结果

通过测试
测试结果

3.总结

  • 第一题使用双指针进行处理,两数之和小于目标,左指针右移;大于目标,右指针左移;等于目标,退出循环返回结果
  • 第二题是第一题的变种,排序后遍历数组,同样是在后面的数组中查找两数之和等于某一个数
  • 第二题由于需要去重,因此三个指针 i、l、r 如果移动之后等于移动之前的数,那么需要继续移动
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值