两数之和[中等]

一、题目

给你一个下标从1开始的整数数组numbers,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数target的两个数。如果设这两个数分别是numbers[index1]numbers[index2],则1 <= index1 < index2 <= numbers.length。以长度为2的整数数组[index1, index2]的形式返回这两个整数的下标index1index2。你可以假设每个输入 只对应唯一的答案 ,而且你不可以重复使用相同的元素。你所设计的解决方案必须只使用常量级的额外空间。

示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:27之和等于目标数9。因此index1 = 1, index2 = 2。返回[1, 2]

示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:24之和等于目标数6。因此index1 = 1, index2 = 3。返回[1, 3]

示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-10之和等于目标数-1。因此index1 = 1, index2 = 2。返回[1, 2]

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers非递减顺序排列
-1000 <= target <= 1000
仅存在一个有效答案

二、代码

【1】使用二分查找法: 通过target - numbers[i]得到目标值,然后根据二分查找目标值,二分查找小于目标值,取右边进行递归,否则取左边的数据进行递归。

class Solution {public int[] twoSum(int[] numbers, int target) {// 通过 target - numbers[i] 得到目标值,然后根据二分查找目标值,二分查找小于目标值,取右边进行递归,否则取左边的数据进行递归。int left = 0, right = 0, val = 0, mid = 0;for (int i = 0; i < numbers.length; i++) {left = i + 1;right = numbers.length - 1;// 需要查找的值val = target - numbers[i];// 中间数据while(left <= right) {mid = (right - left) / 2 + left;// 循环退出条件if (numbers[mid] == val) {return new int[] {i + 1 , mid + 1};}// 如果中间值大于目标值,二分查找左边数组,否则查找右边数组if (numbers[mid] > val) {right = mid;} else {left = mid + 1;}}}return new int[]{-1,-1};}
}

时间复杂度: O(nlog⁡n)其中n是数组的长度。需要遍历数组一次确定第一个数,时间复杂度是O(n),寻找第二个数使用二分查找,时间复杂度是O(log⁡n),因此总时间复杂度是O(nlog⁡n)
空间复杂度: O(1)

【2】双指针: 初始时两个指针分别指向第一个元素位置和最后一个元素的位置。每次计算两个指针指向的两个元素之和,并和目标值比较。如果两个元素之和等于目标值,则发现了唯一解。如果两个元素之和小于目标值,则将左侧指针右移一位。如果两个元素之和大于目标值,则将右侧指针左移一位。移动指针之后,重复上述操作,直到找到答案。

class Solution {public int[] twoSum(int[] numbers, int target) {// 思想:将左右指针累加后,如果sum > target 右指针-1,sum < target 左指针 + 1int left = 0 , right = numbers.length - 1;while (left < right) {int sum = numbers[left] + numbers[right];if (sum == target) {return new int[]{left + 1 , right + 1};} else if (sum > target){--right;}else {++left;}}return new int[]{-1,-1};}
}

时间复杂度: O(n)其中n是数组的长度。两个指针移动的总次数最多为n次。
空间复杂度: O(1)

【3】O(n)的双指针解法的本质原理: 很多人做这个题目想不到正确的O(n)解法,即使看了答案理解了,下次再做的时候还是会忘记。要想真正理解这道题,就要明白解法背后的道理。这样不仅可以记住这道题,还能举一反三解决类似的题目。

很多题解只给出了双指针解法的代码,但没有说明解法的正确性。为什么双指针往中间移动时,不会漏掉某些情况呢?要解答这个问题,我们要从 缩减搜索空间 的角度思考这个解法。下面我将以文字和图片两种方式进行讲解。

首先放上参考答案:

public int[] twoSum(int[] numbers, int target) {int i = 0;int j = numbers.length - 1;while (i < j) {int sum = numbers[i] + numbers[j];if (sum < target) {i++;} else if (sum > target) {j--;} else {return new int[]{i+1, j+1};}}return new int[]{-1, -1};
}

需要注意的是,虽然本题叫做Two Sum II,但解法和Two Sum完全不同。

图解双指针解法的原理: 在这道题中,我们要寻找的是符合条件的一对下标(i,j),它们需要满足的约束条件是:
【1】ij都是合法的下标,即0≤i<n,0≤j<n
【2】i<j(题目要求);
而我们希望从中找到满足A[i] + A[j] == target的下标(i,j)。以n=8为例,这时候全部的搜索空间是:

由于ij的约束条件的限制,搜索空间是白色的倒三角部分。可以看到,搜索空间的大小是O(n^2)数量级的。如果用暴力解法求解,一次只检查一个单元格,那么时间复杂度一定是O(n^2)。要想得到O(n)的解法,我们就需要能够一次排除多个单元格。那么我们来看看,本题的双指针解法是如何削减搜索空间的:

一开始,我们检查右上方单元格(0,7),即计算A[0] + A[7],与target进行比较。如果不相等的话,则要么大于target,要么小于target

假设此时A[0] + A[7]小于target。这时候,我们应该去找和更大的两个数。由于A[7]已经是最大的数了,其他的数跟A[0]相加,和只会更小。也就是说A[0] + A[6]A[0] + A[5]、……、A[0] + A[1]也都小于target,这些都是不合要求的解,可以一次排除。这相当于i=0的情况全部被排除。对应用双指针解法的代码,就是i++,对应于搜索空间,就是削减了一行的搜索空间,如下图所示。

排除掉了搜索空间中的一行之后,我们再看剩余的搜索空间,仍然是倒三角形状。我们检查右上方的单元格(1,7),计算A[1] + A[7]target进行比较。

假设此时A[0] + A[7]大于target。这时候,我们应该去找 和更小的两个数。由于A[1]已经是当前搜索空间最小的数了,其他的数跟A[7]相加的话,和只会更大。也就是说A[1] + A[7]A[2] + A[7]、……、A[6] + A[7]也都大于target,这些都是不合要求的解,可以一次排除。这相当于j=0的情况全部被排除。对应用双指针解法的代码,就是j++,对应于搜索空间,就是削减了一列的搜索空间,如下图所示。

可以看到,无论A[i] + A[j]的结果是大了还是小了,我们都可以排除掉一行或者一列的搜索空间。经过n步以后,就能排除所有的搜索空间,检查完所有的可能性。搜索空间的减小过程如下面动图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL修炼手册17:高级查询优化:深入理解执行计划

目录 写在开头1 执行计划的基本概念1.1 SQL查询解析1.1.1 语法分析1.1.2 语义分析1.1.3 内部数据结构的生成1.2 优化执行计划1.2.1 统计信息的利用1.2.2 索引选择1.2.3 连接算法1.2.4 子查询优化1.3 执行计划的评估1.3.1 统计信息的利用1.3.2 索引选择1.3.3 连接算法1.3.4 子查…

关于视觉3d目标检测学习像素深度的一点理解

在真实世界的一个物体&#xff0c;可以通过相机矩阵将其投影到像素坐标系上 但是&#xff0c;在像素坐标系上的像素&#xff0c;由于相机的原理&#xff0c;导致它的深度信息已经没有了&#xff0c;所以原理上是没法得到其真实深度的(即3d位置) 那么现在的深度学习方法又为什…

【AI】深度学习与图像描述生成——看图说话(1)

还记得我闲来无事&#xff0c;用大模型来“洗图”吗&#xff0c;就是想抄袭别人的图&#xff0c;但是又要装作原创的样子。因为洗稿大家都熟悉&#xff0c;洗图其实也是一样的。 【AIGC】今天想用AI“洗个图”&#xff0c;失败了&#xff0c;进来看我怎么做的-CSDN博客 【AIG…

哈夫曼树(Huffman)

哈夫曼树 Huffman 编码问题 问题引入 什么是编码&#xff1f; 简单说就是建立【字符】到【数字】的对应关系&#xff0c;如下面大家熟知的 ASC II 编码表&#xff0c;例如&#xff0c;可以查表得知字符【a】对应的数字是十六进制数【0x61】 \000102030405060708090a0b0c0d…

php下curl发送cookie

目录 一&#xff1a;使用 CURLOPT_COOKIE 选项 二&#xff1a;CURLOPT_COOKIEFILE 三&#xff1a;CURLOPT_HTTPHEADER php curl发送cookie的几种方式,下面来介绍下 一&#xff1a;使用 CURLOPT_COOKIE 选项 通过设置 CURLOPT_COOKIE 选项&#xff0c;你可以将 cookie 字符…

用ChatGPT创作留学文书三阶玩法详解!

最近人工智能聊天工具Chagpt火爆全网&#xff0c;从闲聊到编程&#xff0c;文学到菜谱&#xff0c;似乎以一种无所不能的姿态推翻了早期“人工智障”的屈辱招牌&#xff0c;正逐步进入并改变我们生产生活方式。作为文字处理大师&#xff0c;如果让人工智能来执笔大学申请文书会…

Linux 下查看端口以及释放端口

目录 一、查看端口是否被占用 1、使用 netstat 命令 2、使用 lsof 命令 二、释放端口 1、使用kill命令 2、使用 fuser 命令 三、netstat 四、lsof 五、fuser 一、查看端口是否被占用 在 Linux 系统上&#xff0c;你可以使用 netstat 或 lsof 命令来查看端口是否被占用。…

串口WiFi模块简介、工作原理、工作方式等8大知识点

WiFi模块又名串口WiFi模块&#xff0c;属于物联网传输层&#xff0c;功能是将串口或TTL电平转为符合WiFi无线网络通信标准的嵌入式模块&#xff0c;内置无线网络协议IEEE802.11b.g.n协议栈以及TCP/IP协议栈。串口WIFI模块&#xff0c;体积小&#xff0c;功耗低&#xff0c;采用…

springboot124中药实验管理系统设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的中药实验管理系统设计与实现 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章…

CentOS 安装 Ruby

1.下载 Ruby3.3 并安装 依次执行 wget https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz tar -zxvf ruby-3.3.0.tar.gz cd ruby-3.3.0 ./configure make make install 2.查看版本 ruby -v

深入了解Spring事件机制的作用与应用场景

Spring的事件机制为应用程序提供了一种灵活且解耦的方式来处理事件&#xff0c;它基于观察者模式构建&#xff0c;使得不同组件之间能够更加独立地工作。本文将深入探讨Spring事件机制的作用以及在实际应用中的使用场景。 作用&#xff1a; 解耦组件&#xff1a; 通过事件机制…

住宅IP VPS的應用指南

什麼是住宅IP VPS&#xff1f; 首先&#xff0c;我們來解釋一下什麼是住宅IP VPS。VPS是Virtual Private Server的縮寫&#xff0c;中文名為虛擬專用伺服器。它是一種虛擬化技術&#xff0c;可以將一臺物理伺服器分割為多個虛擬伺服器&#xff0c;每個虛擬伺服器都有自己獨立的…

【算法Hot100系列】合并区间

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

跨界做电商:百度、抖音、小红书异途同归

早些年&#xff0c;国内电商市场一直被淘宝、京东牢牢把持&#xff0c;其他电商平台只能跟在后面分一点羹。后来&#xff0c;随着拼多多崛起&#xff0c;电商格局开始生变。尤其是近两年&#xff0c;抖音、小红书、百度等玩家纷纷入局&#xff0c;更是让刚刚平静的电商市场又迎…

git tag的用法详解

目录 一、tag标识一个commit 二、查看tag 三、对分支打tag 四、删除tag 五、根据某个tag来clone 一、tag标识一个commit tag是用于去标记一个特定的commit。通常&#xff0c;在进行编译部署之前&#xff0c;我们需要对某一个即将release的版本进行tag&#xff0c;例如tag为…

vue常用指令(v-if)

一、v-if 指令 作用: 根据表达值的真假,切换元素的显示和隐藏( 操纵dom 元素 ) 二、代码演示 1、v-if 设置条件&#xff0c;添加按键修改状态 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta na…

运用ETLCloud快速实现数据清洗、转换

一、数据清洗和转换的重要性及传统方式的痛点 1.数据清洗的重要性 数据清洗、转换作为数据ETL流程中的转换步骤&#xff0c;是指在数据收集、处理、存储和使用的整个过程中&#xff0c;对数据进行检查、处理和修复的过程&#xff0c;是数据分析中必不可少的环节&#xff0c;对…

基本处理函数(ProcessFunction)

基本处理函数 处理函数在数据流处理中扮演着核心角色&#xff0c;负责定义数据流的转换操作。在Flink中&#xff0c;处理函数作为一种特殊的转换算子&#xff0c;提供了强大的功能来处理数据流。Flink几乎所有的转换算子都提供了对应的函数类接口&#xff0c;处理函数也不例外。…

C/C++ - Auto Reference

目录 auto Reference auto 当使用auto​​关键字声明变量时&#xff0c;C编译器会根据变量的初始化表达式推断出变量的类型。 自动类型推断&#xff1a;auto​​关键字用于自动推断变量的类型&#xff0c;使得变量的类型可以根据初始化表达式进行推导。 初始化表达式&#x…

阿里云对象存储(OSS)服务

阿里云对象存储&#xff08;OSS&#xff09;服务 引入依赖 <!--阿里云OSS服务--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>aliyun-oss-spring-boot-starter</artifactId><exclusions><!--排除默认版本的依…