【经典算法】LeetCode1:两数之和(Java/C/Python3实现含注释说明,Easy)

简介: 【经典算法】LeetCode1:两数之和(Java/C/Python3实现含注释说明,Easy)

题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 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]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

原题:LeetCode 1

思路及实现

方式一:暴力解法(不推荐)

思路

最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x。

当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。

代码实现

Java版本
public int[] twoSum(int[] nums, int target) {
    int n = nums.length;
    for (int i = 0; i < nums.length; i++) { // 遍历数组,从第一个元素开始
        for (int j = i + 1; j < nums.length; j++) { // 在当前元素后面的元素中查找与目标值相加等于target的元素
            if (nums[i] + nums[j] == target) { // 如果找到了符合条件的元素对
                return new int[]{i, j}; // 返回这两个元素的下标
            }
        }
    }
    return new int[0]; // 如果没有找到符合条件的元素对,则返回空数组
}
C语言版本
#include <stdio.h>
#include <stdlib.h>
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
    int* result = (int*)malloc(2 * sizeof(int)); // 分配保存结果的内存空间
    *returnSize = 0; // 初始化返回结果数组的大小为0,表示没有找到满足条件的元素对
    
    for (int i = 0; i < numsSize; i++) { // 外层循环遍历数组中的每个元素
        for (int j = i + 1; j < numsSize; j++) { // 内层循环遍历当前元素后面的每个元素
            if (nums[i] + nums[j] == target) { // 检查两个元素的和是否等于目标值
                result[0] = i; // 将符合条件的第一个元素的下标存入结果数组的第一个位置
                result[1] = j; // 将符合条件的第二个元素的下标存入结果数组的第二个位置
                *returnSize = 2; // 更新返回结果数组的大小为2
                return result; // 返回结果数组
            }
        }
    }
    
    return result; // 返回结果数组,如果没有找到满足条件的元素对,数组中的元素值均为0
    
    // 注意:需要在适当的时候释放result指向的动态分配内存,以避免内存泄漏
}
Python3版本
from typing import List
def twoSum(nums: List[int], target: int) -> List[int]:
    result = [] # 用于存储结果的列表
    n = len(nums)
    
    for i in range(n): # 外层循环遍历列表中的每个元素
        for j in range(i+1, n): # 内层循环遍历当前元素后面的每个元素
            if nums[i] + nums[j] == target: # 检查两个元素的和是否等于目标值
                result.append(i) # 将符合条件的第一个元素的下标添加到结果列表中
                result.append(j) # 将符合条件的第二个元素的下标添加到结果列表中
                return result # 返回结果列表
    
    return result # 如果没有找到满足条件的元素对,返回空列表

复杂度分析

  • 时间复杂度分析:O(n^2),其中n为数组nums的长度。这是由于代码使用了两层循环来遍历数组。外层循环将执行n次,而内层循环则将执行(n-1)次、(n-2)次、…、2次、1次,总的执行次数为n * (n-1) / 2,即O(n^2)。
  • 空间复杂度分析:O(1),即常数级别的空间复杂度。因为代码只使用了常数个额外变量来存储元素的下标和存储结果的数组。

方式二:哈希表(推荐)

思路

注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。

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

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

代码实现

Java版本
import java.util.HashMap;
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //key为当前值,value为当前值的位置
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i]; // 计算差值,即目标值与当前元素的差值
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i}; // 返回HashMap中保存的差值元素的下标和当前元素的下标
            }
            map.put(nums[i], i); // 将当前元素添加到HashMap中
        }
        return new int[0]; // 如果没有找到满足条件的元素对,返回空数组
    }
}
C语言版本
#include <stdio.h>
#include <stdlib.h>
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
    int* result = (int*)malloc(2 * sizeof(int));
    *returnSize = 0;
    
    // 创建哈希表
    int hashtable[20001] = {0};
    
    for (int i = 0; i < numsSize; ++i) {
        int complement = target - nums[i]; // 计算差值,即目标值与当前元素的差值
        
        // 检查哈希表中是否存在差值
        if (complement >= -10000 && complement <= 10000 && hashtable[complement + 10000] != 0) {
            result[0] = hashtable[complement + 10000] - 1; // 返回哈希表中保存的差值元素的下标
            result[1] = i; // 返回当前元素的下标
            *returnSize = 2; // 更新返回结果数组的大小为2
            return result;
        }
        
        // 将当前元素添加到哈希表中
        hashtable[nums[i] + 10000] = i + 1;
    }
    
    return result;
}
Python3版本
from typing import List
from collections import defaultdict
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashtable = defaultdict(int) # 使用defaultdict来容纳哈希表
        for i in range(len(nums)):
            complement = target - nums[i] # 计算差值,即目标值与当前元素的差值
            if complement in hashtable:
                return [hashtable[complement], i] # 返回哈希表中保存的差值元素的下标和当前元素的下标
            hashtable[nums[i]] = i # 将当前元素添加到哈希表中
        
        return [] # 如果没有找到满足条件的元素对,返回空列表

复杂度分析

  • 时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。
  • 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。

方式三:双指针法

思路

双指针法是解决两数之和问题的一种常见方法。其思路是首先对输入数组进行排序,然后使用两个指针指向数组的起始位置和结束位置。在每一步迭代中,计算两个指针对应元素的和,并与目标值进行比较。

如果和等于目标值,说明找到了满足条件的两个数,返回它们的索引。

如果和小于目标值,说明需要增加和,因此将左指针向右移动一位。

如果和大于目标值,说明需要减少和,因此将右指针向左移动一位。

重复上述步骤直到找到满足条件的两个数或者左右指针相遇。

双指针法的优点是时间复杂度相对较低,只需要对数组进行一次排序,并且在有序数组中移动指针来寻找目标值,从而减少了比较次数。同时,该方法只需要常数级的额外空间。然而,需要注意的是双指针法要求输入数组必须是有序的,这会导致对原始数组的修改。

实现时可以使用Java的Arrays工具类对数组进行排序,然后使用左右指针按照上述思路进行迭代,最终找到满足条件的两个数或者返回空数组表示未找到。

代码实现

Java版本
import java.util.Arrays;
public class TwoSum {
    public int[] twoSum(int[] nums, int target) {
        // 对数组进行排序
        Arrays.sort(nums);
        
        // 初始化左右指针
        int left = 0;
        int right = nums.length - 1;
        
        while (left < right) {
            // 计算当前左右指针对应元素之和
            int sum = nums[left] + nums[right];
            
            if (sum == target) {
                // 找到目标值,返回对应的数组下标
                return new int[]{left, right};
            } else if (sum < target) {
                // sum小于目标值,将左指针向右移动一位
                left++;
            } else {
                // sum大于目标值,将右指针向左移动一位
                right--;
            }
        }
        
        // 没有找到满足条件的两个数,返回空数组
        return new int[]{};
    }
}

说明:

twoSum 方法接收一个整数数组 nums 和目标值 target,返回一个包含两个数的索引的整数数组,其中这两个数的和等于目标值。

首先使用 Arrays.sort(nums) 对数组进行排序。

初始化左指针 left 指向数组的起始位置,右指针 right 指向数组的结束位置。

使用 while 循环迭代,当左指针小于右指针时:

计算当前左右指针对应元素之和 sum。

如果 sum 等于目标值 target,则返回一个包含左右指针的整数数组。

如果 sum 小于目标值 target,将左指针向右移动一位。

如果 sum 大于目标值 target,将右指针向左移动一位。

如果循环结束后仍然没有找到满足条件的两个数,返回一个空数组。

需要注意的是,在返回结果后,需要及时释放动态分配的内存。

C语言版本
#include <stdio.h>
#include <stdlib.h>
int* twoSum(int* nums, int numsSize, int target) {
    // 对数组进行排序(可以使用快速排序等)
    qsort(nums, numsSize, sizeof(int), compare);
    
    // 初始化左右指针
    int left = 0;
    int right = numsSize - 1;
    
    while (left < right) {
        // 当前左右指针对应元素之和
        int sum = nums[left] + nums[right];
        
        if (sum == target) {
            // 找到目标值,返回对应的数组下标
            int* result = (int*)malloc(2 * sizeof(int));
            result[0] = left;
            result[1] = right;
            return result;
        } else if (sum < target) {
            // sum小于目标值,将左指针向右移动一位
            left++;
        } else {
            // sum大于目标值,将右指针向左移动一位
            right--;
        }
    }
    
    // 没有找到满足条件的两个数,返回空数组
    return NULL;
}
int compare(const void* a, const void* b) {
    // 用于qsort排序的比较函数
    return (*(int*)a - *(int*)b);
}

说明:

twoSum 函数接受三个参数:指向整数数组 nums 的指针、数组的大小 numsSize 和目标值

target;它返回一个指向整数数组的指针,该数组包含两个数的索引,从给定数组中的两个数之和等于目标值。 twoSum 内部首先调用

qsort 函数对数组进行升序排序,这里使用了自定义的 compare 比较函数。 初始化左右指针 left 和 right

分别指向数组的起始位置和结束位置。 使用 while 循环进行迭代,当左指针小于右指针时,执行以下操作: 计算左右指针对应元素的和 sum。

如果 sum 等于目标值 target,创建一个大小为 2 的动态整型数组 result,将左右指针的值分别赋给 result[0] 和

result[1],然后返回 result。 如果 sum 小于目标值 target,将左指针向右移动一位。 如果 sum 大于目标值

target,将右指针向左移动一位。 如果结束循环后仍未找到满足条件的两个数,返回 NULL 表示未找到。 compare

是用于升序排序的比较函数,用于传递给 qsort 函数。 请注意,在使用完返回的结果后,务必使用 free函数释放动态分配的内存,防止内存泄漏。

Python3版本
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 对数组进行排序
        nums.sort()
        
        left = 0  # 左指针
        right = len(nums) - 1  # 右指针
        
        while left < right:
            # 当前左右指针对应元素之和
            sum = nums[left] + nums[right]
            
            if sum == target:
                # 找到目标值,返回对应的数组下标
                return [left, right]
            elif sum < target:
                # sum小于目标值,将左指针向右移动一位
                left += 1
            else:
                # sum大于目标值,将右指针向左移动一位
                right -= 1
        
        # 没有找到满足条件的两个数,返回空数组
        return []

说明:

首先使用 nums.sort() 对数组进行排序。 初始化左指针 left 指向数组的起始位置,右指针 right指向数组的结束位置。 使用 while 循环迭代,当左指针小于右指针时: 计算当前左右指针对应元素之和。 如果和等于目标值

target,则返回包含左右指针的列表。 如果和小于目标值 target,将左指针向右移动一位。 如果和大于目标值

target,将右指针向左移动一位。 如果循环结束后仍然没有找到满足条件的两个数,返回一个空数组。

复杂度分析

  • 时间复杂度:O(n log n),该解法的时间复杂度取决于排序算法的时间复杂度,其中n表示数组的长度
  • 空间复杂度:O(1),只需要常数级的额外空间。请确保在使用完结果后释放动态分配的内存

总结

解法 思路 优点 缺点 时间复杂度 空间复杂度
暴力法 使用两层循环遍历所有可能的组合,找到符合要求的组合 简单直观,易于理解和实现 时间复杂度高,需要遍历所有可能的组合 O(n^2) O(1)
哈希表 遍历数组,使用哈希表记录每个数的索引,查找目标值减去当前数的结果是否存在 时间复杂度低,只需遍历一次数组 需要额外的空间来存储哈希表 O(n) O(n)
双指针法 将数组排序后使用左右指针进行查找,根据和与目标值的大小关系不断移动指针 时间复杂度低,只需遍历一次数组 需要对数组进行排序,改变原始数组的顺序 O(n log n) O(1)

相似题目

题目 描述 难度 LeetCode链接
三数之和(3Sum) 在给定整数数组中寻找所有不重复的三元组,使得它们的和为0 中等 LeetCode 15
四数之和(4Sum) 在给定整数数组中寻找所有不重复的四元组,使得它们的和为目标值 中等 LeetCode 18
两数之和 II - 输入有序数组(Two Sum II - Input array is sorted) 在有序整数数组中寻找两个数,使得它们的和等于目标值 简单 LeetCode 167
两数之和 III - 数据结构设计(Two Sum III - Data structure design) 设计一个类,支持在一个整数数组中寻找两个数的和等于目标值 简单 LeetCode 170
两数之和 IV - 输入BST(Two Sum IV - Input is a BST) 判断给定二叉搜索树中是否存在两个数的和等于目标值 简单 LeetCode 653
相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
91 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
1月前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
2月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
29 2
|
2月前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
141 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
|
2月前
|
算法 Java Linux
java制作海报一:java使用Graphics2D 在图片上写字,文字换行算法详解
这篇文章介绍了如何在Java中使用Graphics2D在图片上绘制文字,并实现自动换行的功能。
137 0
|
11天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
17天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
4天前
|
存储 算法
基于HMM隐马尔可夫模型的金融数据预测算法matlab仿真
本项目基于HMM模型实现金融数据预测,包括模型训练与预测两部分。在MATLAB2022A上运行,通过计算状态转移和观测概率预测未来值,并绘制了预测值、真实值及预测误差的对比图。HMM模型适用于金融市场的时间序列分析,能够有效捕捉隐藏状态及其转换规律,为金融预测提供有力工具。
|
4天前
|
机器学习/深度学习 算法 信息无障碍
基于GoogleNet深度学习网络的手语识别算法matlab仿真
本项目展示了基于GoogleNet的深度学习手语识别算法,使用Matlab2022a实现。通过卷积神经网络(CNN)识别手语手势,如&quot;How are you&quot;、&quot;I am fine&quot;、&quot;I love you&quot;等。核心在于Inception模块,通过多尺度处理和1x1卷积减少计算量,提高效率。项目附带完整代码及操作视频。
|
13天前
|
算法
基于GA遗传算法的PID控制器参数优化matlab建模与仿真
本项目基于遗传算法(GA)优化PID控制器参数,通过空间状态方程构建控制对象,自定义GA的选择、交叉、变异过程,以提高PID控制性能。与使用通用GA工具箱相比,此方法更灵活、针对性强。MATLAB2022A环境下测试,展示了GA优化前后PID控制效果的显著差异。核心代码实现了遗传算法的迭代优化过程,最终通过适应度函数评估并选择了最优PID参数,显著提升了系统响应速度和稳定性。