数组元素的目标和

56 阅读4分钟

数组元素的目标和

给定两个升序排序的有序数组 AB,以及一个目标值 x。数组下标从 0 开始。请你求出满足 A[i] + B[j] = x 的数对 (i, j)。数据保证有唯一解。

所用方法和基本原理

  1. 双指针法原理
    • 该代码运用双指针法来解决问题。双指针法是一种常用的算法技巧,通过使用两个指针遍历数据结构,根据特定条件移动指针以优化遍历过程。
    • 这里设置两个指针,一个指针 i 指向数组 arr1 的起始位置(即 i = 0),另一个指针 j 指向数组 arr2 的末尾位置(即 j = m - 1,其中 m 是数组 arr2 的长度)。
    • 由于数组 arr1arr2 都是升序排列的,通过比较 arr1[i] + arr2[j] 与目标值 x 的大小关系来移动指针。
    • 如果 arr1[i] + arr2[j] > x,说明当前两数之和过大,因为 arr2 是升序的,所以需要将 j 指针向左移动(即 j--),使得 arr2[j] 变小,从而减小两数之和。
    • 如果 arr1[i] + arr2[j] < x,说明当前两数之和过小,由于 arr1 是升序的,所以需要将 i 指针向右移动(在外部的 for 循环中实现),使得 arr1[i] 变大,进而增大两数之和。
    • arr1[i] + arr2[j] == x 时,就找到了满足条件的数对 (i, j),直接返回该数对。

代码及注释

public class TwoSumForSortedArrays {
    // 寻找满足 arr1[i] + arr2[j] = x 的数对 (i, j)
    public static int[] twoSum(int[] arr1, int[] arr2, int x) {
        int n = arr1.length;
        int m = arr2.length;
        // i 指针遍历 arr1,j 指针从 arr2 的末尾开始
        for (int i = 0, j = m - 1; i < n; i++) {
            // 当 arr1[i] + arr2[j] > x 时,减小 j 以减小两数之和
            while (j >= 0 && arr1[i] + arr2[j] > x) j--;
            // 如果找到满足条件的数对,返回结果
            if (arr1[i] + arr2[j] == x) return new int[]{i, j};
        }
        // 如果未找到,返回 {-1, -1}
        return new int[]{-1, -1};
    }
}

举例说明

假设 arr1 = [1, 3, 5, 7]arr2 = [2, 4, 6, 8],目标值 x = 9

  1. 初始化n = 4m = 4i = 0j = 3
  2. 第一轮循环
    • i = 0arr1[0] = 1j = 3arr2[3] = 8arr1[0] + arr2[3] = 1 + 8 = 9
    • 因为 arr1[0] + arr2[3] == 9,直接返回 [0, 3]

再假设 arr1 = [2, 4, 6]arr2 = [3, 5, 7],目标值 x = 10

  1. 初始化n = 3m = 3i = 0j = 2
  2. 第一轮循环
    • i = 0arr1[0] = 2j = 2arr2[2] = 7arr1[0] + arr2[2] = 2 + 7 = 9
    • 因为 arr1[0] + arr2[2] < 10i 在外部 for 循环中自增,i 变为 1
  3. 第二轮循环
    • i = 1arr1[1] = 4j = 2arr2[2] = 7arr1[1] + arr2[2] = 4 + 7 = 11
    • 因为 arr1[1] + arr2[2] > 10,进入 while 循环,j--j 变为 1
    • 此时 arr1[1] + arr2[1] = 4 + 5 = 9,继续 while 循环,j--j 变为 0
    • 此时 arr1[1] + arr2[0] = 4 + 3 = 7while 循环结束。
    • 因为 arr1[1] + arr2[0] < 10i 在外部 for 循环中自增,i 变为 2
  4. 第三轮循环
    • i = 2arr1[2] = 6j = 0arr2[0] = 3arr1[2] + arr2[0] = 6 + 3 = 9
    • 因为 arr1[2] + arr2[0] < 10i 在外部 for 循环中自增,但 i 已达到数组 arr1 的末尾,循环结束。
    • 未找到满足条件的数对,返回 [-1, -1]

方法的优劣

  1. 时间复杂度
    • 外层 for 循环遍历数组 arr1 一次,时间复杂度为 (O(n)),其中 n 是数组 arr1 的长度。
    • 内层 while 循环在每次外层循环时,最多执行 m 次(m 是数组 arr2 的长度),但整体来看,指针 j 最多从数组 arr2 的末尾移动到开头一次。所以内层 while 循环的总时间复杂度为 (O(m))。
    • 总体时间复杂度为 (O(n + m)),这里 nm 分别是两个数组的长度。相比于暴力解法(时间复杂度为 (O(n \times m)),需要嵌套遍历两个数组),双指针法效率更高。
  2. 空间复杂度
    • 除了输入的数组外,只使用了常数级别的额外空间(如 nmij 等变量),空间复杂度为 (O(1))。

优点: - 时间复杂度低,通过双指针法避免了嵌套循环带来的高时间复杂度,能高效地解决有序数组的两数之和问题。 - 空间复杂度小,只需要常数级别的额外空间,对内存友好。

缺点: - 该方法依赖于数组的有序性,如果数组无序,需要先进行排序,而排序操作可能会增加额外的时间复杂度(如使用快速排序,时间复杂度为 (O(n \log n)))。 - 只能处理两数之和的问题,对于更复杂的多数之和问题,需要对算法进行大幅修改或采用其他方法。