LintCode有大部分题目来自LeetCode,但LeetCode比较卡,下面以LintCode为平台,简单介绍我AC的几个题目,并由此引出一些算法基础。

1)两数之和(two-sum)

题目编号:56,链接:http://www.lintcode.com/zh-cn/problem/two-sum/

题目描述:

给一个整数数组,找到两个数使得他们的和等于一个给定的数 target

你需要实现的函数twoSum需要返回这两个数的下标, 并且第一个下标小于第二个下标。注意这里下标的范围是 1 到 n,不是以 0 开头。

注意:你可以假设只有一组解。

样例:给出 numbers = [2, 7, 11, 15], target = 9, 返回 [1, 2],即数字2,7

代码接口:

class Solution {
public:
/*
* @param numbers : An array of Integer
* @param target : target = numbers[index1] + numbers[index2]
* @return : [index1+1, index2+1] (index1 < index2)
*/
vector<int> twoSum(vector<int> &nums, int target) {
// write your code here
}
};

常见的思路是:两层for循环,任意两个数组合求其和,判断是否等于给定的target。但这样太慢,需要O(n^2)的时间,O(1)的额外空间。可以反过来思考,假如当前选择了一个数字a,那么为了满足条件,另一个数字b必须满足:b=targe-a,即在数组中寻找是否存在b。

如何快速寻找数组中是否存在一个数字b?假如数组是有序的,可以使用二分查找方法,其查找时间复杂度是O(logn)。然而题目并没给定这个条件。如果对数组排序,首先就要O(nlogn)的时间进行排序,并且排序后,数字的原始下标也要保存,显然需要O(nlogn)的时间以及O(n)的空间,并不是最好的方法。

如何对一个数组进行快速查找一个元素?算法中提供了一种方法——哈希(Hash),即对数组中的每个元素按照某种方法(hash function)计算其“唯一”值id(称为哈希值),存储在新的数组A中(一般称为哈希数组),并且其下标就是这个“唯一”值。那么如果访问A[id]存在,则这个元素存在,否则,原始数组中不存在该元素。由于数组是顺序存储的支持随机访问,所以查找一个元素是否在数组中,只需要O(1)的时间,但是在初始化哈希数组时,需要O(n)的时间和O(n)的空间。对于某些特定应用中,需要快速的时间,而对空间要求不苛刻时,哈希数组是一个非常好的方法。为了能够满足各种应用场景,又衍生出容量大小可以动态增长的哈希集合(hash set)、哈希映射(hash map),STL提供了关于哈希的两个类:unordered_set和unordered_map,前者只存储元素,后者可以再增加额外的标志信息。详细的内容,请自行补充。

由于构造的哈希数组,其元素的下标已经改变了,所以需要额外存储元素原始的下标,因此此题使用unordered_map<int,int>,其存储的内容为<元素值,元素原始下标>,详细代码:

class Solution {
public:
/*
* @param numbers : An array of Integer
* @param target : target = numbers[index1] + numbers[index2]
* @return : [index1+1, index2+1] (index1 < index2)
*/
/* Tips: find any pair is ok not all pairs.
* using hash map to store the num value and index
* notice: if the target is 4 and the answer expection num 2 + 2,
* only the one num 2 is stored in hash map, but also work ok!
* because must have the other num 2 is not in hash map!
* */
vector<int> twoSum(vector<int> &nums, int target) {
// write your code here
vector<int> v(2,0);
unordered_map<int,int> hash;// val+id
// we can search num and insert it to hash map at same time
// and current num must be not in hash map
for(int i=nums.size(); i--; hash[nums[i]]=i){
if (hash.find(target-nums[i]) == hash.end()) continue;
v[0] = 1 + i; // the index from 1 to n not 0 to n-1
v[1] = 1 + hash[target-nums[i]];
return v;
}
return v; // no answer return {0,0}
}
};

需要注意的是:哈希无法存储相同元素,因为相同元素有相同的哈希值。如果数组{2,5,6},待求值target=4,没有解;而数组{2,2,5,6},target=4则有解。如何处理这种情况?可以反向遍历,初始hash为空,逐渐将已经遍历过的元素加入到哈希中。

2)三数和(3 sum)

题目编号:57,链接:http://www.lintcode.com/zh-cn/problem/3sum/

题目描述:给出一个有n个整数的数组S,在S中找到三个整数a, b, c,找到所有使得a + b + c = 0的三元组。在三元组(a, b, c),要求a <= b <= c。结果不能包含重复的三元组。

样例:如S = {-1 0 1 2 -1 -4}, 你需要返回的三元组集合的是:(-1, 0, 1),(-1, -1, 2)

这个题目难度增加不少,首先变成3个数的和,然后要求找出所有结果,并且不能重复。但是,返回的只是三元组,并不是原始的下标,如果再使用哈希,那么三个数,需要已知两个数,即要两层for循环,那么时间复杂度O(n^2),并且辅助空间也要O(n^2)。有没有更好地方法?在两数之和时,曾考虑过排序,然后二分查找。三数和不用返回原始下标,那么用排序+二分查找可否?

首先按升序排序;然后定义下标变量i,j,k,因为是三元组,所以要三个变量。如果简单的遍历,那么跟是否有序没有关系,其时间复杂度将达到O(n^3)。仔细想想:如果当前选择了a、b、c三个数,如果其和小于目标target,那么需要将其中一个数用更大的数替换;反之亦然。但究竟替换三个数中的哪个数?无法确定就只能先固定两个变量,让其第三个变化(替换)。一种办法是:固定前两个数i,j,然后让k在一个范围中二分变化(二分查找思想),核心代码如下:

	for (int i=0; i<n; ++i){
for (int j=i+1; j<n; ++j){
for (int left=j+1, right=n-1;left!=right;){
int k = (right+left)/2;
int sum = A[i]+A[j]+A[k];
if (sum>target) right = k;
else if (sum<target) left=k;
else {insert(A[i],A[j],A[k]);break;}
}
}
}

抛开一些细节之外,这种方法时间复杂度仍然很大,为O(n^2logn)。仔细观察发现,k值不是连续变化的,而是两边跳跃的。那么可以只固定一个变量i,让j和k变化。当前值小于target时,可以让j增加;否则,k减小。完整代码如下:

class Solution {
public:
/**
* @param numbers : Give an array numbers of n integer
* @return : Find all unique triplets in the array
* which gives the sum of zero.
* each triplet in non-descending order
*/
vector<vector<int> > threeSum(vector<int> &A) {
// write your code here
vector<vector<int> > vs;
int target = 0;
sort(A.begin(),A.end()); // sort A in ascending order
for(int i=0; i<A.size(); ++i){
if (i>0 && A[i-1]==A[i]) continue; // skip duplication
for(int j=i+1, k=A.size()-1; j<k;){
if (j>i+1 && A[j-1]==A[j]){
++j;
continue; // skip duplication
}
if (k<A.size()-1 && A[k]==A[k+1]){
--k;
continue; // skip duplication
}
int sum = A[i]+A[j]+A[k];
if (sum > target) --k;
else if (sum < target) ++j;
else{ // find a triplet
vector v(3,A[i]);
v[1] = A[j++];
v[2] = A[k--];
vs.push_back(v);
}
}
}
return vs;
}
};

注意去除重复的结果。设一个满足条件的三元组<a,b,c>,如果有重复的三元组与之相同,则说明a,b,c中至少有一个元素的值在数组中出现至少两次。假如a的值2,在数组中出现多次,则其必然是连续的(数组已经排序),因此可以使用如上的方法去除重复的三元组。该方法时间复杂度O(nlogn)+O(n^2),空间复杂度为O(1)。

3)最接近的三数和(3sum closest)

题目编号:59,题目链接:http://www.lintcode.com/zh-cn/problem/3sum-closest/

题目描述:给一个包含 n 个整数的数组 S, 找到和与给定整数 target 最接近的三元组,返回这三个数的和。只需返回最接近的三数和,不需要三个数。

样例:例如 S = [-1, 2, 1, -4] and target = 1. 和最接近 1 的三元组是 -1 + 2 + 1 = 2.

只需寻找三数和,无需去除重复,显然,此题比2)简单得多。可以使用类似的方法,并且实时更新最接近的三数和,这里不再详述,一种实现代码:

class Solution {
public:
/**
* @param numbers: Give an array numbers of n integer
* @param target: An integer
* @return: return the sum of the three integers
* the sum closest target.
*/
int threeSumClosest(vector<int> nums, int tar) {
// write your code here
sort(nums.begin(),nums.end());
int ans = INT_MAX;
for(int i=0; i<nums.size(); ++i){
for(int j=i+1, k=nums.size()-1; j<k;){
int sum = nums[i]+nums[j]+nums[k];
// update the closest answer
ans = (abs(tar-sum)<abs(tar-ans) ? sum:ans);
if (sum > tar) --k;
else if (sum < tar) ++j;
else return sum; // sum equal to target
}
}
return ans;
}
};

4)四数和(4 sum)

题目编号:58,题目链接:http://www.lintcode.com/zh-cn/problem/4sum/

题目描述:给一个包含n个数的整数数组S,在S中找到所有使得和为给定整数target的四元组(a, b, c, d)。四元组(a, b, c, d)中,需要满足a <= b <= c <= d,答案中不可以包含重复的四元组。

样例:例如,对于给定的整数数组S=[1, 0, -1, 0, -2, 2] 和 target=. 满足要求的四元组集合为:

(-1, 0, 0, 1),(-2, -1, 1, 2),(-2, 0, 0, 2)

显然,此题难度大大提高。如果沿用2)的思路,则需要O(n^3)的时间复杂度,但空间为常数级。不妨先试一试:

class Solution {
public:
/**
* @param numbers: Give an array numbersbers of n integer
* @param target: you need to find four elements that's sum of target
* @return: Find all unique quadruplets in the array which gives the sum of
* zero.
*/
/*
* Time O(n^3) , Space O(1)
* */
vector<vector<int> > fourSum(vector<int> A, int tar) {
// write your code here
vector<vector<int> > vs;
sort(A.begin(),A.end()); // ascending order
for(int i=0; i<A.size(); ++i){
if (i>0 && A[i-1]==A[i]) continue; // duplication
for(int j=i+1; j<A.size(); ++j){
if (j>i+1 && A[j-1]==A[j]) continue;// duplication
for(int k=j+1, l=A.size()-1; k<l;){
if (k>j+1 && A[k-1]==A[k]){
++k; // duplication
continue;
}
if (l<A.size()-1 && A[l]==A[l+1]){
--l; // duplication
continue;
}
int sum = A[i]+A[j]+A[k]+A[l];
if (sum > tar) --l;
else if (sum < tar) ++k;
else {
vector<int> v(4,A[i]);
v[1]=A[j], v[2]=A[k++], v[3]=A[l--];
vs.push_back(v);
}
}
}
}
return vs;
}
};

很明显,需要定义四个下标变量:i,j,k,l,其中固定i,j,让k和l一个自增,一个自减。同样需要注意去重复,并且保证每个四元组按升序排列。

如果使用1)的方法,首先将任意两个元素组合,计算其两数和并存入哈希;然后再任选两个数a,b,此时去哈希中寻找是否存在target-a-b。但需要注意的是,具有相同和的二元组,可能不唯一,因此需要一个数组存储所有和相同的二元组,因此,使用unordered_map<int,vector<pair<int,int> > > twosum;作为哈希映射存储,其种key表示两数和的值,数组存储具有该值的所有二元组,pair<int,int>为具体的二元组的元素值。因此,构建哈希映射的时间、空间复杂度为O(n^2)。

然后再一次定义两个下标变量i,j,当选择该i,j时,在哈希映射中可能存在多个二元组的和都为target-a-b,设最多有k个和相同的二元组,则整体时间复杂度为O(k*n^2)

如何进行去重复?目前没有很好的办法。回想一下哈希无法存储相同的元素,因此再使用一个哈希存储候选四元组(candidates),对于任意一个满足体题意的四元组,直接到该哈希中检验是否已经存在,从而去重复。下面是一种实现代码:

class Solution {
public:
// hash functional for hashing the vector
struct hashvec{
size_t operator()(const vector<int> &v)const{
string s;
for (int i=0; i<v.size(); s+=" "+v[i++]);
return hash()(s);
}
};
vector<vector<int> > fourSum(vector<int> A, int tar){
// write your code
sort(A.begin(), A.end()); // ascending order
// using hash map to store the sum of A[i]+A[j] and index i,j
unordered_map<int,vector<pair<int,int> > > twosum;
for (int i=0; i<A.size(); ++i){
if (i>0 && A[i-1]==A[i]) continue; // skip duplication
for (int j=i+1; j<A.size(); ++j) {
if (j>i+1 && A[j-1]==A[j]) continue;// skip duplication
twosum[A[i]+A[j]].push_back(make_pair(i,j));
}
} unordered_set<vector<int>,hashvec> cans; // test duplication
vector<vector<int> > res; // results
unordered_map<int,vector<pair<int,int> > >::iterator ts;
vector<pair<int,int> >::iterator it;
for (int i=2; i<A.size(); ++i) {
for (int j=i+1; j<A.size(); ++j) {
ts = twosum.find(tar-A[i]-A[j]);
if (ts == twosum.end()) continue; // can't find sum
for (it=ts->second.begin(); it!=ts->second.end(); ++it){
if (it->second >= i) continue; // skip duplication
vector<int> v(4, A[it->first]);
v[1]=A[it->second], v[2]=A[i], v[3]=A[j];
// add the v to both cans and res if cans has no v
if (cans.find(v) == cans.end()){
cans.insert(v);
res.push_back(v);
}
}
}
}
return res;
}
};

其中,struct hashvec是一个哈希仿函数。所谓仿函数,其实质并不是函数,只是表现出来像一个函数一样。其作用是计算哈希值。因为STL提供的unordered_map只能对基本数据类型进行计算哈希值,哈希值是元素的“唯一”识别码,之所以带引号,是因为并不存在一个哈希函数能对任意一个元素计算出唯一的识别码,当有两个不同的元素,经过哈希函数计算后,其哈希值相同,那么就需要解决冲突(通常有线性探测和十字链表方法,这里不做详细介绍)。一个好的哈希函数,可以提高哈希的查找速度。对于任意一个四元组,STL并没有相应的哈希函数进行计算,但是STL对字符串提供了简单的哈希函数,因此可以利用这一点,将四元组转化成字符串,从而利用STL自带的哈希函数hash<string>()进行计算,如上面代码3~9行。

因此,这种方法虽然额外占用了O(n^2)的空间,但在时间上大大减少,为O(k*n^2),所以对于某些应用还是有参考意义的。

注意:尽管可以使用哈希映射cans进行去重复,但是在生成两数和的哈希映射twosum时,最好还是进行两数和的去重复,否则哈希映射太大,也会影响性能。

注:本文涉及的代码:

two sum:https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/two-sum.cpp

3 sum:https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/3sum.cpp

3 sum closest:https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/3sum-closest.cpp

4 sum:https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/4sum.cpp

[LintCode/LeetCode]——两数和、三数和、四数和的更多相关文章

  1. 【leetcode 简单】第三题 回文数

    判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向 ...

  2. LeetCode:两数之和、三数之和、四数之和

    LeetCode:两数之和.三数之和.四数之和 多数之和问题,利用哈希集合减少时间复杂度以及多指针收缩窗口的巧妙解法 No.1 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在 ...

  3. 【数据结构】Hash表简介及leetcode两数之和python实现

    文章目录 Hash表简介 基本思想 建立步骤 问题 Hash表实现 Hash函数构造 冲突处理方法 leetcode两数之和python实现 题目描述 基于Hash思想的实现 Hash表简介 基本思想 ...

  4. LeetCode两数之和

    LeetCode 两数之和 题目描述 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...

  5. 【LeetCode】18、四数之和

    题目等级:4Sum(Medium) 题目描述: Given an array nums of n integers and an integer target, are there elements ...

  6. 【算法训练营day7】LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和

    [算法训练营day7]LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和 LeetCode454. 四数相加I ...

  7. MySQL之单表查询 一 单表查询的语法 二 关键字的执行优先级(重点) 三 简单查询 四 WHERE约束 五 分组查询:GROUP BY 六 HAVING过滤 七 查询排序:ORDER BY 八 限制查询的记录数:LIMIT 九 使用正则表达式查询

    MySQL之单表查询 阅读目录 一 单表查询的语法 二 关键字的执行优先级(重点) 三 简单查询 四 WHERE约束 五 分组查询:GROUP BY 六 HAVING过滤 七 查询排序:ORDER B ...

  8. 【LeetCode每天一题】4Sum(4数之和)

    Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums s ...

  9. [LeetCode] 454. 4Sum II 四数之和II

    Given four lists A, B, C, D of integer values, compute how many tuples (i, j, k, l) there are such t ...

随机推荐

  1. python之random模块分析(一)

    random是python产生伪随机数的模块,随机种子默认为系统时钟.下面分析模块中的方法: 1.random.randint(start,stop): 这是一个产生整数随机数的函数,参数start代 ...

  2. nginx1.8.1反向代理、负载均衡功能的实现

    nginx1.8.1 proxy 服务器192.168.8.40 web1 centos6.5 httpd2.2.15 web2 centos7.2 httpd2.4.6 1.代理功能的简单实现 ng ...

  3. ocos2d-x 3.0坐标系详解--透彻篇 ---- convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中。

    convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中.重点说明:基于...   不一定要是真实的,  convertToWorldSpace 的结果也只是一个新 ...

  4. linux unzip 中文乱码解决方法

    引自:https://blog.csdn.net/abyjun/article/details/48344379 unzip -O CP936 xxx.zip (用GBK, GB18030也可以)

  5. Linux 文件查找命令详解

    find命令 Linux find命令用来在指定目录下查找文件.任何位于参数之前的字符串都将被视为欲查找的目录名.如果使用该命令时,不设置任何参数,则find命令将在当前目录下查找子目录与文件.并且将 ...

  6. STM32应用实例六:与MS5837压力传感器的I2C通讯

    MS5837压力传感器是一种可用于电路板上,适用于检测10-1200mbar压力范围的传感器,灵敏度非常高,理论上能够检测到0.01mbar的压力变化,实际使用过程中测试并无明显的变化. MS5837 ...

  7. PHP接口继承及接口多继承原理与实现方法详解

    在PHP的接口中,接口可以继承接口.虽然PHP类只能继承一个父类(单继承),但是接口和类不同,接口可以实现多继承,可以继承一个或者多个接口.当然接口的继承也是使用extends关键字,要多个继承的话只 ...

  8. mysql的undo log和redo log

    在数据库系统中,既有存放数据的文件,也有存放日志的文件.日志在内存中也是有缓存Log buffer,也有磁盘文件log file,本文主要描述存放日志的文件.     MySQL中的日志文件,有这么两 ...

  9. 性能测试四十:Mysql存储过程造数据

    性能测试是基于大量数据的,而进行性能测试之前肯定没那么多数据,所以就要自己准备数据 数据构造方法: 1.业务接口 -- 适合数据表关系复杂 -- 优点:数据完整性比较好2.存储过程 -- 适合表数量少 ...

  10. python 全栈开发,Day94(Promise,箭头函数,Django REST framework,生成json数据三种方式,serializers,Postman使用,外部python脚本调用django)

    昨日内容回顾 1. 内容回顾 1. VueX VueX分三部分 1. state 2. mutations 3. actions 存放数据 修改数据的唯一方式 异步操作 修改state中数据的步骤: ...