LeetCode题集-1- 两数之和

这个题目是什么意思呢?简单来说就是在一个数组中找出两个元素,使其和为我们设定的值,并且每个元素只能用一次。
如下图具体示例:

到这里不知道你是否已经有解题思路了呢?
解法一:双层循环
我第一反应就是双层循环,直接暴力破解。因为题目要求每个元素只能使用一次,并且已经计算过的也没必要再次计算,因此内层循环索引起始可以以外层索引+1作为起始点,具体代码如下:
public static int[] TwoSumForFor(int[] nums, int target)
{
for (var i = 0; i < nums.Length; i++)
{
for (var j = i + 1; j < nums.Length; j++)
{
if (nums[i] + nums[j] == target)
{
return [i, j];
}
}
}
return [];
}
我们直接验证一下,通过了:

因为是双层循环因此算法时间复杂度是:O(N2),因为没有引用额外的空间因此空间复杂度是:O(1)。
注:上面的[] ,[i, j]是C#12版本新增功能,是数组简洁表达语法。
解法二:双层循环+左右开弓
如果想在双层循环基础上继续优化算法要怎么办?
我们就按正常思维逻辑来梳理一下双层循环干了什么,外层循环:表示第一个加数,并且从第一个数到最后一个数循环一遍;内层循环:表示第二个加数,其作用就是使第一个加数按从前到后的顺序和其后面的每一个数加一遍。既然如此那能不能从前往后计算的同时也从后往前计算呢?显然使可以的,代码如下:
public static int[] TwoSumForForBidirectional(int[] nums, int target)
{
for (var i = 0; i < nums.Length; i++)
{
var front = nums[i];
var backIndex = nums.Length - 1 - i;
var back = nums[backIndex];
for (var j = i + 1; j < nums.Length; j++)
{
if (front + nums[j] == target)
{
return [i, j];
}
if (back + nums[j - 1] == target)
{
return [j - 1, backIndex];
}
}
}
return [];
}
运行结果如下:

理想情况下可能会提升一倍的效率,但是细心的朋友应该发现,平台上的运行结果,比双层循环还长了3ms,不过感觉这个平台结果不是很准,下面我们用基准测试,对两个方法进行测试,我们随机构建长度为2000的数组,并把目标数随机放到不同位置,测试10000次。

从基准测试的结果来看,整体上并没有提升多少性能。这是因为这个算法本质上时间复杂度还是:O(N2),因此并没有真正起到优化的作用,只有特定的数据分别可能才会有相对较好的表现,这个算法就当作给我们提供了一种解题思路吧。
解法三:单层循环+LastIndexOf
既然左右开弓不行,我们换一个思路,想办法去掉一层循环。
首先外层循环需要保留,因为需要把每个元素都计算一遍,因此我们从内层循环下手。想想题目,是要找到两个数使其和为目标值,那么我们是否可以在循环第一个数时候,通过目标值计算出我们要找的第二个值,看看这个值是否存在,如果存在,则完成算法,否则继续循环直到找到为止。按照这个思路我立马想到C#里的IndexOf和LastIndexOf方法,直接上代码:
public static int[] TwoSumForLastIndexOf(int[] nums, int target)
{
for (var i = 0; i < nums.Length; i++)
{
var j = Array.LastIndexOf(nums, target - nums[i], nums.Length - 1, nums.Length - 1 - i);
if (j >= 0 && j != i)
{
return [i, j];
}
}
return [];
}
运行结果如下:

注:Array.LastIndexOf<T>(T[] array, T value, int startIndex, int count)方法可以指定从什么地方开始查找,查找多少个数。
同样我们再做一次基准测试做对比。

可以发现这一版本算法性能大幅提升,但是我们细想一下,LastIndexOf方法本质还是在数组中找一个元素,最坏的情况还是要把整个数组遍历一遍,只能说C#本身做了很好的优化使其性能很高,但是从算法时间复杂度的角度来看,其仍然是 O(N) 的,也就是说这一版本算法时间复杂度还是O(N2)。
解法四:单层循环+字典(哈希)
哪到底如何才能把O(N)的集合查找时间复杂度优化了呢?如果能改造到O(1) 就好了,顺着这个思路还真想到了一种数据结构-哈希表,可以做到O(1) 的查找时间复杂度。
在C#中可以使用Dictionary字典类型,数据结构选好了,下面就是怎么用的问题,key存什么?value存什么?
再次回忆一下题目要求,是找到两个数使其和为目标值,假设x+y=target,x为第一个数,并且外层循环第一个数,那么当处理数据x时,同时能得到y=target-x,如果数组中后面存在值为y的元素,是不是就意味着这对[x,y]就是我们要找的值,因此我们可以在处理x时,把y=target-x和x所在索引记录下来,正好分别对应Dictionary的key和value。代码如下:
public static int[] TwoSumDictionary(int[] nums, int target)
{
var dic = new Dictionary<int, int>();
for (var i = 0; i < nums.Length; i++)
{
if (dic.TryGetValue(nums[i], out var value))
{
return [value, i];
}
dic.TryAdd(target - nums[i], i);
}
return [];
}
执行结果如下

运行正确,我们再来用基准测试对比一下。

结果和我们预想竟然不一样,还没有LastIndexOf方法效果好,哪里出了问题呢?
下面我们对这两种方法单独做一次全方面的基准测试对比,对每个方法分别用数组长度为100,1000,10000三种情况,各进行10000次测试。结果如下:

可以发现,只有当数组长度越来越大的时候,哈希表方案优势才慢慢体现出来。
我们再来分析一下这个算法复杂度,首先外层循环时间复杂度为O(N) ,字典操作时间复杂度为O(1),因此整体时间复杂度为O(N) 。因为字典需要额外的存储空间并且最大长度为数组长度减1,因此空间就复杂度为O(N) 。这是经典的以空间换时间方案。
从上面的对比不难发现,即使再好的方案也有其使用场景,数据量的大小,空间的大小都会制约着算法方案的选择,因此需要我们因时制宜选出最合适的方案。
没有最好只有最合适。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
LeetCode题集-1- 两数之和的更多相关文章
- Leetcode题库——1.两数之和
@author: ZZQ @software: PyCharm @file: addTwoNumbers.py @time: 2018/9/18 10:35 要求:给定两个非空链表来表示两个非负整数. ...
- Leetcode:0002(两数之和)
LeetCode:0002(两数之和) 题目描述:给定两个非空链表来表示两个非负整数.位数按照逆序方式存储,它们的每个节点只存储单个数字.将两数相加返回一个新的链表.你可以假设除了数字 0 之外,这两 ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- Leetcode(1)两数之和
Leetcode(1)两数之和 [题目表述]: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标.你可以假设每种输入只会对应一 ...
- [LeetCode] 1. Two Sum 两数之和
Part 1. 题目描述 (easy) Given an array of integers, return indices of the two numbers such that they add ...
- LeetCode Golang实现 1. 两数之和
1. 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这 ...
- Leetcode(一)两数之和
1.两数之和 题目要求: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重 ...
- LeetCode题解001:两数之和
两数之和 题目 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个 ...
- [Leetcode] Add two numbers 两数之和
You are given two linked lists representing two non-negative numbers. The digits are stored in rever ...
- 【LeetCode】[0002] 【两数之和】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 给出两个非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字.如果 ...
随机推荐
- 火山引擎数智平台赋能火花思维,A/B测试加速创新
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群. 在数字化浪潮下,火花思维凭借其对数据驱动的理解与实践,搭上了业务快速增长的快车.这一效果的背后,离不开火花思 ...
- 记一次centos7.9崩溃恢复操作(limits.conf配置失误),救援模式
引起故障的原因:调整了操作系统的内核参数文件limits.conf,* soft nproc 131072 * hard nproc 131072 * soft nofile 65536 * ...
- 【实操记录】MySQL主从配置
本文使用MySQL原生支持的主从同步机制,详细记录了配置步骤及运维操作方法,可供大家直接参考.使用. 本文假设已经部署了两台主机的MySQL软件,且数据库服务正常,详细部署步骤可本站搜索:" ...
- 自学Java第二周
本周学习 一.Java能干些什么? 1.共三个版本:Java SE.Java EE.Java ME Java SE:Java语言的(标准版),用于桌面应用开发,是其他两个版本的基础. Java ME: ...
- 完美卸载Docker
1,删除docker所在目录 rm -rf /etc/docker rm -rf /run/docker rm -rf /var/lib/dockershim rm -rf /var/lib/dock ...
- apache ab.exe压力测试
ab.exe是一个性能检测工具,是apache server中的一个小组件,使用简单,方便 下载地址:http://files.cnblogs.com/files/gossip/ab.zip ...
- 第四章:springboot整合mybatis
1,引入mybatis依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifact ...
- ABC357
A link 循环加每一个数,加到哪个数不能加了输出前一个数,注意如果加到最后还能加,记得输出\(n\). 点击查看代码 #include<bits/stdc++.h> using nam ...
- hbuilderx生成ios证书和上架全教程
现在很多公司都使用uniapp作为底层框架来开发app应用,而uniapp的开发工具hbuilderx云打包的时候,需要证书和证书profile文件. 假如是ios应用,则还需要上架到appstore ...
- Gradle的安装和创建java项目(idea)
安装 Gradle下载地址:http://services.gradle.org/distributions/ 下载后解压. 解压后的目录结结构如下: 新增环境变量 在path环境变量中添加以下内容: ...