Leetcode第一题:两数之和(3种语言)

Leetcode第一题:两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的 两个 整数。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

标注:仅在Python解法做详细分析,c++与java如无特别需要注意的地方不做分析。

一、Python解法

解法2,3参考了linfeng886的博客

解法1:暴力搜索

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        #对nums每个元素循环
        for i in range(len(nums)):
        #从nums[i]的下一个开始找
            for j in range(i+1,len(nums)):
            #如果找到了就返回值
                if nums[i]+nums[j]==target:
                    return i,j

分析:代码十分简单,就是首先用i在数组里循环一轮,在每个i循环下,去从剩下的元素找target-nums[i]的值。如果找到了,就return i,j两个数。(程序假设一定可以找的到)
来看看运行结果:
在这里插入图片描述
在这里插入图片描述
可以看到效率是十分低的,主要原因就是用了两个for循环,时间复杂度是O(n2)。

解法2:一次for循环

一开始犯了一个小错误的代码
class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        #直接在i一个一个循环的时候,直接判断target-nums[i]在列表里吗,在的话用list.index()获取索引,用了一次for循环。很棒。
        for i in range(len(nums)):
            if target-nums[i] in nums:
                return i,nums.index(target-nums[i])

此代码的运行问题在于:
在这里插入图片描述
我们可以看到,忘记了一个元素只能用一次的规矩,因此做一个判断即可。
修改版:

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        for i in range(len(nums)):
            if target-nums[i] in nums:
            #增加了返回的两个数下表不能相同的判断
                if i!=nums.index(target-nums[i]):
                    return i,nums.index(target-nums[i])

在这里插入图片描述
可以看到:速度上升了好几倍。效果还不错。但是通过查看官方解答参考了linfeng886的博客知道还有一种更快的方法----基于hash table的解决方法。

解法3:基于Python字典查找

关于hash table(哈希表),简单来说就是存有键值对key,value的一种数据结构,对于熟悉Python的人来说,常见的字典就是一种hash table。它的查找速度是很快的,可以理解为O(1)。所以这里相当于在解法2的基础上做了一个改进。解法2 是在空间不变的前提下,在i循环时,直接在列表里查找是否含有target-nums[0]的元素,而列表的查找速度是远不如hash table。所以解法三的关键就在于,查找的任务在字典中进行而不在list中进行(有待商榷)
代码:

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        #遍历一个i,就把nums[i]放在字典里。之后i不断相加,总会碰到
        #target - nums[i]在字典里的情况。碰到的时候return即可。这里注意到先return的是dict[a],再是当前的i,原因就是dict里存的value都是以前遍历过的i,所以它比当前的i小,按从小到大的顺序所以这样排。
        dict = {}
        for i in range(len(nums)):
            a = target - nums[i]
            if a in dict:
                return dict[a],i
            else:
                dict[nums[i]] = i

运行结果:
在这里插入图片描述
可以从代码中看到,解法3就是把需要查找的target-nums[i]从解法2中list变成了dict而已,但结果提高的十分可观。但要注意的是,这里用空间与实践做了一个tradeoff,也就说解法3虽然快了很多,但是需要的空间增加了一个新的dict。
这也给我们了一个提示:以后如遇到类似的需要遍历查找的元素时,不妨参照本例,利用hash table查找。

二、Java解法

解法2,3参照了官方解法twosum
以及菜鸟教程java数组篇
Pythonliast与java数组区别 https://blog.csdn.net/wu1226419614/article/details/80870120
关于hashmap数据类型
https://www.cnblogs.com/hello-yz/p/3712610.html

解法1:暴力搜索

代码:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] a = new int[2];
        for(int i=0;i<nums.length;i++){
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]+nums[j]==target){
                    a[0] = i;
                    a[1] = j;
                 //return new int[] {i,j};   
                }
            }
        }
        return a;
    }
}

在这里插入图片描述
这里不作特别说明。只想提及的是关于return new int[] {i,j}的些许解释。这种写法是官方解读给出的。参考菜鸟教程关于数组的解释,可以知道这是函数输入参数或者充当返回值的一种方式。
同时,官方解法在类的最后会throw一个异常,若将其删除会报错,因为throw也是return的一种替代形式。(就是说即使这个类在开头就说了不是void的,要返回一个int[]或者其他的东西,但是在最后抛出一个异常语法上是符合的。)对于本例,执行着就会从if下的return离开程序,所以不会抛出异常的。

解法2:两次for循环(两遍hashmap)

在这里和Python的3种解法做一个比较。可以看到两种语言的解法1是完全相同的。但是解法2上,会有一些区别。之后解法3又是完全相同的。为什么解法2会和Python解法2有区别呢?
先回顾下Python解法2:通过i循环列表,直接判断target - nums[i]是否在列表里,在的话,就直接返回i,与list.index(target-nums[I])。这里我们用了Python内置函数index。可以方便的获取到索引,而对于java的数组,并没有那么方便获取数组元素索引的函数。这里有一个很好的比较,从中可以知道java对于数组有一个binarySearch的查找方法,而它本身就是用二分法查找实现的,所以只适用于有序数组。同时若再用一次for循环获取索引,得不偿失。那不如多用一次for循环把索引与数值一一对应起来,用类似Python字典的方式,这样查找的更快-----hashmap
代码:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] b = new int[2];
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>(); 
        for(int i=0;i<nums.length;i++){
            map.put(nums[i],i);
        }
        for(int j=0;j<nums.length;j++){
            int a = target - nums[j];
            if(map.containsKey(a) && j!=map.get(a)){
                b[0] = j;
                b[1] = map.get(a);
                    return b;
                 //return new int[] {j,map.get(a)};
            }
        }
    return b;
}
}

在这里插入图片描述
这里需要注意几点:
1.HashMap<Integer,Integer> = new HshMap<Integer,Integer>()这里的Integer不能用基本数据类型int。可以参看这里
2.hashmap声明定义格式,和一般类的实例化一样。
class1 xx = new class1()
3.hashmap与hashtable。hashmap基本上可以等同于hashtable。而且可以看作为其升级版。HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。详情可查看 http://www.importnew.com/7010.html
4.hashmap的put,get分别为存与取。以及containsKey在代码里都有体现,很容易理解。
5.注意if判断里的&&(短路运算符)不能被&代替,可以通过LeetCode的简单试例,但是提交时会报错。分析:&即使前面为false,运算符后面的逻辑式还是会被判断。而后面的为j != map.get(a)。很可能a是不存在的,故会报错。将map.get(a)打印出来是一个null指针。进一步解释,运行用&的代码会报错:
Exception in thread “main” java.lang.NullPointerException
这也就是空指针异常。解释是"程序遇上了空指针"。简单地说就是调用了未经初始化的对象或者是不存在的对象,这个错误经常出现在创建图片,调用数组这些操作中,比如图片未经初始化,或者图片创建时的路径错误等等。对数组操作中出现空指针。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)。参见 https://zhidao.baidu.com/question/494551043.html
但是,这一切在python中是允许的。

解法3:一次for循环(一次hashmap)

此解法思想与Python解法3如出一辙,是一模一样的,唯一区别在于Python使用字典做查找,Java使用HashMap做查找。因此本解法不做过多说明。
代码:

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

在这里插入图片描述
可以看到,与解法2相比速度提升有限。
这里需要注意的是:1.代码最后一行无论return的是什么(必须是数组)无所谓的,因为不会走到这一步的,但是最优解还是像官方解答一样抛出一个异常比较好。因为如果真走到这一步了,说明程序肯定出现了异常。
2.我这里写的和官方解法略有不同,其实是一样的。我把put进去的值换成了target-nums[i]。然后判断nums[i]是否在map中,具体就不说了,略显繁琐。

三、C++解法

有了上述两种语言的解答做铺垫,我觉得C++解法思路就会清晰很多了。

解法1:暴力搜索

直接看代码:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //vector类型为长度可变的数组,更灵活。需要#include<vector>
        vector<int> a;
        //int a[2];这里指定返回的是verctor类型,故这里不能用普通数组array
        for(int i=0;i<nums.size();i++){
            for(int j =i+1;j<nums.size();j++){
                if(nums[i]+nums[j]==target){
                    //在a的最后添加元素
                    a.push_back(i);
                    a.push_back(j);
                    //a[0] = i;
                    //a[1] =j;
                    return a;
                }
            }
        }
    //注意这里和java不一样,不需要一定要返回一个vector了。虽然该类要求有一个返回值
    }
};


这里做一些笔记:
1.求数组长度。
Python:len(list);
java:nums.length;
c++:nums.size()
2.java与c++的基本数组类型长度都是不可变的,要求灵活的使用用vector代替。关于数组与vector在c++与Java中的使用
c++相关参见菜鸟教程
Java的vector参见zheting的博客
重要的一点贴出来了:
java使用需要import java.util.Vector;
插入功能:
public final synchronized void adddElement(Object obj)
将obj插入向量的尾部。obj可以是任何类型的对象。对同一个向量对象,亦可以在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数组转换成相应的对象。
例如:要插入整数1时,不要直接调用v1.addElement(1),正确的方法为:

Vector v1 = new Vector(); 
Integer integer1 = new Integer(1); 
v1.addElement(integer1); 

3.关于数组作为函数的形参。
本例中是这样写的:

vector<int> twoSum(vector<int>& nums, int target) {
//insert your code
}

或者这样写:

vector<int> twoSum(vector<int> &nums, int target) {
//insert your code
}

或者:

vector<int> twoSum(vector<int> nums, int target) {
//insert your code
}

具体可查看菜鸟教程关于数组做形参的讲解

解法2:两次map(非哈希表)

这里注意的是用的是c++的map实现的key,value配对。而c++中还有hash_map,即hash table。二者的区别是:
hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其内存数据结构是不一样的
二者区别具体可查看
zhenyusoso的博客Miles-的博客
先来看map实现的代码:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> a;
        map<int,int> map;
        //hash_map<int,int> hp;
        for(int i=0;i<nums.size();i++){
           map[nums[i]] = i;
            
        }
        for(int j=0;j<nums.size();j++){
            if(map.count(target-nums[j])==1 && map[target-nums[j]]!=j){
                a.push_back(j);
                a.push_back(map[target-nums[j]]);
                return a;
            }
        }
    return a;
    }
};

在这里插入图片描述
做几点分析:
1.此方法和java的解法2版本是十分接近的。主要不同之处在于java的hashmap调用的方法有:put(key,value),get(key),containsKey()
而c++的map查找键值是否在为count。关于map的count与find是很常用的方法,二者用途一样。具体见Andy Niu的博客
2.这里用的是map,为什么不用hash_map类来实现呢?我在xcode中实现了一遍,macos系统关于hash_map的声明比较特殊:

#if defined __GNUC__ || defined __APPLE__
#include <ext/hash_map>
#else
#include <hash_map>
#endif
int main()
{
        using namespace __gnu_cxx;

        hash_map<int, int> map;
}

同时用find方法判别hash_map是否含有想要的key。

hash_map<int,int> hp;
if(hp.find(target-nums[j])!=hp.end() && hp[target-nums[j]]!=j){
//代码区
}

解法3:1次map(非哈希表)

这个就没有什么要分析的了,和上面两种语言的解法三一模一样。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> a;
        map<int,int> map;
        for(int i=0;i<nums.size();i++){
            if(map.find(target-nums[i])!=map.end()){
                a.push_back(map[target-nums[i]]);
                a.push_back(i);
                return a;
            }
            else{
                map[nums[i]] = i;
            }
        }
         return a;
        }
    

};

在这里插入图片描述
这里代码用了map的find而不是count来判断map是否含有指定key。

ps:后面的刷题笔记应该会3种语言分开记录,这样一篇文章太长,容易产生厌倦感。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值