LeetCode 1 两数之和[一] | Java 刷题打卡

203 阅读2分钟

本文正在参加「Java主题月 - Java 刷题打卡」,详情查看 活动链接

题目描述

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

思路分析

首先能想到最简单的方式就是暴力破解,通过两个 for 循环来进行判断,复杂度是O(n2){O(n^2)}。但是仔细一想,似乎还有优化的空间,能否把复杂度降到O(n){O(n)}呢,我们可以用一个 map 将数组中的值存储起来,key 对应 nums[i],value 对应 i,将 target - nums[i] 的值判断是否存在于 map 中,如果存在,就返回数组中对应的两个位置。该解法的时间复杂度是O(n){O(n)},空间复杂度也是O(n){O(n)}

还有一种解法是排序+双指针,先对该数组进行排序,排序前先拷贝一份数组,然后对数组进行排序,排序后利用双指针找到对应数的下标,因为已经排序过了,所以数组中的位置已经变化了,所以需要将拷贝的数组遍历寻找对应的下标。

代码展示

解法一:暴力破解,时间复杂度O(n2){O(n^2)},空间复杂度O(1)O(1)

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

解法二:通过 map 存储遍历过的值,进行判断。时间复杂度O(n){O(n)},空间复杂度O(n)O(n)

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

解法三:排序+双指针,时间复杂度O(nlogn){O(nlogn)},空间复杂度O(n)O(n)。这种方式写法最复杂。

   public int[] twoSum(int[] nums, int target) {
   
    		int[] tempArray = Arrays.copyOf(nums,nums.length);
        int length = nums.length;
        int head = 0;
        int tail = length - 1;
        quickSort(nums, length);

        int[] targetArray = null;
        while (head < tail) {
            if (nums[head] + nums[tail] > target) {
                tail--;
            } else if (nums[head] + nums[tail] < target) {
                head++;
            } else {
                targetArray = new int[]{head, tail};
                break;
            }
        }
        int count = 0;
        boolean firstHasValue = false;
        boolean secondHasValue = false;
        for (int i = 0;i < length;i++){
            if (nums[targetArray[0]] == tempArray[i] && !firstHasValue){
                targetArray[0] = i;
                count++;
                firstHasValue = true;
                if (count == 2){
                    return targetArray;
                }
                continue;
            }
        
            if (nums[targetArray[1]] == tempArray[i] && !secondHasValue){
                targetArray[1] = i;
                count++;
                secondHasValue = true;
            }
            if (count == 2){
                return targetArray;
            }
        }

        return null;
    }


    private void quickSort(int[] nums,int n){
        quickInternal(nums,0,n - 1);
    }

    private void quickInternal(int[] nums,int l,int r){
        if(l >= r){
            return;
        }
        int division = partition(nums,l,r);
        quickInternal(nums,l,division - 1);
        quickInternal(nums,division + 1,r);
    }

    private int partition(int[] a,int l,int r){
        int base = a[l];
        while(l < r){
            
            while(l < r && a[r] >= base){
                r--;
            }
            a[l] = a[r];

            while(l < r && a[l] <= base){
                l++;
            }
            a[r] = a[l];
        }
        a[l] = base;
        return l;
    }

先利用快排对数组进行排序,然后利用双指针寻找到合适下标,最后再遍历最原始的数组(也就是拷贝后的数组),找到原本的下标位置,最后一步找对应的下标位置时尤其要注意

总结

两数之和的三种解法,暴力破解最容易但是时间复杂度最高,利用哈希表的解法时间复杂度最优但是需要耗费O(n){O(n)}的空间,而双指针+快排这种的时间复杂度是O(nlogn){O(nlogn)},空间复杂度也不低O(n){O(n)},而且写法也比较复杂。