文章目录
- 第一题: 两数之和
- 题目描述
- 示例
- 解题思路
- Go语言实现 - 一遍哈希表法
- C++实现
- 算法分析
- 排序和双指针法
- Go语言实现 - 排序和双指针法
- C++
- 算法分析
- 暴力法
- Go语言实现 - 暴力法
- C++
- 算法分析
- 二分搜索法
- Go语言实现 - 二分搜索法
- C++
- 算法分析
第一题: 两数之和
题目描述
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组索引。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解题思路
- 暴力法:双重循环遍历数组中的每个数,寻找是否存在一个数与它相加等于目标值。
- 两遍哈希表法:遍历数组,将每个数及其索引存入哈希表,然后再次遍历数组,查找
target - nums[i]
是否在哈希表中。 - 一遍哈希表法:遍历数组,对于每个数,查找
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语言实现的代码简单易懂,效率较高,适合解决这类问题。
排序和双指针法
-
首先,创建一个数组,包含原始数组的元素和它们的索引。
-
然后,根据数组元素对数组进行排序。
-
使用两个指针,一个从开始(最小元素)的位置,另一个从结束(最大元素)的位置。
-
比较两个指针指向的元素之和与目标和:
- 如果和等于目标和,返回两个元素的索引。
- 如果和小于目标和,移动左侧指针,使它指向一个更大的数。
- 如果和大于目标和,移动右侧指针,使它指向一个更小的数。
这种方法的关键在于,排序后我们可以使用双指针高效地找到两个数,使得它们的和等于目标和。
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),我们需要一个额外的数组来存储元素和它们的索引。
这种方法在空间复杂度上与哈希表方法相同,但时间复杂度稍高,因为它需要排序。然而,这种方法在不允许修改原数组或需要保持元素原始顺序的情况下可能更有用。
暴力法
- 使用两个嵌套循环遍历数组中的所有元素对。
- 对于每一对元素,检查它们的和是否等于目标和。
- 如果找到一对元素的和等于目标和,返回这两个元素的索引。
这种方法的时间复杂度是 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),我们只需要常数级别的额外空间来存储索引。
暴力法是一种简单直接的方法,但是对于大型数据集来说,它的效率不高。在实际应用中,通常会优先考虑使用哈希表或排序和双指针法来解决这类问题。
二分搜索法
- 首先,对数组进行排序,并创建一个新数组来保存排序后元素的原始索引。
- 对于数组中的每个元素,使用二分搜索查找
target - nums[i]
。 - 如果找到,确保两个元素的原始索引不同,然后返回它们的原始索引。
这种方法的关键在于,排序后我们可以使用二分搜索高效地找到第二个数。
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),我们需要一个额外的数组来保存索引。
二分搜索法在空间复杂度上与哈希表方法相同,但时间复杂度稍高,因为它需要排序。这种方法在不允许修改原数组或需要保持元素原始顺序的情况下可能更有用。然而,对于"两数之和"这个问题,哈希表法通常是更优的选择,因为它的时间复杂度更低。
C++的实现