常见数据结构与算法三(两数之和)

目录

暴力解法

哈希表解法

二分查找解法

双指针(对撞指针)解法


给定一个升序排列的整数数组 nums ,从数组中找出两个数满足相加之和等于目标数 target 。
假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。
返回两数的下标值,以数组形式返回。

暴力解法

public int[] twoSum (int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }

方法的逻辑如下:

  1. 获取数组 nums 的长度 n

  2. 使用两层嵌套循环来遍历数组中的元素对:

    • 外层循环变量 i 从 0 遍历到 n-1
    • 内层循环变量 j 从 i+1 遍历到 n
  3. 在内层循环中检查元素 nums[i] 和 nums[j] 是否满足和为 target 的条件:

    • 如果 nums[i] + nums[j] == target,则说明找到了满足条件的一对元素。此时,方法返回一个包含这两个元素索引的数组,即 new int[]{i, j}
  4. 如果两层循环结束后没有找到满足条件的元素对,则方法返回一个空数组 new int[0]

这种方法的时间复杂度是 O(n^2),其中 n 是数组 nums 的长度。由于这种方法没有使用任何优化,所以当数组很大时可能会非常慢。

哈希表解法

将数组的值作为key存入map,target - num作为key

public int[] twoSumMap (int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
            if (map.containsKey(target - nums[i])) {
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i], i);
        }
        return new int[0];
    }

这个方法同样接收一个整型数组 nums 和一个整数 target 作为参数,目的是找出数组中两个数的和为 target 的一对元素,并返回这两个元素的索引。

方法的逻辑如下:

  1. 创建一个 HashMap,用于存储数组元素的值和对应的索引。

  2. 遍历数组 nums

    • 对于每个元素 nums[i],计算 complement,即 target - nums[i]
    • 检查 map 中是否已经存在 complement 作为键(key)的值,如果存在,说明之前已经遇到了一个数,与当前的 nums[i] 相加等于 target
      • 如果找到了,那么 map.get(complement) 就会给出之前数的索引,i 就是当前数的索引。这时,方法返回一个包含这两个索引的数组,即 new int[]{map.get(complement), i}
    • 如果 map 中不存在 complement,则将当前元素的值和索引作为键值对存入 map,即 map.put(nums[i], i)
  3. 如果遍历结束后没有找到满足条件的元素对,则方法返回一个空数组 new int[0]

这种方法利用了哈希表的高效键查找特性,将时间复杂度从暴力解法的 O(n^2) 降低到了 O(n),其中 n 是数组 nums 的长度。

二分查找解法

先固定一个值(从下标0开始),再用二分查找查另外一个值,找不到则固定值向右移动,继续二分查找

import java.util.Arrays;

public class Solution {
    public int[] twoSumBinarySearch(int[] nums, int target) {
        // 创建一个数组的副本并对其进行排序
        int[] sortedNums = nums.clone();
        Arrays.sort(sortedNums);
        
        for (int i = 0; i < sortedNums.length; ++i) {
            int complement = target - sortedNums[i];
            int low = i + 1; // 从当前元素的下一个开始二分查找
            int high = sortedNums.length - 1;
            
            while (low <= high) {
                int mid = low + (high - low) / 2;
                if (sortedNums[mid] == complement) {
                    // 找到了两个数,现在需要找到它们在原始数组中的索引
                    int index1 = findIndex(nums, sortedNums[i], -1);
                    int index2 = findIndex(nums, complement, index1);
                    return new int[]{index1, index2};
                } else if (sortedNums[mid] > complement) {
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
        }
        
        // 如果没有找到满足条件的元素对,返回一个空数组
        return new int[0];
    }

    // 辅助函数,用于在原始数组中找到特定值的索引
    private int findIndex(int[] nums, int value, int excludeIndex) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == value && i != excludeIndex) {
                return i;
            }
        }
        return -1;
    }
}

首先创建了原始数组的一个副本并对其进行排序,然后使用二分查找来寻找目标值的补数。一旦找到了这样的一对数,我们使用 findIndex 辅助函数在原始数组中找到它们的索引。

注意,当找到第二个数的索引时,需要确保它不是第一个数的索引(如果两个数相同的话)。

对数组进行了排序,排序的时间复杂度通常是 O(n log n)

双指针(对撞指针)解法

使用两个指针分别指向数组的头部和尾部,通过移动这两个指针来找到和为 target 的两个数。

public int[] twoPoint (int[] nums, int target) {
        int low = 0, high = nums.length - 1;
        while (low < high) {
            int sum = nums[low] + nums[high];
            if (sum == target) {
                return new int[]{low + 1, high + 1};
            } else if (sum < target) {
                ++low;
            } else {
                --high;
            }
        }
        return new int[]{-1, -1};
    }

这个方法的逻辑如下:

  1. 初始化两个指针 low 和 high,分别指向数组的第一个元素和最后一个元素。

  2. 在 low 小于 high 的条件下进行循环:

    • 计算当前两个指针指向的元素的和 sum
    • 如果 sum 等于 target,则找到了满足条件的一对元素,返回它们的索引。注意,索引是从1开始的,所以返回的是 {low + 1, high + 1}
    • 如果 sum 小于 target,则需要增加和的值,所以 low 指针右移(即 ++low)。
    • 如果 sum 大于 target,则需要减少和的值,所以 high 指针左移(即 --high)。
  3. 如果循环结束还没有找到满足条件的元素对,返回 {-1, -1} 作为标志,表示没有找到。

时间复杂度为 O(n),因为它只需要一次遍历数组。然而,这个方法的前提是数组必须是有序的。

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值