一、双指针算法题(上)
283. 移动零
题目要求:
思路:两层循环,第一层循环寻找0,第二层找非0,交换。两层循环,时间复杂度为O()
优化思路:双指针
cur先遍历数组,寻找非0元素,dest暂时按兵不动,dest到快指针中间的元素(左开右开),用来表示0元素,cur到数组结尾表示的是为探索区域,直到cur找到非0元素,交换0元素和非0元素,dest移动,数组开始到dest,左闭右开,表示 的是已完成区域。如下图
用dest表示第一个0元素所在位置的前一个位置,dest初始化为-1,是因为不清楚0元素的位置
cur表示当前非0元素的下标,cur要遍历数组,所以初始化为0
不管如何,在cur找到非0元素时,[dest+1,cur-1]这一范围下标对应的一定是0元素,cur的目的就是寻找非0元素,找到之后,就和dest指向的0元素进行交换。
如果是暴力解法,第二层循环不可避免的会重复遍历。
双指针,cur指针就相当于暴力解法的第一层循环,dest指针相当于第二层,但是没有进行重复遍历(不会回退)
代码实现:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for(int dest=-1,cur=0;cur<nums.size();cur++)
{
if(nums[cur])
{
swap(nums[++dest],nums[cur]);
}
}
}
};
1089. 复写零
题目要求:
思路:遍历数组,遇到0,先将0元素后的元素向后移动一位,再进行复写0,
移动时,需要从后向前,但是最后一个移动的元素的数组最后一个元素的前一个。
并且,在移动前,需要对寻找到的0元素下标进行判断,如果已经是最后一个元素,或者倒数第二个元素,将最后一个元素置为0后,复写0就结束了。
如果不是上述情况,才需要进行先移动,后复写的操作,如下图。
优化思路:双指针
0元素需要被复写,只要复写一次,原数组末尾的元素就需要去除。用cur指针遍历数组,找出原数组经过复写后的最后一个元素的下标。dest指针根据cur指针指向的元素不断++,非0元素+1,0元素+2,最后表示的是复写后新数组最后一个元素的下标。
如果0是数组中的最后一个元素,也即cur指向了0元素,此时加上该元素,数组已达上限,但是又需要复写,dest会出现越界的情况。
代码实现
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int cur=0,dest=-1;
while(cur<arr.size())
{
if(arr[cur]==0) dest+=2;
else dest+=1;
if(dest>=arr.size()-1) break;
cur++;
}
if(dest==arr.size())
{
arr[arr.size()-1]=0;
dest-=2;
cur-=1;
}
while(cur>=0)
{
if(arr[cur]==0)
{
arr[dest]=0;
arr[dest-1]=0;
dest-=2;
cur--;
}
else{
arr[dest]=arr[cur];
cur--;
dest--;
}
}
}
};
202. 快乐数
题目要求:
解题思路:不断对一个数每个位置数字求平方和的过程中,一定会出现两次相同的结果,也即出现循环,判断循环出现的数是否为1,即可判断是否为快乐数字。
代码实现:
class Solution {
public:
int seq(int n)
{
int sum=0;
while(n)
{
int r=n%10;
sum+=r*r;
n/=10;
}
return sum;
}
bool isHappy(int n) {
int slow=n,fast=seq(n);
while(slow!=fast)//可能是为1,也可能是不为1,但二者终究会相同
{
slow=seq(slow);
fast=seq(seq(fast));
}
return slow==1;
}
};
11. 盛最多水的容器
题目要求:
解题思路:两层循环,比较每一种结果,得出最大值。O()的时间复杂度,会超时。如下
优化思路:对撞指针,O(n)的时间复杂度
容积等于长乘以宽,如果先让长度最大,那么在长度减小的过程中,容积要想更大,只能寻找更大的高度。在长度固定下,高度较低的下标开始移动,每一次移动,动要重新判断出谁更高谁更低,并且继续让较低的人移动一个坐标,每次移动都选取更大的容积。
代码实现:
class Solution {
public:
int maxArea(vector<int>& height) {
int less_hight=0;//记录较小的高度
int maxarea=0;//记录容积
for(int l=0,r=height.size()-1;l<r;)//对撞指针
{
if(height[l]>height[r])//选择高度较低的指针进行移动
{
less_hight=height[r];
maxarea=max(maxarea,(r-l)*less_hight);//先记录当前较大容积
r--;//再移动
}
else
{
less_hight=height[l];
maxarea=max(maxarea,(r-l)*less_hight);
l++;
}
}
return maxarea;
}
};
611. 有效三角形的个数
题目要求:
思路:三层循环,每层循环确定一条边,测试所有的可能,符合条件的让个数=1。会超时,如下图。
优化思路:1.最小的两条边只要大于第三边,就一定构成三角形。2.对撞指针,利用一层for循环固定最大的一边,一个指针固定第二大的一边,另一个指针从最小的一个数开始寻找符合条件的数。
代码实现:
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());//升序排序
int size=0;//统计个数
for(int i=nums.size()-1;i>=2;i--)//固定最大的一边
{
int r=i-1;//第二大的一边
int l=0;//最小的一边
while(l<r)//对撞指针固定剩余较小两边
{
if((nums[l]+nums[r])>nums[i])
{
//最小的一边都符合条件,右侧的大于等于它的一定也满足
size+=(r-l);
r--;
}
else l++;//不满足需要更大的一边
}
}
return size;
}
};