LeetCode 第一题: 两数之和

简介:   给定一个整数数组 `nums`​ 和一个目标值 `target`​,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组索引。\你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

题目描述

  给定一个整数数组 nums​ 和一个目标值 target​,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组索引。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

解题思路

  1. 暴力法:双重循环遍历数组中的每个数,寻找是否存在一个数与它相加等于目标值。
  2. 两遍哈希表法:遍历数组,将每个数及其索引存入哈希表,然后再次遍历数组,查找 target - nums[i]​​ 是否在哈希表中。
  3. 一遍哈希表法:遍历数组,对于每个数,查找 target - nums[i]​​ 是否在哈希表中,如果在,直接返回结果;如果不在,将当前数存入哈希表。

  截图为Go语言的测试

Go语言实现 - 一遍哈希表法

package main
func twoSum(nums []int, target int) []int {
   
   
    hashTable := make(map[int]int)
    for i, num := range nums {
   
   
        complement := target - num
        if index, ok := hashTable[complement]; ok {
   
   
            return []int{
   
   index, i}
        }
        hashTable[num] = i
    }
    return nil
}

C++实现

#include <vector>
#include <unordered_map>

class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        std::unordered_map<int, int> hashTable;
        for (int i = 0; i < nums.size(); ++i) {
            int complement = target - nums[i];
            if (hashTable.find(complement) != hashTable.end()) {
                return {hashTable[complement], i};
            }
            hashTable[nums[i]] = i;
        }
        return {};
    }
};

算法分析

  • 时间复杂度:O(n),其中 n 是数组中的元素数量。我们只遍历了包含有 n 个元素的列表两次。在哈希表中的操作时间复杂度为 O(1)。
  • 空间复杂度:O(n),所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n 个元素。
    这个Go语言实现的代码简单易懂,效率较高,适合解决这类问题。

  ​image

排序和双指针法

  1. 首先,创建一个数组,包含原始数组的元素和它们的索引。
  2. 然后,根据数组元素对数组进行排序。
  3. 使用两个指针,一个从开始(最小元素)的位置,另一个从结束(最大元素)的位置。
  4. 比较两个指针指向的元素之和与目标和:

    • 如果和等于目标和,返回两个元素的索引。
    • 如果和小于目标和,移动左侧指针,使它指向一个更大的数。
    • 如果和大于目标和,移动右侧指针,使它指向一个更小的数。
      这种方法的关键在于,排序后我们可以使用双指针高效地找到两个数,使得它们的和等于目标和。

Go语言实现 - 排序和双指针法

package main
import (
    "sort"
)
type Pair struct {
   
   
    Value int
    Index int
}
func twoSum(nums []int, target int) []int {
   
   
    pairs := make([]Pair, len(nums))
    for i, num := range nums {
   
   
        pairs[i] = Pair{
   
   Value: num, Index: i}
    }
    sort.Slice(pairs, func(i, j int) bool {
   
   
        return pairs[i].Value < pairs[j].Value
    })
    left, right := 0, len(nums)-1
    for left < right {
   
   
        sum := pairs[left].Value + pairs[right].Value
        if sum == target {
   
   
            return []int{
   
   pairs[left].Index, pairs[right].Index}
        } else if sum < target {
   
   
            left++
        } else {
   
   
            right--
        }
    }
    return nil
}

C++

#include <vector>
#include <algorithm>
#include <utility>

class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        std::vector<std::pair<int, int>> numWithIndex;
        for (int i = 0; i < nums.size(); ++i) {
            numWithIndex.push_back({nums[i], i});
        }
        std::sort(numWithIndex.begin(), numWithIndex.end());

        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int sum = numWithIndex[left].first + numWithIndex[right].first;
            if (sum == target) {
                return {numWithIndex[left].second, numWithIndex[right].second};
            } else if (sum < target) {
                ++left;
            } else {
                --right;
            }
        }
        return {};
    }
};

算法分析

  • 时间复杂度:O(nlogn),其中 n 是数组中的元素数量。排序的时间复杂度是 O(nlogn),双指针遍历的时间复杂度是 O(n)。
  • 空间复杂度:O(n),我们需要一个额外的数组来存储元素和它们的索引。
    这种方法在空间复杂度上与哈希表方法相同,但时间复杂度稍高,因为它需要排序。然而,这种方法在不允许修改原数组或需要保持元素原始顺序的情况下可能更有用。

  ​image

暴力法

  1. 使用两个嵌套循环遍历数组中的所有元素对。
  2. 对于每一对元素,检查它们的和是否等于目标和。
  3. 如果找到一对元素的和等于目标和,返回这两个元素的索引。
    这种方法的时间复杂度是 O(n^2),因为它需要对数组中的每一对元素进行一次检查。

Go语言实现 - 暴力法

package main
func twoSum(nums []int, target int) []int {
   
   
    for i := 0; i < len(nums); i++ {
   
   
        for j := i + 1; j < len(nums); j++ {
   
   
            if nums[i] + nums[j] == target {
   
   
                return []int{
   
   i, j}
            }
        }
    }
    return nil
}

C++

#include <vector>

class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = i + 1; j < nums.size(); ++j) {
                if (nums[i] + nums[j] == target) {
                    return {i, j};
                }
            }
        }
        return {};
    }
};

算法分析

  • 时间复杂度:O(n^2),其中 n 是数组中的元素数量。最坏的情况下,我们需要遍历每个元素与其他元素的组合。
  • 空间复杂度:O(1),我们只需要常数级别的额外空间来存储索引。
    暴力法是一种简单直接的方法,但是对于大型数据集来说,它的效率不高。在实际应用中,通常会优先考虑使用哈希表或排序和双指针法来解决这类问题。

  ​image

二分搜索法

  1. 首先,对数组进行排序,并创建一个新数组来保存排序后元素的原始索引。
  2. 对于数组中的每个元素,使用二分搜索查找 target - nums[i]​。
  3. 如果找到,确保两个元素的原始索引不同,然后返回它们的原始索引。
    这种方法的关键在于,排序后我们可以使用二分搜索高效地找到第二个数。

Go语言实现 - 二分搜索法

package main
import (
    "sort"
)
func twoSum(nums []int, target int) []int {
   
   
    indexes := make([]int, len(nums))
    for i := range nums {
   
   
        indexes[i] = i
    }
    sort.Slice(indexes, func(i, j int) bool {
   
   
        return nums[indexes[i]] < nums[indexes[j]]
    })
    for i := 0; i < len(nums); i++ {
   
   
        left, right := i+1, len(nums)-1
        complement := target - nums[indexes[i]]
        for left <= right {
   
   
            mid := left + (right-left)/2
            if nums[indexes[mid]] == complement {
   
   
                return []int{
   
   indexes[i], indexes[mid]}
            } else if nums[indexes[mid]] < complement {
   
   
                left = mid + 1
            } else {
   
   
                right = mid - 1
            }
        }
    }
    return nil
}

C++

#include <vector>
#include <algorithm>
#include <numeric>

class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        std::vector<int> indexes(nums.size());
        std::iota(indexes.begin(), indexes.end(), 0);
        std::sort(indexes.begin(), indexes.end(), [&nums](int i, int j) { return nums[i] < nums[j]; });

        for (int i = 0; i < nums.size(); ++i) {
            int left = i + 1, right = nums.size() - 1;
            int complement = target - nums[indexes[i]];
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (nums[indexes[mid]] == complement) {
                    return {indexes[i], indexes[mid]};
                } else if (nums[indexes[mid]] < complement) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return {};
    }
};

算法分析

  • 时间复杂度:O(nlogn),其中 n 是数组中的元素数量。排序的时间复杂度是 O(nlogn),二分搜索的时间复杂度是 O(logn),总共进行了 n 次二分搜索。
  • 空间复杂度:O(n),我们需要一个额外的数组来保存索引。
    二分搜索法在空间复杂度上与哈希表方法相同,但时间复杂度稍高,因为它需要排序。这种方法在不允许修改原数组或需要保持元素原始顺序的情况下可能更有用。然而,对于"两数之和"这个问题,哈希表法通常是更优的选择,因为它的时间复杂度更低。

  ​image

  C++的实现

  ​image

  ‍

相关文章
|
1月前
|
算法
LeetCode刷题---167. 两数之和 II - 输入有序数组(双指针-对撞指针)
LeetCode刷题---167. 两数之和 II - 输入有序数组(双指针-对撞指针)
|
3月前
|
存储
LeetCode热题 首题 两数之和
LeetCode热题 首题 两数之和
19 1
|
3月前
|
索引 容器
双指针解决leetcode1两数之和
双指针解决leetcode1两数之和
19 0
|
4月前
|
存储 算法 Java
小白刷力扣之两数之和
小白刷力扣之两数之和
|
6月前
|
C语言
【Leetcode-1.两数之和 -3.无重复字符的最长子串 -9.回文数(C语言)】
【Leetcode-1.两数之和 -3.无重复字符的最长子串 -9.回文数(C语言)】
18 0
|
4月前
|
算法
【LeetCode】每日一题&&两数之和&&寻找正序数组的中位数&&找出字符串中第一个匹配项的下标&&在排序数组中查找元素的第一个和最后一个位置
【LeetCode】每日一题&&两数之和&&寻找正序数组的中位数&&找出字符串中第一个匹配项的下标&&在排序数组中查找元素的第一个和最后一个位置
|
7月前
|
缓存 算法 Java
【手绘算法】力扣 1 两数之和(Two Sum)
Hi,大家好,我是Tom。一个美术生转到Java开发的程序员。从今天开始,我将带大家每天刷一道题。我会用手绘的方式给大家讲解解题思路。在解题过程中,也会手写一些伪代码。当然,如果想要完整的源码的话,可以到我的个人主页简介中获取。 今天给大家分享的是力扣启蒙题第1题,求两数之和。虽然很简单,但是它的通过率只有52%。
64 0
|
1月前
|
机器学习/深度学习 算法 索引
leetcode热题100.两数之和
leetcode热题100.两数之和
13 1
|
1月前
|
C语言
【C语言】Leetcode 两数之和 (含详细题解)
【C语言】Leetcode 两数之和 (含详细题解)
83 0
|
2月前
|
存储 索引 Python
LeetCode刷题笔记(1)—— 两数之和
LeetCode刷题笔记(1)—— 两数之和