【刷题笔记】两数之和II_二分法||二分查找||边界||符合思维方式

两数之和II_二分法||二分查找

1 题目描述

https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

2 思路

看到这一个题目,数组和有序,第一个想法就是二分查找(当然看了官方题解之后,发现还有双指针做法)。

做法很简单,给定了target,我们对数组numbers进行遍历,对于numbers的每一个元素,重新在数组中寻找和该元素之和为target的元素。如果找到的元素和遍历到的元素重复了,那么继续遍历,然后再寻找;如果不重复,那就将两个元素的位置都加一获得真实位置,然后返回。

public int[] twoSum(int[] numbers, int target) {int pre = 0;for (int i = 0; i < numbers.length; i++) {if (i > 0 && numbers[i] == pre) // 如果遇到重复元素,就别浪费这个时间了。continue;int other_tar = target - numbers[i];int pos = biSearch(numbers, other_tar);if (pos >= 0) {if (pos == i) continue; // 防止找到自己头上, 比如参数是([0,4,5], 8), 就有会找到两个4。return new int[]{i + 1, pos + 1}; // 索引+1变成真实位置。}pre = numbers[i]; // 记录前一个值}return null;
}

那么最重要的问题是,如何进行二分查找?

我们就事论事,如何分析这个题目呢?

相似题目可以看我的这篇文章【刷题笔记】H指数||数组||二分查找的变体

题目本身已经强调了,非递减顺序,也就是说,数组中可能会存在一些相等的元素。举个例子

towSum([1,3,4,4], 8)

当我们遍历到第一个4的时候,为了让和为8,我们需要在数组中找到另一个为4的元素。如果二分是普通的二分查找:

 if (numbers[mid_index] == num) {return mid_index;}
else if (numbers[mid_index] < num) l = real_mid + 1;
else r = real_mid - 1;

我们极有可能在碰到第一个4的时候,就返回了其坐标,而这和我们题目中的要求是冲突的。那么我们就会接着遍历numbers数组,碰到第二个4,然后继续进行二分查找,找到了第一个4,返回位置,发现这个组合是符合要求的,最后我们返回的两个位置是[4,3]。我替你们试过了,就算你返回的的两个坐标是对的,还是要保证坐标是从小到大的,反过来也会报错。

怎么解决这种存在多个相等元素的问题呢?我的解决方法是分成两步:

  1. 遍历numbers的时候,遇到重复元素,只遍历第一个。上面代码中的:
 if (i > 0 && numbers[i] == pre) // 如果遇到重复元素,就别浪费这个时间了。continue;

就是解决这个问题的。

  1. 在二分查找的时候,只找重复元素的最后一个。这样假设碰到多个重复元素,而且恰好两个重复元素之和等于我们target的情况,最终返回的坐标一定是递增顺序的。

那么我们接下来考虑,怎么在二分查找的时候,找到最后一个符合条件的元素呢?

对于二分查找有两个最重要的问题:如何计算mid如何跳转left和right

我在【刷题笔记】H指数||数组||二分查找的变体这篇博客中提出了一个思考的范式:

这个两个问题本身是一个问题,只要我们确定了如何跳转left和right,就能确定如何计算mid。

(这只是我的一点浅薄的看法,大家要根据自己的刷题情况实时更新自己的理念,我考虑出来的东西也不一定具有普适性,我只是刚刷题的菜鸡。)

怎么理解上面这句话呢?比如我们这道题,我们确定了目标是寻找满足条件(即等于other_tar)的最后一个元素,也就是右边界,一个很符合直觉的想法就是,left指针是不断右移的,要找右边界,left指针最合适。

在这里插入图片描述

那么我们看到,当遇到这种情况的时候,numbers[mid]==other_tarmid可能是右边界吗?可能。那mid右边的位置还可能是右边界吗?不一定。如果left移动到mid右边,会不会错过右边界?可能会。所以,为了避免这种跳过边界的可能性,当mid满足条件的时候,我们不是直接返回mid,而是让left转移到mid的位置上,通过left不断寻找右边界。而当numbers[mid] < other_tar的时候,让left++;当numbers[mid] > other_tar的时候,right--,这些都是常规操作了。

也就是说,当查找边界的时候,我们的left指针最后可能会指向边界。为什么是可能?因为数组里可能没有我们要找的值,这时候left一路向右,势不可挡,直接窜到了数组的边界之外,也是可能的。

现在我们解决了第二个问题,如何跳转left和right。那么我们回过头解决第一个问题,如何计算mid

第二个问题,我们可以直接考虑在只剩下两个元素的时候(即只剩下left和right的时候),该如何计算mid

在这里插入图片描述

众所周知,当我们在只剩下两个元素的时候,mid元素要么是(left + right) / 2,放在left上,要么是(left + right) / 2 + 1,放在right上。

我们已经确定了,left在某些条件下是可能直接跳转到mid上的, 如果让mid=left,下一步如果left需要跳转,left=mid,然后mid=left。。。。。。无限循环。

所以,为了避免死循环,当只有偶数个元素的时候,我们需要让mid跳转到中间两个元素的后一个元素上。所以我说,当我们确定了leftright的跳转问题之后,如何计算mid的问题就迎刃而解。

当然,传统的二分法,left跳转到mid+1,right跳转到mid-1,不会出现死循环的。咱们现在讨论的是边界问题,所以有些特殊。

我还是那句话,我花生豆大的小脑仁接受不了太多弯弯绕绕,面对二分问题的时候,left和right的取值,我倾向于直接使用真实位置,即从1开始的位置。

public int biSearch(int[] numbers, int num) {int l = 1, r = numbers.length;...
}

这样有一个好处就是符合我们的思维直觉,本身二分法的变化就多,能简化思考的地方就简化思考。

那么如果l~r范围内(闭区间)的元素个数是奇数个,(l+r)/2就是中间数的真实位置,如果是偶数个,我们就设为(l+r)/2 + 1。这个方法虽然笨,但是符合我们的思维直觉。

 public int biSearch(int[] numbers, int num) {int l = 1, r = numbers.length;while (l < r) { // 一旦两个指针重合,遍历结束,要么是找到了,要么是l跳到边界外了。int real_mid = (l + r) / 2 + ((l - r + 1) % 2 == 0 ? 1 : 0);int mid_index = real_mid - 1;if (numbers[mid_index] == num) l = real_mid;else if (numbers[mid_index] < num) l = real_mid + 1;else r = real_mid - 1;}if (l <= numbers.length && numbers[l-1] == num) return l-1;else return -1;
}

在这里,所有的指针我都使用了真实值,只有在用到numbers[mid_index]的时候,才用的索引。当然,这些都是我自己的习惯。

因为我们是用left指针来判断边界,left在跳转的过程中,我们计算mid的时候是选择了靠右型,如果left跳转到mid+1的位置,可能跳出边界。

所以我们最后的判断条件是if (l <= numbers.length && numbers[l-1] == num) return l-1;
返回索引。

3 代码

class Solution {public int[] twoSum(int[] numbers, int target) {int pre = 0;for (int i = 0; i < numbers.length; i++) {if (i > 0 && numbers[i] == pre)continue;int other_tar = target - numbers[i];int pos = biSearch(numbers, other_tar);if (pos >= 0) {if (pos == i) continue;return new int[]{i + 1, pos + 1};}pre = numbers[i];}return null;}public int biSearch(int[] numbers, int num) {int l = 1, r = numbers.length;while (l < r) {int real_mid = (l + r) / 2 + ((l - r + 1) % 2 == 0 ? 1 : 0);int mid_index = real_mid - 1;if (numbers[mid_index] == num) l = real_mid;else if (numbers[mid_index] < num) l = real_mid + 1;else r = real_mid - 1;}if (l <= numbers.length && numbers[l-1] == num) return l-1;else return -1;}
}

在这里插入图片描述

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

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

相关文章

【软件测试】银行核心业务系统性能测试总结,一篇通透...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 下面讨论的是字符…

zblog免费插件应用中心-zblog最全插件功能

随着网络时代的不断发展&#xff0c;博客已经成为人们分享知识、表达观点的重要平台之一。在众多博客系统中&#xff0c;Zblog因其简洁、高效的特点备受欢迎。然而如何让博客更具吸引力、提高曝光度&#xff0c;成为许多博主关注的问题。在这个问题面前&#xff0c;Zblog插件应…

代码随想录第十六天(一刷C语言)|找树左下角的值路径总和从中序与后序遍历序列构造二叉树

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、找树左下角的值 思路&#xff1a;采用递归 ledcode题目&#xff1a;https://leetcode.cn/problems/find-bottom-left-tree-value/description/ AC代码&#xff1a; /*** Definition f…

Attention机制(笔记)

参考&#xff1a;2.3.2注意力机制-part1_哔哩哔哩_bilibili 什么是attention&#xff1f; 答&#xff1a;注意力放在事物最有辨识度的部分 attention计算机制&#xff1a; 为什么用这个公式可以得到attention&#xff1f; 补充说明&#xff08;chatGPT给出的解释&#xff09;&…

音频修复和增强软件iZotope RX 10 mac特点介绍

iZotope RX 10 mac是一款音频修复和增强软件&#xff0c;主要特点包括&#xff1a; 声音修复&#xff1a;iZotope RX 10可以去除不良噪音、杂音、吱吱声等&#xff0c;使音频变得更加清晰干净。 音频增强&#xff1a;iZotope RX 10支持对音频进行音量调节、均衡器、压缩器、限…

深入解析进程

在现代计算机系统中&#xff0c;进程是一个核心概念&#xff0c;它代表了程序的执行实例。通过并发执行多个进程&#xff0c;计算机能够提高效率和资源利用率。 1. 进程的概念 进程是指在计算机系统中正在执行的程序的实例。每个进程都有自己的地址空间、寄存器集合、堆栈和文…

XC1136 功率传输(PD) Sink控制器IC PD诱骗器芯片 输出可调 可支持多个

XC1136是一款功率传输(PD) Sink控制器IC。XC1136可以从符合Type-CPD协议的电源中请求最大或指定电压。输入电压范围:3V~28V支持USBType-C规范版本1.3支持USB PD2.0和PD3.0通讯协议&#xff0c;最多支持七个电源对象 该XC1136内置拉低电阻CC1和CC2引脚。当XC1136连接到T…

什么牌子的台灯对孩子的眼睛好?安利五款适合孩子备考的护眼台灯

近年来&#xff0c;青少年的近视问题越来越严重&#xff0c;近视率持续升高&#xff0c;不少上小学一年级就已经戴上了厚厚的近视眼镜。导致这种现象发生的原因有两个&#xff0c;一个是孩子长时间使用电子产品导致。还有就是现在孩子的学习任务&#xff0c;不仅远比80、90后上…

python -- python安装

1、python的诞生和发展&#xff1a; python语言是一种解释型、面向对象型、动态数据类型的高级程序设计语言。 2、python的安装&#xff1a; 1、安装解析器&#xff1a; 在安装的过程中需要注意的是&#xff1a; 在安装pycharm的时候也是同样的道理&#xff0c;需要指定安装…

MySQL进阶知识:锁

目录 前言 全局锁 表级锁 表锁 元数据锁&#xff08;MDL&#xff09; 意向锁 行级锁 行锁 行锁演示 间隙锁/临界锁 演示 前言 MySQL中的锁&#xff0c;按照锁的粒度分&#xff0c;分为以下三类 全局锁&#xff1a;锁定数据库中的所有表。表级锁&#xff1a;每次操…

提升性能测试效率:JMeter中的用户自定义变量!

前言 在测试过程中&#xff0c;我们经常会碰到测试服务地址有改动的情况&#xff0c;为了方便&#xff0c;我们会把访问地址参数化&#xff0c;当访问地址变化了&#xff0c;我们只需要把参数对应的值改动一下就可以了。 一&#xff1a;添加配置元件-用户定义的变量&#xff…

视频平台跨网级联视频压缩解决方案

一、 简介 视频监控领域对带宽有着较大的需求&#xff0c;这是因为视频流需要实时占用网络带宽资源。视频监控的传输带宽是组网结构的基础保障&#xff0c;关系到视频监控的稳定性、可靠性和可拓展性等因素。例如&#xff0c;720P的视频格式每路摄像头的比特率为2Mbps&…