📕作者简介:非科班在读,纯技术博客分享,致力于C/C++,涉及Python、C/C++、Linux,数据结构,git企业级开发,Mysql等。
📗本文收录于算法指南,旨在帮助读者应对各大互联网大厂笔试题,构建完整的算法体系!
前言:双指针简介
常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。
对撞指针:⼀般⽤于顺序结构中,也称左右指针。
。对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
。对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
◦ left == right (两个指针指向同⼀个位置)
◦ left > right (两个指针错开)
快慢指针:⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
这种⽅法对于处理环形链表或数组⾮常有⽤。其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想。
快慢指针的实现⽅式有很多种,最常⽤的⼀种就是:
。 在⼀次循环中,每次让慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢。
话不多说,现在来看看大厂们比较有代表性的面试题吧!
一、611. 有效三角形的个数
1.1 算法思路(排序 + 双指针)
先将数组排序。
我们可以固定⼀个「最⻓边」,然后在⽐这条边⼩的有序数组中找出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指针」来优化。
- 设最⻓边枚举到 i 位置,区间 [left, right] 是 i 位置左边的区间(也就是⽐它⼩的区间):
- 如果 nums[left] + nums[right] > nums[i] ;说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成⽐nums[i] ⼤的⼆元组,满⾜条件的有 right - left 种。此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进⼊下⼀轮判断。
- 如果 nums[left] + nums[right] <= nums[i] ;说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满⾜条件的⼆元组。left 位置的元素可以舍去, left++ 进⼊下轮循环。
1.2 代码实现
class Solution { public: int triangleNumber(vector<int>& nums) { sort(nums.begin(), nums.end());//排序 int ret=0; for(int i=nums.size()-1; i>=2; i--) { int left=0, right=i-1, target=nums[i]; while(left<right) { if(nums[left] + nums[right] > target) { ret += right-left; right--; } else { left++; } } } return ret; } };
二、LCR 179. 查找总价格为目标值的两个商品
(https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/)
2.1 算法思路
注意到本题是升序的数组,因此可以⽤「对撞指针」优化时间复杂度。
算法流程如下:
- 初始化 left , right 分别指向数组的左右两端(这⾥不是我们理解的指针,⽽是数组的下标)
- 当 left < right 的时候,⼀直循环以下过程:
- 1.当 nums[left] + nums[right] == target 时,说明找到结果,记录结果,并且返回;
- 2.当 nums[left] + nums[right] < target 时:
- 对于 nums[left] ⽽⾔,此时 nums[right] 相当于是 nums[left] 能碰到的最⼤值(别忘了,这⾥是升序数组哈~)。如果此时不符合要求,说明在这个数组⾥⾯,没有别的数符合 nums[left] 的要求了(最⼤的数都满⾜不了你,你已经没救了)。因此,我们可以⼤胆舍去这个数,让 left++ ,去⽐较下⼀组数据;
- 那对于 nums[right] ⽽⾔,由于此时两数之和是⼩于⽬标值的, nums[right]还可以选择⽐ nums[left] ⼤的值继续努⼒达到⽬标值,因此 right 指针我们按兵不动;
- 当 nums[left] + nums[right] > target 时,同理我们可以舍去nums[right] (最⼩的数都满⾜不了你,你也没救了)。让 right-- ,继续⽐较下⼀组数据,⽽ left 指针不变(因为他还是可以去匹配⽐ nums[right] 更⼩的数)。
2.2 代码实现
class Solution { public: vector<int> twoSum(vector<int>& price, int target) { int left=0, right=price.size()-1; while(left<right) { if(price[left] + price[right] > target) right--; else if(price[left] + price[right] < target) left++; else return {price[left], price[right]}; } return {-1, -1}; } };
三、15. 三数之和
3.1 算法思路
本题与两数之和类似,是⾮常经典的⾯试题。
与两数之和稍微不同的是,题⽬中要求找到所有「不重复」的三元组。那我们可以利⽤在两数之和那⾥⽤的双指针思想,来对我们的暴⼒枚举做优化:
- 先排序;
- 然后固定⼀个数 a :
- 在这个数后⾯的区间内,使⽤「双指针算法」快速找到两个数之和等于 -a 即可。
但是要注意的是,这道题⾥⾯需要有「去重」操作~
- 找到⼀个结果之后, left 和 right 指针要「跳过重复」的元素;’
- 当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素
3.2 代码实现
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> ret; //1. 排序 sort(nums.begin(), nums.end()); //2. 双指针思路 for(int i=nums.size()-1; i>=2; i--) { if(nums[i]<0) break; int left =0, right=i-1; while(left<right) { if(nums[left] + nums[right] + nums[i] > 0) right--; else if(nums[left] + nums[right] + nums[i] < 0) left++; else { //相等,记录数据;处理相同元素 ret.push_back({nums[left], nums[right], nums[i]}); left++, right--; //处理相同元素 while(left<right && nums[left-1] == nums[left]) { left++; } while(left<right && nums[right+1] == nums[right]) { right--; } } //处理相同元素,防止重叠 while(i >=2 && nums[i] == nums[i-1]) { i--; } } } return ret; } };
四、18. 四数之和
4.1 算法思路(排序 + 双指针)
a. 依次固定⼀个数 a ;
b. 在这个数 a 的后⾯区间上,利⽤「三数之和」找到三个数,使这三个数的和等于 target - a 即可。
4.2 代码实现
lass Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { vector<vector<int>> ret; // 1. 排序 sort(nums.begin(), nums.end()); // 2. 利⽤双指针解决问题 int n = nums.size(); for(int i = 0; i < n; ) // 固定数 a { // 利⽤ 三数之和 for(int j = i + 1; j < n; ) // 固定数 b { // 双指针 int left = j + 1, right = n - 1; long long aim = (long long)target - nums[i] - nums[j]; while(left < right) { int sum = nums[left] + nums[right]; if(sum < aim) left++; else if(sum > aim) right--; else { ret.push_back({nums[i], nums[j], nums[left++], nums[right--]}); // 去重⼀ while(left < right && nums[left] == nums[left - 1]) left++; while(left < right && nums[right] == nums[right + 1]) right--; } } // 去重⼆ j++; while(j < n && nums[j] == nums[j - 1]) j++; } // 去重三 i++; while(i < n && nums[i] == nums[i - 1]) i++; } return ret; } };