有人相爱,有人夜里开车看海,有人LeetCode第一题都做不出来

简介: 有人相爱,有人夜里开车看海,有人LeetCode第一题都做不出来

前言

在LeetCode的第一题下面,有这样一句评论“有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。”看到这条评论,你是得意的笑呢,还是苦涩的笑?

LeetCode第一题为“两数之和”,难度为“简单”,如果这样一个简单的题,没做不出来,的确有些心酸。这就像学一门编程语言时,打印“Hello World”的程序都没写不出来的感觉是一样的,凄凉。

下面就来一起看看这道题。

“两数之和”

题名称为“两数之和”,题目详情如下,对应官方链接:https://leetcode-cn.com/problems/two-sum/image.png

题面分析

题目用大白话来说就是:有一个整数数组,从中找出两个数,这两个数满足它们的和等于指定的数。

image.png然后就是一些限制性条件,比如:“只会有一个答案”,那么一旦找到一组数据,直接返回就OK了。

其中“数组中同一元素不能使用两遍”这个限制条件有一定的歧义,迷惑了很多人,我在第一次做题的时候就很困惑:循环两次算是“使用两遍”吗?

结合官方给出的双层for循环方法,仔细分析之后,才明白这里的“使用两遍”并不是读取或遍历两次,而是指计算和时不能使用两次。比如,数组内容为[2,3,4],指定目标值为6,那么返回的结果应该是[0,2],也就是2+4=6,而不是3 + 3 = 6。这才是题意中的使用两遍的本意。

下面看具体的解答方案。

方案一:暴力枚举

我们先从最简单,能直观看到的解题方案:双层循环,也就是暴力枚举。

基本思路就是:遍历数组中的每一行数据x,然后拿目标数值target减去x(即target - x),然后再次遍历数组寻找值为target - x的数据项。

image.png在计算过程中,很显然当循环执行到9-2=7之后,7已经存在于数组当中,不需要再继续计算匹配了。此循环过程,还可以进一步优化:第一层循环之后,x之前的元素已经匹配过了,因此不需要再次匹配,只需匹配x之后的元素即可。

实现代码如下:

// 暴力遍历
public int[] twoSum(int[] nums, int target) {
    int len = nums.length;
    for (int i = 0; i < len - 1; i++) {
        for (int j = i + 1; j < len; j++) {
            if (nums[i] + nums[j] == target) {
                return new int[]{i, j};
            }
        }
    }
    return new int[0];
}

注意上面第二层for循环中j取值为i+1,就是上面所说的x之前的元素不再匹配,直接匹配后面的元素。

计算上述代码的时间复杂度,直接看最内层(第二层循环)的时间频度,随着第一层循环的执行,第二层的时间频度依次为n-1,n-2,n-3,n-4…… 2,1,是一个等差数列,因此整体的时间复杂度计算公式为 n(n-1 + 1)/2 = n^2/2。得到时间复杂度为:O(n^2)。

由于在该算法中定义的内部变量只有len,它是固定值,不会随着nums数组的变化而变化,也就是常数,因此空间复杂度为1,记作 O(1)。

方案二:哈希表

说到哈希表,它是在算法中经常会用到的一种数据存储结构,可以根据key轻易的获得对应的value值。甚至可以说,每当遇到一个新算法时,都要优先考虑一下能否通过哈希表的形式来解决。

使用哈希表进行存储会引起空间复杂度的变化。哈希表存储的数据可能会随着问题规模n的增加而增加,因此,空间复杂度很可能会由原来的O(1)变为O(n),这种变换也就是通常所说的拿空间换时间。

暴力枚举算法中,我们看到导致算法复杂度较高的原因是查找target-x的时间复杂度较高,因此,可采用上面提到的哈希表来实现快速查找元素的目的。将原来查找的时间复杂度会从O(n)降低为近似O(1)。为什么说是近似O(1)呢?因为一旦出现哈希冲突,查找用时可能会退化到 O(n)。但只要仔细地挑选哈希函数,在哈希表中进行查找的用时应当被摊销为 O(1)。

在本题中,就可以拿空间换时间,先创建一个哈希表,对于每一个 x,首先查询哈希表中是否存在target - x,如果不存在则将 x 插入到哈希表中,如果存在则返回key对应的坐标和当前元素的坐标。image.png整体来看,该算法就是:一个外层for循环,一个内层哈希表的快速查找。示例代码如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
            if (map.containsKey(target - nums[i])) {
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i], i);
        }
        return new int[0];
    }
}

我们知道,哈希表查询的时间复杂度为O(1),外层循环时间复杂度为O(n),因此该算法的时间复杂度为 O(n) 。关于空间复杂度,随着问题规模n的变化,哈希表所占的空间也会随着变化,因此,空间复杂度 O(n)。

方法三 排序 + 双指针

除了上述两种算法之外,还有一种解决方案的思路:先将nums元素排序,再使用两个指针,从前后两个值开始逐步逼近target。具体实现代码如下:

public int[] twoSum2(int[] nums, int target) {
    // 转化成 List, 以方便排序
    List<Integer> numList = new ArrayList<>(nums.length);
    // 保留原始 坐标位置
    Map<String, Integer> location = new HashMap<>(nums.length);
    for (int i = 0; i < nums.length; i++) {
        numList.add(nums[i]);
        if (location.containsKey(nums[i] + "")) {
            location.put(nums[i] + "AGAIN", i);
        } else {
            location.put(nums[i] + "", i);
        }
    }
    Collections.sort(numList);
    int preIdx = 0;
    int sufIdx = numList.size() - 1;
    int[] result = new int[2];
    int tmpPre,tmpSuf;
    while (preIdx < sufIdx) {
        tmpPre = numList.get(preIdx);
        tmpSuf = numList.get(sufIdx);
        int tmpRes =  tmpPre + tmpSuf;
        // 两数之和 大于 target,后面的坐标前移
        if (tmpRes > target) {
            sufIdx -= 1;
        } else if (tmpRes < target) {
            // 两数之和 小于 target , 前面的坐标后移
            preIdx += 1;
        } else {
            if (location.containsKey(tmpPre + "AGAIN")) {
                result[0] = location.get(tmpPre + "AGAIN");
                location.remove(tmpPre + "AGAIN");
            } else {
                result[0] = location.get(tmpPre + "");
            }
            if (location.containsKey(tmpSuf + "AGAIN")) {
                result[1] = location.get(tmpSuf + "AGAIN");
                location.remove(tmpSuf + "AGAIN");
            } else {
                result[1] = location.get(tmpSuf + "");
            }
            if (tmpPre == tmpSuf) {
                // 前后值一致时,交换两者的顺序
                int tmpCpm = result[0];
                result[0] = result[1];
                result[1] = tmpCpm;
            }
            return result;
        }
    }
}

上述算法时间复杂度 O(nlogn) , 第一个for循环的时间复杂度为O(n), 排序的时间复杂度最优为 O(nlogn), while循环的时间复杂度为 O(n), 取最大范围的则为O(nlogn)。

空间复杂度 O(n) , 原始坐标location, 排序对象numList 都可以视为 O(n), 则相加为O(2n) ,忽略常系数则为 O(n)。

关于此种算法并没有第二种方法有优势,而且操作流程相对繁琐一些,用来参考,拓展思路即可。

小结

你可能很久之前已经做过“两数之和”这道题,但阅读完本篇文章之后,是否产生了新的思考?希望这文章能够让你从另外一个层面来学习“两数之和”这道题,而不是仅仅刷了一道算法题。

目录
相关文章
|
3月前
|
算法
人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。
这篇文章介绍了解决LeetCode第一题"两数之和"的方法,提供了题目的描述、输入输出示例,并给出了解决这个问题的算法思路。
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
56 6
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
50 4
|
3月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
113 2
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
16 1
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
3月前
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
56 7
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
27 4
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
52 5