[LintCode/LeetCode]——两数和、三数和、四数和
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]——两数和、三数和、四数和的更多相关文章
- 【leetcode 简单】第三题 回文数
判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向 ...
- LeetCode:两数之和、三数之和、四数之和
LeetCode:两数之和.三数之和.四数之和 多数之和问题,利用哈希集合减少时间复杂度以及多指针收缩窗口的巧妙解法 No.1 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在 ...
- 【数据结构】Hash表简介及leetcode两数之和python实现
文章目录 Hash表简介 基本思想 建立步骤 问题 Hash表实现 Hash函数构造 冲突处理方法 leetcode两数之和python实现 题目描述 基于Hash思想的实现 Hash表简介 基本思想 ...
- LeetCode两数之和
LeetCode 两数之和 题目描述 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...
- 【LeetCode】18、四数之和
题目等级:4Sum(Medium) 题目描述: Given an array nums of n integers and an integer target, are there elements ...
- 【算法训练营day7】LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和
[算法训练营day7]LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和 LeetCode454. 四数相加I ...
- MySQL之单表查询 一 单表查询的语法 二 关键字的执行优先级(重点) 三 简单查询 四 WHERE约束 五 分组查询:GROUP BY 六 HAVING过滤 七 查询排序:ORDER BY 八 限制查询的记录数:LIMIT 九 使用正则表达式查询
MySQL之单表查询 阅读目录 一 单表查询的语法 二 关键字的执行优先级(重点) 三 简单查询 四 WHERE约束 五 分组查询:GROUP BY 六 HAVING过滤 七 查询排序:ORDER B ...
- 【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 ...
- [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 ...
随机推荐
- Ubuntu14.04+caffe+CPU
刚刚在上篇博客记录了windows10下GPU版本caffe的安装,正准备跑跑论文里的代码,发现好多命令都是.sh命令,这是linux系统的脚本文件.不能直接在windows下运行,于是我想把.sh转 ...
- Django 自定义过滤器和模板标签
前提:自定义模板标签和过滤器必须位于Django的某个应用中,这个应用可以包含一个templatetags目录, 和models.py views.py 处于同一级目录.若这个templatetags ...
- Python3学习笔记21-实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性. 给实例绑定属性的方法是通过实例变量,或者通过self变量: class Student(object): def __init__(se ...
- 64位Win7系统WMware安装Mac OS
1. 准备工作 l VMWare Workstation,我的版本是 l MAC OS安装光盘镜像文件,种子地址 http://www.kuaipan.cn/file/id_611 ...
- 【转】CString与string、char*的区别和转换
我们在C++的开发中经常会碰到string.char*以及CString,这三种都表示字符串类型,有很多相似又不同的地方,常常让人混淆.下面详细介绍这三者的区别.联系和转换: 各自的区别 char*: ...
- python中对列表的所有操作方法
列表: names = ['a','b','c','d'] 1.追加:names.append() >>> names.append('e') >>> names ...
- Python-HTML 最强标签分类
编程: 使用(展示)数据 存储数据 处理数据 前端 1. 前端是做什么的? 2. 我们为什么要学前端? 3. 前端都有哪些内容? 1. HTML 2. CSS 3. JavaScript 4.jQue ...
- vue2的缓存问题(非原创)
keep-alive是vue内置的一个组件,可以使被它包含的组件处于保留状态,或避免被重新渲染. 用法: 运行结果描述: input输入框内,路由切换输入框内部的内容不会发生改变. 常见的用法:(下图 ...
- 对以内部 git 仓库为 composer 依赖的 package,加上版本号
现实问题 之前同事做了一个 composer package,做为公司大量 laravel 项目的通用模块. 但是,在实际使用中,每个项目对改 package 的依赖版本是有所不同的.否则 compo ...
- poj2019 二维RMQ模板题
和hdu2888基本上一样的,也是求一个矩阵内的极值 #include<iostream> #include<cstring> #include<cstdio> # ...