当前位置: 首页 > news >正文

leetcode: Two Sum

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.lryc.cn/news/2618.html

相关文章:

  • 共享模型之无锁(三)
  • 微信小程序 Springboot校运会高校运动会管理系统
  • 走进独自开,带你轻松干副业
  • SpringBoot+Vue实现师生健康信息管理系统
  • 数据库第四章节第三次作业内容
  • 一篇五分生信临床模型预测文章代码复现——FIgure 9.列线图构建,ROC分析,DCA分析 (四)
  • 神经网络实战--使用迁移学习完成猫狗分类
  • Attention机制 学习笔记
  • 数据类型与运算符
  • 算法刷题-二叉树的锯齿形层序遍历、用栈实现队列 栈设计、买卖股票的最佳时机 IV
  • 华为OD机试 - 最小传递延迟(Python)| 代码编写思路+核心知识点
  • 集中供热调度系统天然气仪表内网仪表图像识别案例
  • 笔试题-2023-复旦微-数字IC设计【纯净题目版】
  • 【Linux】冯诺依曼体系结构和操作系统概念
  • HTML5之HTML基础学习笔记
  • FreeRTOS信号量 | FreeRTOS十
  • 【SpringBoot】SpringBoot常用注解
  • 数据一致性
  • Docker不做虚拟化内核,对.NET有什么影响?
  • HTML总结
  • ByteHouse:基于ClickHouse的实时数仓能力升级解读
  • [SSD固态硬盘技术 15] FTL映射表的神秘面纱
  • 浅析依赖注入框架的生命周期(以 InversifyJS 为例)
  • HER2靶向药物研发进展-销售数据-上市药品前景分析
  • 【第38天】不同路径数问题 | 网格 dp 入门
  • LINUX之链接命令
  • 1628_MIT 6.828 xv6_chapter0操作系统接口
  • 使用 Sahi 实现 Web 自动化测试
  • 天津菲图尼克科技携洁净及无菌防护服解决方案与您相约2023生物发酵展
  • Java 网络编程详解