leetcode 腾讯50题 0/50 两数之和

每天一道leetcode题目,50天完成腾讯经典50题
题目1 两数之和

题目描述

给定一个整型数组,要求返回两个数的下标,使得两数之和等于给定的目标值,要求同一个下标不能使用两次。
数据保证有且仅有一组解。

样例

给定数组 nums = [2, 7, 11, 15],以及目标值 target = 9,

由于 nums[0] + nums[1] = 2 + 7 = 9,
所以 return [0, 1].

解法1 C++ 暴力枚举

由于是第一题,对复杂度要求不高,通过暴力枚举法,可以通过两层循环遍历所有的情况便可以找到解,如果找不到结果解返回空数组,由于只有一个解,找到一组解便可以直接返回结果

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        int n =(nums.size());
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(nums[i]+nums[j]==target){
                    res=vector<int>({i,j});
                    return res;
                }
            }
        }
        return res;
    }
};

学到的知识点:

之前一直写的c,对于c++使用较少,通过这道题目,学习了c++ vector向量的一些相关的知识,vector
vector 是向量类型,它可以容纳许多类型的数据,如若干个整数,所以称其为容器。可以类比与c语言的数组,是数组的更高高级的版本,可以支持更多的操作,具有更好的使用体验。
vector 是C++ STL的一个重要成员,使用它时需要包含头文件:

#include;

一、vector 的初始化:

  1. vector a(10); //定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
  2. vector a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1
  3. vector a(b); //用b向量来创建a向量,整体复制性赋值
  4. vector a(b.begin(),b.begin+3); //定义了a值为b中第0个到第2个(共3个)元素
  5. int b[7]={1,2,3,4,5,9,8};
    vector a(b,b+7); //从数组中获得初值

二、vector对象的几个重要操作

  • a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
  • a.assign(4,2); //是a只含4个元素,且每个元素为2
  • a.back(); //返回a的最后一个元素
  • a.front(); //返回a的第一个元素
  • a[i]; //返回a的第i个元素,当且仅当a[i]存在
  • a.clear(); //清空a中的元素
  • .empty(); //判断a是否为空,空则返回ture,不空则返回false
  • a.pop_back(); //删除a向量的最后一个元素
  • a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+ 3(不包括它)
  • a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
  • a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
  • a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
  • a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8 ,插入元素后为1,4,5,9,2,3,4,5,9,8
  • a.size(); //返回a中元素的个数;
  • a.capacity(); //返回a在内存中总共可以容纳的元素个数
  • a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机
  • a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2
  • a.reserve(100); //将a的容量(capacity)扩充至100,也就是说现在测试a.capacity();的时候返回值是100.这种操作只有在需要给a添加大量数据的时候才 显得有意义,因为这将避免内存多次容量扩充操作(当a的容量不足时电脑会自动扩容,当然这必然降低性能)
  • a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
  • a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<

三、顺序访问vector的几种方式

  • 向向量a中添加元素
 vector<int> a;
 for(int i=0;i<10;i++)
 a.push_back(i);
  • 也可以从数组中选择元素向向量中添加
int a[6]={1,2,3,4,5,6};
vector<int> b;
for(int i=1;i<=4;i++)
b.push_back(a[i]);
  • 也可以从现有向量中选择元素向向量中添加
int a[6]={1,2,3,4,5,6};
vector<int> b;
vector<int> c(a,a+4);
for(vector<int>::iterator it=c.begin();it<c.end();it++)
b.push_back(*it);
  • 也可以从文件中读取元素向向量中添加
ifstream in("data.txt");
vector<int> a;
for(int i; in>>i)
    a.push_back(i);
  • 误区
vector<int> a;
for(int i=0;i<10;i++)
    a[i]=i;

这种做法以及类似的做法都是错误的。

从向量中读取元素

1、通过下标方式读取
int a[6]={1,2,3,4,5,6};
vector<int> b(a,a+4);
for(int i=0;i<=b.size()-1;i++)
    cout<<b[i]<<" ";
2、通过遍历器方式读取
int a[6]={1,2,3,4,5,6};
vector<int> b(a,a+4);
for(vector<int>::iterator it=b.begin();it!=b.end();it++)
    cout<<*it<<" ";

四、几种重要的算法

  • 使用时需要包含头文件:#include
  • sort(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素进行从小到大排列
  • reverse(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素倒置,但不排列,如a中元素为1,3,2,4,倒置后为4,2,3,1
  • copy(a.begin(),a.end(),b.begin()+1); //把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开 始复制,覆盖掉原有元素
  • find(a.begin(),a.end(),10); //在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置
C++11新特性

C++11新特性——大括号初始化
C++11之前,C++主要有以下几种初始化方式:

//小括号初始化

string str("hello");
//等号初始化
string str="hello";
//大括号初始化
struct Studnet{
    char* name;
    int age;
};
Studnet s={"dablelv",18}; //纯数据(Plain of Data,POD)类型对象
Studnet sArr[]={{"dablelv",18},{"tommy",19}};  //POD数组

虽然C++03提供了多样的对象初始化方式, 但不能提供自定义类型对象的大括号初始化方式,也不能在使用new[]的时候初始化POD 数组。幸好,C++11扩充了大括号初始化功能,弥补了C++03的不足。

class Test{    
    int a;    
    int b;    
public:    
    C(int i, int j);    
};    
Test t{0,0};                    //C++11 only,相当于 Test t(0,0);    
Test* pT=new Test{1,2};         //C++11 only,相当于 Test* pT=new Test{1,2};    
int* a = new int[3]{ 1, 2, 0 }; //C++11 only

此外,C++11大括号初始化还可以应用于容器,终于可以摆脱 push_back() 调用了,C++11中可以直观地初始化容器了:

// C++11 container initializer    
vector<string> vs={ "first", "second", "third"};    
map<string,string> singers ={ {"Lady Gaga", "+1 (212) 555-7890"},{"Beyonce Knowles", "+1 (212) 555-0987"}}; 

因此,可以将C++11提供的大括号初始化作为统一的初始化方式,既降低了记忆难度,也提高的代码的统一度。

此外,C++11中,类的数据成员在申明时可以直接赋予一个默认值:

class C    
{
private:  
    int a=7; //C++11 only
};    

在c++11中,vector的添加元素除了push_back之外,可以直接使用大括号表达式来赋值
在本题中,将i,j添加到vector中
可以使用push_back

res.push_back(i);
res.push_back(j);

也可以使用大括号表达式

res={i,j};
res = vector<int>({i, j});

方法2 建立哈希表

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;//存储和为target的两个数字的数组下标
        unordered_map<int,int> hash;//定义一个哈希表,用来映射数组,可以在o(1)复杂度找到是否存在有与nums[i]对应的nims[j]使得二者和target
        for(int i=0;i<nums.size();i++){
            int numsj=target-nums[i];//numj 要寻找的另外一个数
            if(hash.count(numsj)){
                res={i,hash[numsj]};
                return res;
            }
            hash[nums[i]]=i;//nums[i]找不到配对的,加入到哈希表中
        }
        return res;
        
    }
};

在方法1中,时间复杂度是O(n^2),空间复杂度为O(1),需要两层循环才能找到结果,如何使用一趟扫描就能找到所求的解呢,用空间来换时间,我们知道哈希查找的效率可以达到O(1)复杂度,我们一趟扫描,如果在哈希表中找到了对应的target-nums[i[那么就查找成功,返回这两个数的下标,否则查找失败,则将这一数加入到哈希表当中.
C+++的stl中,有map,hash_map,unordered_map等多种容器

  • map
      内部数据的组织,基于红黑树实现,红黑树具有自动排序的功能,因此map内部所有的数据,在任何时候,都是有序的。

  • hash_map
      基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存。底层实现上,使用一个下标范围比较大的数组来存储元素,形成很多的桶,利用hash函数对key进行映射到不同区域进行保存。

    插入操作:得到key -> 通过hash函数得到hash值 -> 得到桶号(hash值对桶数求模) -> 存放key和value在桶内
    取值过程:得到key -> 通过hash函数得到hash值 -> 得到桶号(hash值对桶数求模) -> 比较桶内元素与key是否相等 -> 取出相等纪录的value
    当每个桶内只有一个元素时,查找时只进行一次比较,当很多桶都没有值时,查询更快。
    用户可以指定自己的hash函数与比较函数。

  • unordered_map
      C++ 11标准中加入了unordered系列的容器。unordered_map记录元素的hash值,根据hash值判断元素是否相同。map相当于java中的TreeMap,unordered_map相当于HashMap。无论从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。

  • 对于STL里的map容器,count方法与find方法,都可以用来判断一个key是否出现,count统计的是key出现的次数,因此只能为0/1,而find基于迭代器实现,以mp.end()判断是否找到要求的key。

  • 为什么要使用unordered_map代替hash_map?

    • 因为标准化的推进,unordered_map原来属于boost分支和std::tr1中,而
      hash_map属于非标准容器。
    • 另外,使用之后,感觉速度和hash_map差不多,但是支持string做key,也可以使用复杂的对象作为key。
    • gxx需要添加编译选项:–std=gnu++0x或者–std=c++0x

因此,在本次做题中,用unordered_map 实现哈希表
在·本题中使用int到int的映射,因此初始化为

unordered_map<int,int> hash;

map容器,count方法与find方法,都可以用来判断一个key是否出现,count统计的是key出现的次数,因此只能为0/1,而find基于迭代器实现,以map.end()判断是否找到要求的key。
本题使用hash.count(numsj)来判断numsj是否存在与哈希表当中,也可以使用find()来做

hash.find(numsj)!=hash.end()

将当前这个元素加入哈希表的操作

 hash[nums[i]]=i;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值