leetcode: Two Sum

news/2024/10/20 18:53:41/

leetcode: Two Sum

  • 1. 题目
    • 1.1 题目描述
  • 2. 解答
    • 2.1 baseline
    • 2.2 基于baseline的思考
    • 2.3 优化思路的实施
      • 2.3.1 C++中的hashmap
      • 2.3.2 实施
      • 2.3.3 再思考
      • 2.3.4 最终实施
  • 3. 总结

1. 题目

1.1 题目描述

Given an array of integers nums and an integer target, return indices of the two 
numbers such that they add up to target. You may assume that each input would have 
exactly one solution, and you may not use the same element twice.
You can return the answer in any order.

Example 1:

Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].

Example 2:

Input: nums = [3,2,4], target = 6
Output: [1,2]

Example 3:

Input: nums = [3,3], target = 6
Output: [0,1]

Constraints:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • Only one valid answer exists.

2. 解答

2.1 baseline

  一个比较直观的想法是,罗列出所有的可能方案,然后找到和等于target的方案,返回即可。这里面蕴含的数学概念是:组合,从n个元素中取出2个元素; 用数学公式表示为cn2c_{n}^{2}cn2
  在用代码表示cn2c_{n}^2cn2之前,可以先绘制一下示意图:

  根据该示意图不难写出对应的代码。

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

2.2 基于baseline的思考

  baseline是一种“Brute Force”的思想,它的时间复杂度是o(n2)o(n^2)o(n2),这个时间复杂度极大概率不是最优的。
  多层循环的常用优化点在于将循环解耦。例如将o(n2)o(n^2)o(n2)----->o(n)+o(n)o(n) + o(n)o(n)+o(n)。外层循环表示的含义是数组中的每一个元素都有机会作为候选答案。因此这层循环很难去除。
  来看内层循环:内层循环在做的事情是对于当前nums[i]nums[i]nums[i], 通过遍历数组的方式,确认是否在其他元素中存在与之相加等于sum的元素;如果有找到答案。加粗的几个词语,是优化的关键:

“Brute Force”之所以效率低,是因为它在内循环中,试图以数组这一种数据结构,来解决查找问题。而数组的查找智能以遍历的方式进行,其查找的时间复杂度为n。

  而哈希表(hashmap)这种数据结构,可以做到查找问题以o(1)o(1)o(1)的时间复杂度进行。因此在进行真正的解决方案之前,先要根据数组构建对应的hashmap,以这种辅助数据的方式,将两层for循环进行解耦,从而时间复杂度降低为o(n)+o(n)o(n) + o(n)o(n)+o(n)

2.3 优化思路的实施

2.3.1 C++中的hashmap

   hashmap(哈希表)是一个概念,不同编程语言对其有自己的实现。c++将其实现为std::unordered_map形式,(这边需要强调,不是std::map,std::map是对read-black tree的实现,其插入元素和访问元素的时间复杂度是log(n)log(n)log(n))。
  具体相关的代码有:

#include <unordered_map>  // 头文件
std::unordered_map<int, int> um;  // 定义一个unordered_map 对象
um.contains(k);  //判断k键值是否在um中,该时间复杂度是o(1) c++20才支持
um.find(k) != um.end();  //判断k键值是否在um中,该时间复杂度是o(1) c++11才支持
int index = um[k];  //获得k健值对应的value,该时间复杂度为o(1)

2.3.2 实施

class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {std::unordered_map<int, int> um;for(int i = 0; i < nums.size(); ++i){um[nums[i]] = i;}for(int i = 0; i < nums.size(); ++i){int another = target - nums[i];if(um.find(another) != um.end() && um[another] != i)return {i, um[another]};}return {-1, -1};}
};

  这里要注意一下

um[another] != i

主要是对应题干中的you may not use the same element twice.

2.3.3 再思考

  但该方案提交leetcode之后,耗时仍旧位于第二峰值区域。说明有继续优化的空间。目前的方案时间复杂度为o(N)+o(n)o(N) + o(n)o(N)+o(n), 大写字母N表示是构建哈希表的部分,必须要遍历掉所有的元素;小写o(n)则是大概率仅仅会遍历部分元素,在中途就会找到答案中途退出。因此o(N)o(N)o(N)是当前的瓶颈。这里面细分,o(N)o(N)o(N)是存在冗余信息的:
  对于当前待查找对象nums[i], 我们只需知道在该元素之前是否有与之能够成功匹配的元素即可。因为若出现与nums[i]匹配的元素nums[ii]在i之后,则在处理元素nums[ii]的时候,nums[i]是作为ii之前,已经存在于hash表中的。这个时候nums[i]仍旧能够被翻出来。
  这样做的好处在于将o(N)+o(n)o(N) + o(n)o(N)+o(n)转变为o(n)+o(n)o(n) + o(n)o(n)+o(n)

2.3.4 最终实施

class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {std::unordered_map<int, int> um;for(int i = 0; i < nums.size(); ++i){int another = target - nums[i];if(um.find(another) != um.end())return {i, um[another]};um[nums[i]] = i; }return {-1, -1};}
};

  此时的解决方案,就可以位于第一峰值处。
在这里插入图片描述

3. 总结

  虽然"Brute Force"解法一般不是最优的,但快速的写出该解法作为baseline,是做进一步分析的前提。
  分析耗时的瓶颈所在:两层for循环导致的o(n2)o(n^2)o(n2)时间复杂度。往往可以借助于“辅助数据结构”,解耦到"o(N) + o(N)"的方案。而hashmap作为可以获得常量级插入和访问的数据结构,是非常优质的,需要熟悉其用法。
  若想要进一步优化,“o(N) + o(n)” --> "o(n) + o(n)"是一种手段。因为此时的瓶颈在于o(N)。
  最后在大的框架代码写完之后,要思考题干中的特殊限制代表的含义,例如you may not use the same element twice. 思考自己的代码是否已经体现了其含义。


http://www.ppmy.cn/news/24010.html

相关文章

网络流量传输MTU解析

基本概念 以太网的链路层对数据帧的长度会有一个限制&#xff0c;其最大值默认是1500字节&#xff0c;链路层的这个特性称为MTU&#xff0c;即最大传输单元 Maximum Transmission Unit&#xff0c;最大传输单元&#xff0c;指的是数据链路层的最大payload&#xff0c;由硬件网…

java数据类型

数据类型 类型分类&#xff0c;存储范围&#xff0c;字面量&#xff0c;默认值&#xff0c;类型转换 类型分类 存储范围 数据类型字节数表示范围byte1-128~127short2-32768~32767&#xff0c;正负3万左右int4-2147483648~2147483647&#xff0c;正负21亿左右long8-922337203…

CrossOver2023mac跨系统互通切换win虚拟机

CrossOver2023版是在Mac上运行Win软件的最简单方法&#xff0c;有了它&#xff0c;你无须 Win许可、重新启动或使用虚拟机即可在mac上使用Win软件。CrossOver23可以轻松地从Dock本地启动Win程序。CrossOver版还集成了macOS 功能&#xff0c;例如跨平台复制和粘贴&#xff0c;以…

【C++详解】——vector类

&#x1f4d6; 前言&#xff1a;本期介绍vector类。 目录&#x1f552; 1. vector的介绍&#x1f552; 2. vector的使用&#x1f558; 2.1 定义&#x1f558; 2.2 iterator&#x1f558; 2.3 空间增长&#x1f558; 2.4 增删查改&#x1f552; 2. vector的模拟实现&#x1f558…

【linux】线程概念

概念 什么是线程 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控制序列” 一切进程至少都有一个执行线程&#xff0c;线程在进程内部运行&#xff0c;本质是在进程地址空间内运行 在Linux系统中&a…

【大数据Hadoop】Hadoop 3.x 新特性总览

Hadoop 3.x 新特性剖析系列11. 概述2. 内容2.1 JDK2.2 EC技术2.3 YARN的时间线V.2服务2.3.1 伸缩性2.3.2 可用性2.3.3 架构体系2.4 优化Hadoop Shell脚本2.5 重构Hadoop Client Jar包2.6 支持等待容器和分布式调度2.7 支持多个NameNode节点2.8 默认的服务端口被修改2.9 支持文件…

实战打靶集锦-004-My-Cmsms

**写在前面&#xff1a;**记录一次艰难曲折的打靶经历。 目录1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 WEB服务探查4.1.1 浏览器访问4.1.2 目录枚举4.1.3 控制台探查4.1.4 其他目录探查4.2 阶段小结5. 公共EXP搜索5.1 CMS搜索5.2 Apache搜索5.3 PHP搜索5.4 MySQL搜索5…

福利篇1——嵌入式软件行业与公司汇总

前言 汇总嵌入式软件行业与公司,供参考。 文章目录 前言一、嵌入式软件行业和公司汇总1、芯片行业代表性公司2、人工智能代表性公司1)智能驾驶方向代表性公司2)机器人方向代表性公司3、消费电子领域代表性公司4、传统电子电器领域代表性公司5、国企和军工领域代表性公司6、网…