【面试经典150 | 双指针】两数之和

这篇具有很好参考价值的文章主要介绍了【面试经典150 | 双指针】两数之和。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【双指针】【二分法】【哈希表】【数组】


题目来源

面试经典150 | 167. 两数之和 II - 输入有序数组

【面试经典150 | 双指针】两数之和,面试经典150题,双指针,二分法,哈希表,数组,c++,算法

题目解读

给定一个下标从 1 开始按照 非递减顺序排列 的整数数组 numbers,找出两数之和等于 target 的两个数,返回它们的下标,其中每个整数只能使用一次,题目保证只有唯一的答案。


解题思路

本题属于基础题,与 1. 两数之和 解法基本一致。现在有三种解法如下。

方法一:暴力枚举

一个比较容易想到的方法就是枚举所有可能的两数组合,使用两层枚举,第一层枚举第一个整数,第二层枚举第二个整数。本题的数据量为 1 0 4 10^4 104,两层枚举的时间复杂度为 1 0 8 10^8 108,勉强可以通过。

具体地,在枚举中判断两数之和是否等于 target,如果相等,直接返回对应的下标。

因为每个元素只可以使用一次,并且两数先后出现的顺序没有要求,因此
第二层枚举的整数可以从第一层枚举的整数的后一个位置开始。

实现代码

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();
        for (int i = 0; i < n; ++i) {
            for (int j = i+1; j < n; ++j) {
                if (numbers[i] + numbers[j] == target) {
                    return {i + 1, j + 1};
                }
            }
        }
        return {-1, -1};    // 本题保证一定有解,程序不会运行到此处
    }
};

但是实测中,最后几个测试用例超时了!

复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)

方法二:哈希表

方法一中的时间复杂度可以优化到 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n),先来介绍时间复杂度为 O ( n ) O(n) O(n) 的方法,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 的方法将在方法三中介绍。

我们在枚举第二个整数的时候,可以事先用一个哈希表来记录下所有整数以及位置,这样枚举第二个整数的时间复杂度可以降为 O ( 1 ) O(1) O(1),但是需要一个额外的空间。

具体地,可以先一次遍历 numbers,记录每个整数以及下标;记录完毕后,枚举第一个加数,在哈希表中查找第二个加数;以上的过程可以用一个循环就可以解决:枚举第一个加数之后,先在哈希表中查询有么有合适的第二个加数,然后再将当前的加数放入哈希表中,这样可以省去一次 for 循环。

实现代码

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        unordered_map<int, int> idx;
        for (int i = 0; i < numbers.size(); ++i) {
            if (idx.find(target - numbers[i]) != idx.end()) {
                int idx1 = min(i, idx[target - numbers[i]]);
                int idx2 = max(i, idx[target - numbers[i]]);
                return {idx1 + 1, idx2 + 1};
            }
            idx[numbers[i]] = i;
        }
        return {-1, -1};
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 为数组 numbers 的长度,只要一次循环就可以枚举两个加数。

空间复杂度: O ( n ) O(n) O(n),记录整数以及位置所用的空间。

方法三:二分法

在方法二中,我们是利用哈希表来降低枚举的线性时间的,我们还可以使用二分方法来降低线性枚举的时间复杂度。

前面两种方法中,都没有用到题目中 非递减顺序排列 这一条件,我们可以利用这种有序性进行二分查找第二个加数。

具体地,枚举第一个加数,假设下标为 i,接着要在 numbers[i+1,...,n-1] 中使用二分法查找 target - numbers[i],如果查找到直接返回两个加数的对应下标,否则继续枚举第一个数查找。

实现代码

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();
        for (int i = 0; i < numbers.size(); ++i) {
            int num1 = numbers[i];
            auto it = find(numbers.begin() + i + 1, numbers.end(), target - num1);
            if (it != numbers.end()) {
                int j = it - numbers.begin();
                return {i + 1, j + 1};
            }
        }
        return {-1, -1};
    }
};

复杂度分析

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),枚举第一个数的时间复杂度为 O ( n ) O(n) O(n),在每次枚举中最坏需要二分查找 O(logn) 次,才能找到合适的第二个加数。

空间复杂度: O ( 1 ) O(1) O(1)

方法四:双指针

以上三种都不是最优的,现在介绍时间复杂度和空间复杂度都是最优的方法——双指针。

初始左右两个指针 l e f t left left r i g h t right right 分别指向 numbers 的第一个位置和最后一个位置。每次计算两个指针指向的整数之和,与 target 进行比较:

  • 如果 numbers[left] + numbers[right] = target,直接返回 {left + 1, right + 1}(因为下标从 1 开始);
  • 如果 numbers[left] + numbers[right] > target,则将 right 指针左移一位;
  • 如果 numbers[left] + numbers[right] < target,则将 left 指针右移移位。

为什么两数之和小了,右移 left 就可以了,右移 right 不可以吗?为什么两数之和大了,左移 right 就可以了,左移 left 不可以吗?

假设 numbers[i] + numbers[j] = target 是唯一解,其中 0 <= i < j <= n-1。初始时 left = 0right = n-1,除非初始的时候,左右两个指针已经位于 ij 处,否则一定是左指针先到达下标 i,或者右指针先到达下标 j

  • 左指针先到达下标 i 时,右指针还在 j 的右侧,此时 numbers[left] + numbers[right] > target,于是需要将 right 指针左移一位,这样才能缩小两数之和;
  • 右指针先到达下标 j时,左指针还在 i 的左侧,此时 numbers[left] + numbers[right] < target,于是需要将 left 指针右移一位,这样才能增加两数之和。

于是,就有了以上所示的双指针更新规则。

实现代码

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();
        int l = 0, r = n - 1;
        while (l <= r) {
            int sum = numbers[l] + numbers[r];
            if (sum > target) {
                --r;
            }
            else if (sum < target) {
                ++l;
            }
            else {
                return {l+1, r+1};
            }
        }

        return {-1, -1};
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n),双指针相向移动,它们 一共最多走 n 次。

空间复杂度: O ( 1 ) O(1) O(1),使用的额外变量只有两个指针。


知识回顾

今天来看看 C++ \texttt{C++} C++ 中二分查找的几个 API。

find() 使用二分法来查找数组中指定值的位置,其返回的是迭代器:

  • 如果顺利查找到指定元素,则返回该元素位置迭代器;
  • 如果没有查找到指定元素,则返回尾后迭代器;

通过位置迭代器与首位置迭代器作差可以得到该元素在数组中的位置。

lower_bound()upper_bound() 的含义与用法可以参考 【二分查找】几种基本题型,你会了吗?。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。文章来源地址https://www.toymoban.com/news/detail-706569.html

到了这里,关于【面试经典150 | 双指针】两数之和的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 二分法相关使用

    在线OJ:704. 二分查找 有序数组下的二分思路如下: 由于这里是有序数组, 我们可以可以先得到中点位置, 中点可以把数组分为左右两边; 如果中点位置的值等于目标值, 直接返回中点位置; 如果中点位置的值小于目标值, 则去数组中点左侧按同样的方式查找; 如果中点位置的值大

    2024年02月07日
    浏览(35)
  • 【二分查找】一文带你掌握二分法 (附万能模板)

    一、简介 哪怕没有学过编程的同学,也许不知道二分法这个名字,但也一定接触过它的核心思想。不了解的同学也没关系,我用一句话就能概括出它的精髓: 将一个区间一分为二,每次都舍弃其中的一部分。 二分法能够极大地降低我们在解决问题时的时间复杂度。假如你要

    2024年01月19日
    浏览(42)
  • 【剑指Offer】二分法例题

    链表是数据结构中重要的一个章节,他的重要性也不言而喻,在未来不管是笔试还是面试都会遇到这类的题目,所以接下来我就会把一些链表的常考的题目全部整理出来供大家学习指正。 题目链接 描述: 给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包

    2023年04月13日
    浏览(37)
  • 牛顿法、割线法、二分法

    牛顿法求解非线性方程组 割线法求解非线性方程组 二分法求解根号3  另外,今天上机课写程序时,发现不同的起始点可以收敛到不同的零点。也许这是一个新的值得研究的地方。 看来,计算数学也是这样,光听理论无法实现大的突破,也没法产生好的想法,必须在实践应用

    2024年02月05日
    浏览(41)
  • 非线性方程二分法

    优点:算法直观、简单、总能保证收敛;局限:收敛速度慢、一般不单独用它求根,仅为了获取根的粗略近似 设 f ( x ) f(x) f ( x ) 在 [ a , b ] [a,b] [ a , b ] 上连续、严格单调、满足条件 f ( a ) f ( b ) 0 f(a)f(b)0 f ( a ) f ( b ) 0 则在区间 [ a , b ] [a,b] [ a , b ] 内必有一根 x ∗ x^* x ∗ 。通

    2024年02月04日
    浏览(34)
  • 算法:二分法---寻找H指数

    1、题目: 给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数 。 根据维基百科上 h 指数的定义: h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且每篇论文 至少 被引用

    2024年02月08日
    浏览(35)
  • 06-C++ 基本算法 - 二分法

    在这个笔记中,我们将介绍二分法这种基本的算法思想,以及它在 C++ 中的应用。我们将从一个小游戏猜数字开始,通过这个案例来引出二分法的概念。然后我们将详细讲解什么是二分法以及它的套路和应用。最后,我们还会介绍 C++ STL 中的二分查找函数。让我们一起来探索

    2024年02月16日
    浏览(32)
  • 33. 搜索旋转排序数组(二分法)

    题目要求必须设计一个时间复杂度为  O(log n)  的算法解决此问题,所以我们可以采用二分法。 Step1. 先把 nums[0] 作为目标值,通过二分法找到旋转点索引; Step2. 如果旋转点索引为0,则数组本身就是升序的,否则思想上可以将数组一分为二,看做两个升序数组。 Step3. 判断

    2024年02月05日
    浏览(31)
  • 1241:二分法求函数的零点

    【题目描述】 有函数:f(x)=x5−15x4+85x3−225x2+274x−121 已知f(1.5)0,f(2.4)0 且方程f(x)=0在区间[1.5,2.41.5,2.4] 有且只有一个根,请用二分法求出该根。 【输入】 (无) 【输出】 该方程在区间[1.5,2.41.5,2.4]中的根。要求四舍五入到小数点后66位。 【输入样例】 【输出样例】 【AC代码】

    2024年01月25日
    浏览(28)
  • 【力扣】74. 搜索二维矩阵 <二分法>

    给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非递减顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。 示例 1: 1 3 5 7 10 11 16 20 23 30 34 60 输入:matrix = [[1,3,5,7],[10,

    2024年02月15日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包