1. Two Sum (Array, Hash Table) 两数之和
题目描述:
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
思路分析:
为了找到两个数索引,可以固定一个nums[i],然后再在数组中搜索(target-nums[i])的索引。
1. 暴力破解
两层遍历,固定一个元素,循环搜索另外一个元素,时间复杂度0(n^2),效率太低舍弃;
2. 哈希表(hash table)
由于对Hash表的搜索是0(1),所以我们可以先把数组元素全部放入Hash表(因为要求返回元素索引,所以让数组元素作键,数组元素索引作值)。这样只需一遍遍历数组元素即可,时间复杂度0(n)。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> vi;
unordered_map<int,int> hmap;//声明一个Hash Table
for(auto i = 0; i < nums.size(); i++){
hmap.insert(pair<int,int>(nums[i],i));
}
for(auto j = 0; j < nums.size(); j++){
if(hmap.find(target - nums[j]) != hmap.end()){
int k = hmap[target - nums[j]];
if(k < j){//为了避免重复,按序输出
vi.push_back(k);
vi.push_back(j);
}
}
}
return vi;
}
};
4. Median of Two Sorted Arrays(Array,Binary Search,Divide and Conquer)两个排序数组的中位数
题目描述:
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。
请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。
示例 1:
nums1 = [1, 3] nums2 = [2] 中位数是 2.0
示例 2:
nums1 = [1, 2] nums2 = [3, 4] 中位数是 (2 + 3)/2 = 2.5
思路分析:
本题需要考虑两个方面:1.两数组总数为奇数,直接返回中间值。2.两数组总数为偶数时,返回中间两个值得均值。
接下来需要使用递归:在递归函数中,要保证第一个数组总是小于第二个数组;如果第一个数组为空,直接返回第二个数组中间值;若求第一个最小的元素,则返回两数组两个元素较小的一个。
再接下来使用二分搜索:具体看下面程序
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { //Divide Conquer
int m = nums1.size();
int n = nums2.size();
//重新创建两个vector<int>对象,目的防止在总个数为偶数时传参出错
vector<int> x = nums1;
vector<int> y = nums2;
int sum = m + n;
if(sum & 0x1){ // 当总个数为奇数时直接返回中间值
return findTop_K_Value(nums1, m, nums2, n, sum / 2 + 1);
}
else{// 当总个数为偶数时返回中间两个值的均值
return ( findTop_K_Value(nums1, m, nums2, n, sum / 2)
+ findTop_K_Value(x, m, y, n, sum / 2 + 1) ) / 2.0; //x,y的目的防止在总个数为偶数时传参出错
}
}
double findTop_K_Value(vector<int>& a, int m, vector<int>& b, int n, int Top_K){
if(m > n){//保证第一个数组总是小于第二个数组
return findTop_K_Value(b, n, a, m, Top_K);
}
if(m == 0){//如果第一个数组为空,直接返回第二个数组的中间值
return b[Top_K - 1];
}
if(Top_K == 1){//如果要求的是第一个返回的最小值,则求a[0]和b[0]较小的一个
return min(a[0], b[0]);
}
//将Top_K分成两部分,二分搜索
int part_a = min(Top_K / 2, m), part_b = Top_K - part_a;
if(a[part_a - 1] < b[part_b -1]){
a.erase(a.begin(), a.begin() + part_a); //删除a[part_a]前面元素
return findTop_K_Value(a, m - part_a, b, n, Top_K - part_a);
}
else if(a[part_a - 1] > b[part_b -1]){
b.erase(b.begin(), b.begin() + part_b); //删除b[part_b]前面元素
return findTop_K_Value(a, m, b, n - part_b, Top_K - part_b);
}
else
return a[part_a - 1];
}
};
11. Container With Most Water(Array,Two Pointers)盛最多水的容器
题目描述:
给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。画 n 条垂直线,使得垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
注意:你不能倾斜容器,n 至少是2。
思路分析:
1.首先假设我们找到能取最大容积的纵线为 i , j (假定i<j),那么得到的最大容积 C = min( ai , aj ) * ( j- i) ;
2.下面我们看这么一条性质:
①: 在 j 的右端没有一条线会比它高! 假设存在 k |( j<k && ak > aj) ,那么 由 ak> aj,所以 min( ai,aj, ak) =min(ai,aj) ,所以由i, k构成的容器的容积C' = min(ai,aj ) * ( k-i) > C,与C是最值矛盾,所以得证j的后边不会有比它还高的线;
②:同理,在i的左边也不会有比它高的线;
这说明什么呢?如果我们目前得到的候选: 设为 x, y两条线(x< y),那么能够得到比它更大容积的新的两条边必然在 [x,y]区间内并且 ax' > =ax , ay'>= ay;
3.所以我们从两头向中间靠拢,同时更新候选值;在收缩区间的时候优先从 x, y中较小的边开始收缩。
算法时间复杂度O(n)
此处参考:https://blog.youkuaiyun.com/runningtortoises/article/details/45568305
class Solution {
public:
int maxArea(vector<int>& height) {
int Max = -1, area, left = 0, right = height.size()-1,k;
while(left < right){//从两头向中间靠拢,同时更新候选值
area = (height[left] < height[right] ? height[left] : height[right]) * (right - left);
Max = Max < area ? area : Max;
if(height[left] < height[right]){//在收缩区间的时候优先从较小的边开始收缩
k = left;
while(left < right && height[left] <= height[k])
++left;
}
else{
k = right;
while(left < right && height[right] <= height[k])
--right;
}
}
return Max;
}
};
15. 3Sum(Array,TwoPointers) 三数之和
题目描述:
给定一个包含 n 个整数的数组 num
,判断 num
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]思路分析:
1 排序
2 前面的两个小数和后面的一个大数相加比较,如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
3 前面的数和后面的数相遇,本次循环结束。
如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。 还有注意的就是:记得越过重复的数,以避免答案重复。
时间复杂度O(n^2)
class Solution {
public:
/*时间复杂度O(n^2)
1 排序
2 前面的两个小数和后面的一个大数相加比较,如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
3 前面的数和后面的数相遇,本次循环结束。
如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。
还有注意的就是:记得越过重复的数,以避免答案重复。当然也可以使用额外的数据结果,如set来作为容器,不允许重复数据的,或者最后使用unique处理掉重复元素;
最好还是踏踏实实用实例走一走,就可以发现很多原来无法发现的问题。用的实例最好包括特例的实例,例如空,重复,边界重复等。
*/
vector<vector<int>> threeSum(vector<int>& num) {
vector<vector<int>> ret;
sort(num.begin(), num.end());
int len = num.size();
if(len < 3)
return ret;
for(int i = 0; i < len - 2; ++i){
if(i > 0 && num[i] == num[i - 1]) continue;//防止第一个数字重复, 避免重复项
// 此处剪枝格外重要
if(num[i] + num[len - 2] + num[len - 1] < 0) continue;
if(num[i] + num[i + 1] + num[i + 2] > 0) return ret;
for(int j = i + 1, k = len -1; j < k;){
if(num[i] + num[j] + num[k] < 0) ++j;
else if(num[i] + num[j] + num[k] > 0) --k;
else{
vector<int> tmp = {num[i],num[j],num[k]};
ret.push_back(tmp);
do{
++j;
}while(j < k && num[j] == num[j - 1]);
do{
--k;
}while(j < k && num[k] == num[k + 1]);
}
}
}
return ret;
}
};
16. 3Sum Closest(Array,TwoPointers) 最接近的三数之和
题目描述:
给定一个包括 n 个整数的数组 nums
和 一个目标值 target
。找出 nums
中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1. 与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).思路分析:
1 排序
2 本题与上题在技巧上处理,把三个数加和后与target作差作为判断依据 。 如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
3 前面的数和后面的数相遇,本次循环结束。
如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。本 还有注意的就是:记得越过重复的数,以避免答案重复。
时间复杂度O(n^2)
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int closest = INT_MAX;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size() - 2; ++i){
for(int j = i + 1, k = nums.size() - 1; j < k;){
int formula = nums[i] + nums[j] + nums[k] - target;
if(formula == 0)
return target;
else if(formula > 0){
--k;
if(formula < abs(closest)) closest = formula;
}
else{
++j;
if(-formula < abs(closest)) closest = formula;
}
}
}
return closest + target;
}
};
18. 4Sum (Array,HashTable,TwoPointers) 四数之和
题目描述:
给定一个包含 n 个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
思路分析:
1. 通用法: 先排序后夹逼,将K-sum问题退化到K-1 Sum问题,最终退化到2Sum问题,K-sum问题最好能做到O(n^(K-1))复杂度,有严格数学证明
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& num, int target) {
//通用法 退化到2Sum问题,K-sum问题最好能做到O(n^(K-1))复杂度,有严格数学证明
vector<vector<int>> ret; //ret是return的缩写
if(num.size() < 4) return ret;
int n = num.size();
sort(num.begin(), num.end());
for(int i = 0; i < n - 3; ++i) //(1) trick 1 确定范围到倒数第四个数
{
if(i > 0 && num[i] == num[i-1]) continue; //防止第一个数字重复 (2)trick 2 避免重复项
for(int j = i+1; j < n - 2; ++j)
{
if(j > i + 1 && num[j] == num[j-1]) continue; //防止第二个数字重复
int newtarget = target - num[i] - num[j];
int k = j + 1;
int l = n - 1;
while(k < l)
{
if(num[k] + num[l] == newtarget)
{
vector<int> tmp{num[i], num[j], num[k], num[l]};
ret.push_back(tmp);
++k; --l;
while(k < l && num[k] == num[k-1]) ++k; //trick 3 跳过重复项
while(k < l && num[l] == num[l+1]) --l;
}
else if(num[k] + num[l] > newtarget) --l;
else ++k;
}
}
}
return ret;
}
};
2. 经过剪枝处理后的高效通用法
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& num, int target) { //better solution!
vector<vector<int>> ret;
int sz = num.size();
sort(num.begin(), num.end());
for (int a = 0; a < sz - 3; ++a) {
if (a > 0 && num[a] == num[a-1]) continue;//防止第一个数字重复, 避免重复项
// 此处剪枝格外重要,未剪枝60ms,剪枝15ms, nice!
if (num[a] + num[sz - 3] + num[sz - 2] + num[sz - 1] < target) continue;
if (num[a] + num[a + 1] + num[a + 2] + num[a + 3] > target) return ret;
for (int b = a + 1; b < sz - 2; ++b) {
if (b > a + 1 && num[b] == num[b-1]) continue;//防止第二个数字重复, 避免重复项
// 此处剪枝可从15ms到12ms,要注意若>target, 不能直接return了,只是内层b循环不必再试,外层a循环还要继续
if (num[a] + num[b] + num[sz - 2] + num[sz - 1] < target) continue;
if (num[a] + num[b] + num[b + 1] + num[b + 2] > target) break;
int c = b + 1, d = sz - 1;
while (c < d) { // c和d在此循环中相向而行,寻找和为target的情况
if (num[a] + num[b] + num[c] + num[d] > target) --d;
else if (num[a] + num[b] + num[c] + num[d] < target) ++c;
else {
vector<int> tmp{num[a], num[b], num[c], num[d]};
ret.push_back(tmp);
do {
--d;
} while (c < d && num[d] == num[d+1]);//防止第四个数字重复, 避免重复项
do {
++c;
} while (c < d && num[c] == num[c-1]);//防止第三个数字重复, 避免重复项
}
}
}
}
return ret;
}
};
26. Remove Duplicates from Sorted Array(Array,TwoPointers)删除排序数组中的重复项
题目描述:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 int len = removeDuplicates(nums); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); }思路分析:
初始化一个新变量index记录非重复元素个数,for循环查找不同元素,并记录,最终返回index。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int index = 0;
if(nums.size() <= 1)
return nums.size();
nums[index++] = nums[0];
for(int i = 0; i < nums.size() - 1; ++i){
if(nums[i] != nums[i + 1])
nums[index++] = nums[i + 1];
}
return index;
}
};