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

Leetcode第一题:两数之和(3种语言)@](这里写自定义目录标题)Leetcode第一题:两数之和给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的两个整数。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。示例:给定nums=[2,7,11,15],target=9因为nums[0]+nums1=2+7=9所以返回…

大家好,又见面了,我是你们的朋友全栈君。

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了。虽然该类要求有一个返回值
    }
};

Leetcode第一题:两数之和(3种语言)
这里做一些笔记:
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种语言分开记录,这样一篇文章太长,容易产生厌倦感。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/131493.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • qq视频资源是什么_qq代码视频教程

    qq视频资源是什么_qq代码视频教程QQ视频资源裂变源码有哪些功能对于2021年网赚引流最快变现的一些思路qq资源!已解锁全部!点击进入观赏!这个是分享以后得卡片标题内容这个系统有2个版本,第一调用单个视频资源链接(支持mp4,m3u8格式),第二个版本支持观看10-60秒后强制分享弹窗下上图片位置带自定义图片广告跳转,跳转链接可批量留多个裂变网页链接达到裂变式框架“`…

  • L2-012关于堆的判断(堆)[通俗易懂]

    L2-012关于堆的判断(堆)[通俗易懂]堆题目链接将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种:x is the root:x是根结点;x and y are siblings:x和y是兄弟结点;x is the parent of y:x是y的父结点;x is a child of y:x是y的一个子结点。输入格式:每组测试第1行包含2个正整数N(≤ 1000)和M(≤ 20),分别是插入元素的个数、以及需要判断的命题数。下一行给出区间[−10000,10000]内的N个要被

  • 在总线周期的t1,t2,t3,t4状态,cpu_计算机组成原理总线带宽怎么算

    在总线周期的t1,t2,t3,t4状态,cpu_计算机组成原理总线带宽怎么算大家好,我是小黄鸭,又来更新了,应小伙伴的需要,该实验也过了。实验所用的软件资源/测试电路也全部开放,地址在MOOC中国大学为:https://www.icourse163.org/learn/HUST-1205809816#/learn/announce附带实验测试,地址在Educode上为:https://www.educoder.net/shixuns/ckff6yv9/challenges光是给的Excel自生成电路表格就上了7个,再加上密密麻麻的电路图,各自安好吧整体框架该实验

    2022年10月13日
  • windows各版本序列号集合

    windows各版本序列号集合因经常使用,避免每次都上网到处找,在此做了集合(不定期更新)windows2003R2Sp264位企业版MR78C-GF2CY-KC864-DTG74-VMT73windows2003R232位企业版JCDPY-8M2V9-BR862-KH9XB-HJ3HMwindows764位旗舰版使用激活工具,附件1,出处:http://www.xpg…

  • 什么叫pure function(纯函数)[通俗易懂]

    什么叫pure function(纯函数)[通俗易懂]在Knockout中,用到了pureComputer(),其原理来自于纯函数(purefunction)。那么,什么叫纯函数呢?纯函数(来自:http://en.wikipedia.org/wiki/Pure_function)     在计算机编程中,假如满足下面这两个句子的约束,一个函数可能被描述为一个纯函数:给出同样的参数值,该函数总是求出同样的结果。该函数结

  • LeetCode1两数之和

    LeetCode1两数之和题目:给定一个整数数列,找出其中和为特定值的那两个数。你可以假设每个输入都只会有一种答案,同样的元素不能被重用。示例:给定nums=[2,7,11,15],target=9因为nums[0]+nums[1]=2+7=9所以返回[0,1]分析:可以直接遍历两遍数组,第一遍用target-nums[i],第二遍找nums数组中是否存在target-num…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号