【leetcode1】1. 两数之和

题目名字 两数之和
链接 https://leetcode-cn.com/problems/two-sum/
难度 简单
类型 数组
方法 暴力枚举、哈希表

注:题目中数组中同一个元素不能使用两遍。意思是:结果不能用两遍同一个数,并且假设只有一个结果。


python 实现

方法一:暴力枚举

1. 个人解决的代码

思路: 寻找两数之和,最简单的想法就是循环遍历,使用两层循环,从左到右,当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。

# 为了增强代码的可读性,python3.5 之后,引入了一个 typing 模块,用来解释变量的类型。可有可无。
from typing import List
class Solution:
	def twoSum(self, nums:List[int], target: int) -> List[int]:
		id = 0
		for i, x in enumerate(nums):
			id += 1
			for j, y in enumerate(nums[i+1:]):
				if x + y == target:
					return [i, j+id]

代码解读: 使用了 内置函数 enumerate() 获得 index, nums[i+1:] 使用 list 切片只考虑当前元素 后面的元素。

2. 官方实现:
class Solution:
	def twoSum(self, nums: List[int], target: int) -> List[int]:
		n = len(nums)
		for i in range(n):
			for j in range(i + 1, n):
				if nums[i] + nums[j] == target:
					return [i, j]
					
nums = [2,7,11,15]
target = 9
sol = Solution()
result = sol.twoSum(nums, target)
print(result)

代码解读: 官方的实现更加简洁。先获取了 nums 的 长度,然后使用 range 来仅仅遍历 index,思路和上面的类似。

3. 复杂度分析:

时间复杂度:\(O(N^2)\) , 其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。

空间复杂度:\(O(1)\)

空间复杂度:如果程序所占用的存储空间和输入值无关,则该程序的空间复杂度就为 O(1);反之,如果有关,则需要进一步判断它们之间的关系:

  • 如果随着输入值 n 的增大,程序申请的临时空间成线性增长,则程序的空间复杂度用 O(n) 表示;
  • 如果随着输入值 n 的增大,程序申请的临时空间成 n2 关系增长,则程序的空间复杂度用 O(n2) 表示;
  • 如果随着输入值 n 的增大,程序申请的临时空间成 n3 关系增长,则程序的空间复杂度用 O(n3) 表示;

方法二:哈希表

思路: 注意到 方法一 的 时间复杂度 较高的原因是 寻找 target - x 的时间复杂度过高

使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 \(O(N)\) 降低到 \(O(1)\)

这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

class Solution:
	def twoSum(self, nums:List[int], target:int) -> List[int]:
		hashtable = dict()
		for i, num in enumerate(nums):
			if target - num in hashtable:
				return [hashtable[target - num], i]
			hashtable[num] = i

代码解读: Python 字典的底层实现是哈希表。当然字典也可以使用别的方式实现。实现字典的另一种常见方法是 red-black trees。每种方法都有各自的优缺点。红黑树总是可以在 \(O(log n)\) 中执行查找。哈希表可以在 \(O(1)\) 时间内执行查找,尽管根据输入,它可以降级为 o(n)。

显然,这里的 key 是 nums 中的元素,而 value 是 对应的 index。

复杂度分析:

时间复杂度:\(O(N)\),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 \(O(1)\) 地寻找 target - x。

空间复杂度:\(O(N)\),其中 N 是数组中的元素数量。主要为哈希表的开销。

显然, 哈希表是 空间换时间。


C++ 实现

方法一:暴力枚举

#include <vector>
#include <iostream>
using namespace std;

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

int main(){
	vector<int> nums = {2,7,11,15};
	int target = 9;

	Solution sol;
	vector<int> result = sol.twoSum(nums, target);

	for(auto array: result) {
		cout << array << " ";
	}

	cout << endl;
	return 0;
}

代码解读:实现 python 的官方实现一样。不过在代码中可以学习到:

  • 头文件知识
  • 标准命名空间
  • vector 怎么定义函数的返回值;怎么定义 vector 的形参;怎么定义 vector ;怎么传递 vector 实参; 如何接收 vector 的返回结果;
  • 如何打印 vector 的每个元素。

方法二:哈希表

#include <unordered_map>

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		unordered_map<int, int> hashtable;
		for (int i = 0; i < nums.size(); ++i) {
			// 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;
			// 反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
			auto it = hashtable.find(target - nums[i]);
			// hashtable.end(): 返回指向容器中最后一个键值对之后位置的正向迭代器。
			// 这里的意思是 it 在 hashtable 的最后一个键值对之前找到了该值。
			if (it != hashtable.end()) { 
				return {it->second, i};
			}
			hashtable[nums[i]] = i;
		}
		return {};
	}
};

代码解读:

  • 怎么导入 哈希表 头文件,怎么定义哈希表,怎么 找 key,怎么判断 找到的 key 在 哈希表中, 怎么在哈希表中添加元素。

参考:http://c.biancheng.net/view/7231.html

posted @ 2023-02-25 15:31  cold_moon  阅读(31)  评论(0编辑  收藏  举报