原题链接

2Sum

Two Sum

意思是给定一个数组,求数组中哪两个元素的和是给定值。


蛮力法的求解就是两层for循环,时间复杂度是O(n2)。

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

显然这种方式对于算法题来说复杂度过高了,仔细想一下,每次固定一个i,变化j的时候,小于i的那部分其实在之前已经访问过一次了,为什么呢
假设nums.大小为10,此时i为5,j从0到9计算nums[i] + nums[j]

nums[0] + nums[5],
nums[1] + nums[5],
...
nums[4] + nums[5],

当i = 0, 1, 2, 3, 4时是不是都计算过?,所以又重复计算了一遍,整个程序多计算了n遍,这便是复杂度的原因。


解决方法,首先想到的优化就是让j从i+1开始

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

效率得到了一定优化,在考虑是否可以继续优化呢,想一下,在遍历j时

/* i == 5时遍历了 */
nums[6], nums[7], nums[8], nums[9];/* i == 6时遍历了 */
nums[7], nums[8] ...

所以发现对于i后面的那些仍然会重复遍历n次,还有什么方法可以优化呢,其实到这,再优化的方法只能想办法让复杂度变为O(n),也就是让每一个元素只遍历一遍,那么就不能套循环,只能使用一层循环。

for(int i = 0; i < nums.size(); ++i)
{}

当遍历某个nums[i]时,唯一可能知道的、程序可能会优化的就是从nums[0]到nums[i-1],因为nums[i]往后的元素还没有遍历过,根本不知道是什么。再想,可不可以不判断nums[i] + nums[j]的和而是直接判断i前面有没有nums[j]这个数呢?nums[j]是多少?(假设j是0到i-1中的某个数)

int left = target - nums[i];

我们只需要判断前i-1个数中有没有left就行了,那么就需要使用某种数据结构存储访问过的nums[i],什么数据结构可以达到o(1)的效果呢?哈希表

/* 通常使用unordered_map来代表哈希表 */
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> hash;for(int i = 0; i < nums.size(); ++i){int left = target - nums[i];if(hash.find(left) != hash.end())return {hash[left], i};elsehash[nums[i]] = i;}return {0, 0};}
};

3Sum

扩展题型为Three Sum,原题链接3Sum

要求和2Sum差不多,区别在于是三个数的和,target为0,同时会有多个解,而且最要命的是竟然可以有重复的元素。


吸收了2Sum的教训,聪明的boy可能想这里我也要用unordered_map,于是乎写出如下代码

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {/* 为了处理重复元素,首先排序nums */std::sort(nums.begin(), nums.end());vector<vector<int>> ans;unordered_map<int, int> hash;for(int i = 0; i < nums.size() - 2; ++i){int target = -nums[i];hash.clear();for(int j = i + 1; j < nums.size(); ++j){int ntarget = target - nums[j];if(hash.find(ntarget) != hash.end()){ans.push_back({nums[i], nums[j], ntarget});/* * 如果后面几个元素和当前元素重复,直接跳过,为什么可以直接跳过呢* 如果nums[j] == nums[j + 1],那么当j++后仍然求出的是当前结果,因为* ntarget只可以是在nums[j]前面的数*/while(j < nums.size() && nums[j + 1] == nums[j])++j;}elsehash[nums[j]] = j;     }/* 同理 */while(i < nums.size() - 2 && nums[i] == nums[i + 1])++i;}return ans;}
};

于是乎兴奋的submit,却发现,额….

效率低的吓人,为什么呢,因为即使这样,仍然有着O(n2)的复杂度,唔…又开始进入优化的坑
对于现实主义者的我们来说O(n)是不可能了,和O(nlogn)有关的二分法好像也不太适用。首先判断肯定是要固定一个之后再遍历一遍,因为仍然有两个数是不确定的。

这里引入一种方法,模仿二分发left和right的移动。因为序列是有序的,那么仅仅需要判断nums[i + 1]和nums[nums.size() - 1]的和,从而得知是大(向左移),小(向右移动)

int left = 0;
int right = nums.size() - 1;
while(left < right)
{if(nums[left] + nums[right] > target)--right;else if(nums[left] + nums[right] < target)++left;else{/* 结果中的一员 push到结果中*//* 防止重复 */while(left < right && nums[left] == nums[left + 1])++left;while(left < right && nums[right] == nums[right - 1])--right;/* * 为什么这里需要++和--* 此时left是最后一个和之前的nums[left]重复的下标,需要++到第一个不重复的下标* 因为nums[left]已经改变,nums[left] + nums[right]不可能再等于target,所以right无需保持在最后一个和之前nums[right]重复的位置,也向前移动--* /++left;--right;}
}

利用这种方法的效率比上面高一些,可能原因就在于是从两边同时向中间移动,但是仍然摆脱不了O(n3)的复杂度(我一直以为上面的方法可以达到O(logn)….错了好久),代码如下

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {std::sort(nums.begin(), nums.end());vector<vector<int>> ans;for(int i = 0; i < nums.size(); ++i){int target = -nums[i];int left = i + 1;int right = nums.size() - 1;while(left < right){if(nums[left] + nums[right] > target)--right;else if(nums[left] + nums[right] < target)++left;else{ans.push_back({nums[i], nums[left], nums[right]});while(left < right && nums[left] == nums[left + 1])++left;while(left < right && nums[right] == nums[right - 1])--right;++left;--right;}}while(i < nums.size() - 2 && nums[i] == nums[i + 1])++i;}return ans;}
};

此时可能回想,2Sum我能不能也使用这种方法提高效率呢,想法是好的,可是要求是有序数组,而基于比较的最快的排序快排也只能是O(nlogn),显然得不偿失


4Sum

最后一个扩展为4Sum,原题链接4Sum

和3Sum完全一样,只是4个数的和,代码也类似,不再强调了。
唔…leetcode上的解法也都是O(n3),既然都这样就不想优化了


3Sum Closest

原题链接3Sum Closest

意思是给定一个数组,求数组中哪三个数的和最接近target,返回三个数的和。
这道题和3Sum是一样的,利用上面的思想,固定一个,剩下两个从两边开始找即可,当然需要排好序,代码如下

class Solution {
public:int threeSumClosest(vector<int>& nums, int target) {std::sort(nums.begin(), nums.end());int min_dis = INT_MAX;int three_sum = 0;for(int i = 0; i < nums.size(); ++i){int left = i + 1;int right = nums.size() - 1;while(left < right){/* 多出一部分用于比较和target的距离,记录和 */int sum = nums[i] + nums[left] + nums[right];if(abs(sum - target) < min_dis){three_sum = sum;min_dis = abs(sum - target);}if(sum > target)--right;else if(sum < target)++left;elsereturn sum;}}return three_sum;}
};

注:多数代码都在这里直接手打的,难免有错误,轻喷

每天一道LeetCode-----给定序列中2/3/4个元素的和为target的所有集合,或3个元素的和最接近target的集合相关推荐

  1. 每天一道LeetCode-----在给定序列中找到满足nums[i]nums[i-1]nums[i]nums[i+1]的位置,要求时间复杂度是O(logN)

    Find Peak Element 原题链接Find Peak Element 给定一个序列,找到一个位置i满足nums[i]>nums[i−1]&&nums[i]>num ...

  2. Python---寻找给定序列中相差最小的两个数字

    编写函数,寻找给定序列中相差最小的两个数字 def getTwoClosestElements(arr):#先进行排序,使得相邻元素最接近#相差最小的元素必然相邻seq = sorted(arr)#先 ...

  3. 每天一道LeetCode-----找到给定序列中所有和为某个值的集合或集合个数,序列中可以有/无重复项,集合元素顺序不同算不同集合等

    Combination Sum 原题链接Combination Sum 给定一个无重复项的序列,找到所有和是target的集合,每个元素可以使用多次. 可以用深度优先(dfs),对于某个元素都有两种选 ...

  4. 每天一道LeetCode-----计算给定序列中所有长度为k的滑动窗的最大值集合

    原题链接Sliding Window Maximum 给定一个数组,从左到右每k个位置算作一个滑动窗,每到达一个滑动窗,都需要找到这个滑动窗中最大的元素并记录下来,最后返回所有最大元素组成的数组.要求 ...

  5. 每天一道LeetCode-----找到序列中第一个没有出现的正整数,要求时间复杂度是O(n),空间复杂度是O(1)

    First Missing Positive 原题链接First Missing Positive 给定一个序列,找到第一个没有出现的正整数,要求复杂度是O(n),空间复杂度为O(1). 这种时间复杂 ...

  6. LeetCode 1819. 序列中不同最大公约数的数目

    文章目录 1. 题目 2. 解题 1. 题目 给你一个由正整数组成的数组 nums . 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数. 例如,序列 [4,6,16] 的最大公约数 ...

  7. 每天一道LeetCode-----在给定数组中找到一个子数组,使得这个子数组的元素乘积最大

    Maximum Product Subarray 原题链接Maximum Product Subarray 在给定数组中找到一个子数组,使得这个子数组元素的乘积最大.给定数组中可能有正数,负数和0 思 ...

  8. 每天一道LeetCode-----删除序列中指定元素,将满足要求的元素移动到前面

    Move Zeroes 原题链接Move Zeroes 意思是给定一个数组,将所有的0都移到后面,不能改变非0元素的相对顺序 把非0元素移到前面,后面补零即可,比较简单 class Solution ...

  9. 每日一道Leetcode -网格矩阵中的路径相关系列【动态规划|路径中(不)含值|有无障碍物|最小路径和】

    class Solution:def uniquePaths(self, m: int, n: int) -> int:"""排列组合方法:从左上角到右下角的过程中 ...

最新文章

  1. 【BZOJ】1711: [Usaco2007 Open]Dining吃饭
  2. Win7系统电脑休眠后无法唤醒的解决方法
  3. 微课|玩转Python轻松过二级(2.4节):常用内置函数用法精要3
  4. springsecurity原理执行流程_3. Spark原理-执行流程解析
  5. redis 完整使用文档(2021)
  6. 联想台式计算机亮度怎么调,台式联想电脑亮度在哪里调(手把手教你调电脑亮度)...
  7. 【Transformer】继续学习:Attention,Self-Attention,Multi-head Attention。
  8. 基于GA优化BP神经网络的传感器故障诊断算法matlab仿真
  9. POJ1042 John钓鱼 C语言代码
  10. 11月区块链投融资事件回顾
  11. 短网址(Short URL)服务
  12. 审美--《艺术与审美》课程学习笔记
  13. 计算机考证证书怎么打印
  14. 中国程序员视角下的英文命名
  15. 3D立体照片墙—HTML
  16. PHP2(phps)- URL编码解码原理
  17. 【BABY夜谈大数据】计算文本相似度
  18. LINUX查看文件内容命令cat vi/vim more tail head的区别
  19. 【整理】编程单词缩写规则
  20. android x86玩和平精英,这几款安卓手机玩和平精英,让体验与操作更好,关键它们还便宜...

热门文章

  1. django手机访问_Django从入门到大作业:2-见网页
  2. 2013年人人校园招聘笔试题
  3. 2006年清华大学计算机研究生机试真题
  4. beta 圆桌桌 4
  5. javascript基础07
  6. using可以用于释放操作,相当于Dispose()
  7. 【Weiss】【第03章】练习3.20:中缀表达式转后缀表达式
  8. .NET下安装卸载WindowsService批处理脚本
  9. 在ASP.NET MVC 4中使用Kendo UI Grid
  10. c#写数据到Excel慢