大家好,我是漫步coding, 最近在整理2022年Redis最新面試題, 大家也可以通過我下面的博客地址在線閱讀,今天講講LeetCode高頻算法面試題 - 兩數(shù)之和。本文首發(fā)于公眾號:漫步coding
題目來源于 LeetCode 上第 1 號問題:兩數(shù)之和。題目難度為 Easy。
題目描述
給定一個整數(shù)數(shù)組 nums
和一個目標(biāo)值 target
,請你在該數(shù)組中找出和為目標(biāo)值的那 兩個 整數(shù),并返回他們的數(shù)組下標(biāo)。
你可以假設(shè)每種輸入只會對應(yīng)一個答案。但是,你不能重復(fù)利用這個數(shù)組中同樣的元素。
題目難度: ★
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
示例 2:
輸入:nums = [3,2,4], target = 6
輸出:[1,2]
示例 3:
輸入:nums = [3,3], target = 6
輸出:[0,1]
題目解析
使用查找表來解決該問題。
設(shè)置一個 map 容器 record 用來記錄元素的值與索引,然后遍歷數(shù)組 nums。
- 每次遍歷時使用臨時變量 complement 用來保存目標(biāo)值與當(dāng)前值的差值
- 在此次遍歷中查找 record ,查看是否有與 complement 一致的值,如果查找成功則返回查找值的索引值與當(dāng)前變量的值 i
- 如果未找到,則在 record 保存該元素與索引值 i
代碼實現(xiàn)
tips: 以下代碼是使用Go代碼實現(xiàn)的不同解法, 文章最后可以看C++、C、Java、Python實現(xiàn)
1、暴力解法
最容易想到的方法是枚舉數(shù)組中的每一個數(shù) x,尋找數(shù)組中是否存在 target - x。
當(dāng)我們使用遍歷整個數(shù)組的方式尋找 target - x 時,需要注意到每一個位于 x 之前的元素都已經(jīng)和 x 匹配過,因此不需要再進行匹配。而每一個元素不能被使用兩次,所以我們只需要在 x 后面的元素中尋找 target - x。
func twoSum(nums []int, target int) []int {
numsLen := len(nums)
for i, num := range nums {
for j := i + 1; j < numsLen; j++ {
if target == num + nums[j] {
return []int{i, j}
}
}
}
return []int{}
}
執(zhí)行結(jié)果:
- 時間復(fù)雜度:O(N^2),其中 N 是數(shù)組中的元素數(shù)量。最壞情況下數(shù)組中任意兩個數(shù)都要被匹配一次。
- 空間復(fù)雜度:O(1)。
內(nèi)存方面還可以,不過運行時間偏慢, 時間復(fù)雜度太高了。
2、使用map方式, nums 的值進行倒排索引, 以空間換時間, 時間復(fù)雜度是O(n)
func twoSum(nums []int, target int) []int {
numsMap := make(map[int]int)
for index, num := range nums{
numsMap[num] = index
}
for index, num := range nums {
if preIndex, ok := numsMap[target-num]; (ok && preIndex != index) {
return []int{preIndex, index}
}
}
return []int{}
}
執(zhí)行結(jié)果:
時間復(fù)雜度:O(n),我們只遍歷了包含有O(n)個元素的列表兩次。在表中進行的每次查找只花費 O(1)的時間。
空間復(fù)雜度:O(n),所需的額外空間取決于哈希表中存儲的元素數(shù)量,該表最多需要存儲 N個 元素。
3、使用map方式, nums 的值進行倒排索引, 一遍哈希表
事實證明,我們可以一次完成。在進行迭代并將元素插入到表中的同時,我們還會回過頭來檢查表中是否已經(jīng)存在當(dāng)前元素所對應(yīng)的目標(biāo)元素。如果它存在,那我們已經(jīng)找到了對應(yīng)解,并立即將其返回。
func twoSum(nums []int, target int) []int {
numsMap := make(map[int]int)
for index, num := range nums {
if preIndex, ok := numsMap[target-num]; ok {
return []int{preIndex, index}
}
numsMap[num] = index
}
return []int{}
}
執(zhí)行結(jié)果:
時間復(fù)雜度:O(n),我們只遍歷了包含有O(n)個元素的列表一次。在表中進行的每次查找只花費 O(1)的時間。
空間復(fù)雜度:O(n),所需的額外空間取決于哈希表中存儲的元素數(shù)量,該表最多需要存儲 N個元素。
4、雙指針游標(biāo)
雙指針的思路
- 將原數(shù)組排好序, 設(shè)置兩個指針left, right
- left 從0開始, right 做 len(nums) - 1 開始
- 當(dāng)nums[left] + nums[right] < target時,我們就沒有必要對所有的right0 ∈(left, right),因為nums[left] + nums[right0] 一定是比target小的, 所以left = left + 1后重新判斷
- 當(dāng)nums[left] + nums[right] > target時,同樣的,對left0 ∈(left, right),必然有nums[left0] + nums[right] > target, 所以right = right - 1后重新判斷
- 當(dāng)nums[left] + nums[right] = target時,就是我們想要知道的兩個數(shù)值了
- 然后利用Map將數(shù)值轉(zhuǎn)換為之前的游標(biāo)
func twoSum(nums []int, target int) []int {
type Node struct{
Value int
Next *Node
}
// 用鏈表是解決數(shù)組重復(fù)問題
numsMap := make(map[int]*Node)
var current *Node
for index, num := range nums{
if _, ok := numsMap[num]; ok{
current = numsMap[num]
for {
if current.Next == nil{
current.Next = &Node{index, nil}
break
}
current = current.Next
}
}else{
numsMap[num] = &Node{index, nil}
}
}
sort.Ints(nums)
left := 0
right := len(nums) - 1
for {
if left >= right{
break
}
sum := nums[left] + nums[right]
if sum == target{
if nums[left] == nums[right]{
return []int{numsMap[nums[left]].Value, numsMap[nums[left]].Next.Value}
}else{
return []int{numsMap[nums[left]].Value, numsMap[nums[right]].Value}
}
}
if sum < target{
left ++
}else{
right --
}
}
return []int{}
}
執(zhí)行結(jié)果:
因為要排序,時間復(fù)雜度也比較高, 需要Map存儲游標(biāo),空間復(fù)雜度也比較高, 算是一個不太好的實現(xiàn)方式。
其他語言版本
C++
// 1. Two Sum
// 時間復(fù)雜度:O(n)
// 空間復(fù)雜度:O(n)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> record;
for(int i = 0 ; i < nums.size() ; i ++){
int complement = target - nums[i];
if(record.find(complement) != record.end()){
int res[] = {i, record[complement]};
return vector<int>(res, res + 2);
}
record[nums[i]] = i;
}
return {};
}
};
執(zhí)行結(jié)果
[圖片上傳失敗...(image-51924b-1653220059347)]
C
// 1. Two Sum
// 時間復(fù)雜度:O(n)
// 空間復(fù)雜度:O(n)
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
int *ans=(int *)malloc(2 * sizeof(int));
int i,j;
bool flag=false;
for(i=0;i<numsSize-1;i++)
{
for(j=i+1;j<numsSize;j++)
{
if(nums[i]+nums[j] == target)
{
ans[0]=i;
ans[1]=j;
flag=true;
}
}
}
if(flag){
*returnSize = 2;
}
else{
*returnSize = 0;
}
return ans;
}
執(zhí)行結(jié)果
Java
// 1. Two Sum
// https://leetcode.com/problems/two-sum/description/
// 時間復(fù)雜度:O(n)
// 空間復(fù)雜度:O(n)
class Solution {
public int[] twoSum(int[] nums, int target) {
int l = nums.length;
int[] ans=new int[2];
int i,j;
for(i=0;i<l-1;i++)
{
for(j=i+1;j<l;j++)
{
if(nums[i]+nums[j] == target)
{
ans[0]=i;
ans[1]=j;
}
}
}
return ans;
}
}
執(zhí)行結(jié)果
Python
# 1. Two Sum
# https://leetcode.com/problems/two-sum/description/
# 時間復(fù)雜度:O(n)
# 空間復(fù)雜度:O(n)
class Solution(object):
def twoSum(self, nums, target):
l = len(nums)
print(nums)
ans=[]
for i in range(l-1):
for j in range(i+1,l):
if nums[i]+nums[j] == target:
ans.append(i)
ans.append(j)
print([i,j])
break
return ans
執(zhí)行結(jié)果
結(jié)語
綜合來看, 選擇方式3比較好: 使用map方式, nums 的值進行倒排索引, 一遍哈希表, Go的實現(xiàn)在內(nèi)存方面和時間復(fù)雜度都還可以, Python相對表現(xiàn)就差很多。
幾種語言運行效果對比
Go程序在內(nèi)存方面表現(xiàn)最佳, Python在內(nèi)存和運行時間表現(xiàn) 比較其他的語言都比較差。