C++算法(1)双指针

在开启这个新的专题之前,仍然照常写一些前言类的东西吧,这个专题本应该是我在完成学习C++这个专题之后再开始的,然而比赛将至,我目前觉得C++学习这个专题的内容暂时够用,所以不得不先放下C++基础的学习,转而学习算法,当然我还是觉得C++以及后续Linux系统的学习是我学习的主路线,所以这部分是必然不会放下的。那么到了算法这边,我觉得是一种新且不同的学习方法和内容,这可能对学习者个人的逻辑思维方面要求较高,总而言之是一种新的挑战,那么当然也希望在此过程中可以得到收获和提升。

1.移动零

283. 移动零 - 力扣(LeetCode)

这其实是一个数组划分的问题:

快速排序也是类似的思想,只不过快速排序要划分的是小于等于某个值和大于等于某个值。

题目的要求是把所有数组划分为0和非0的两块区域,为了使以上三个区间保持这个要求,那么先将cur放在0处,将dest放在-1处,当cur遇到非0数时dest++,将此非0数和dest所指位置调换,再cur++;当cur遇到0时直接跳过,cur++。

代码如下:

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        for(int dest=-1,cur=0;cur<nums.size();cur++)
        {
            if(nums[cur]!=0)
            {
               swap(nums[++dest],nums[cur]);
            }
        }
    }
};

2.复写零

1089. 复写零 - 力扣(LeetCode)

/

如果这道题直接从前往后就地修改的话,逻辑上十分简单,但这么解答是错的,因为复写的数必然会覆盖没被复写的数。这个时候可以尝试从后往前去复写,但难点在于找到最后一个复写的数。找到最后一个复写的数可以通过模拟一遍从前向后复写来实现,其中一个细节是在cur不断向后扫描的过程中注意判断dest是否已经到达终点,也要搞明白终点的判断 :最后一个复写有可能是n-1处(复写非0数),也有可能是n处(复写0),如果满足到终点的条件则及时退出循环,cur不再++,保留此时的cur值,这就是最后一个复写的位置。还有一个细节要注意处理,若最后一个复写的数是0,则dest所指处必然是n,如果从此处开始从后向前复写,则数组越界访问,为了处理这个问题,则要将数组最后一个位置赋为0,将dest向后退后两步,cur向后退后一步,此后即可正常复写。

代码如下:

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) 
    {
      int dest=-1;
      int cur=0;
      int n=arr.size();
      while(cur<n)
      {
        if(arr[cur]==0)
        {
            dest++;
        }
        dest++;
        if(dest>=n-1)//看是否到尽头了,如果到了就不用再cur++了
        {
            break;
        }
        cur++;
      }    
        if(dest>=n)
        {
            arr[n-1]=0;
            dest-=2;
            cur--;
        } 
        while(cur>=0)
        {
            if(arr[cur]==0)
            {
                arr[dest--]=arr[cur];
            }
            arr[dest--]=arr[cur];
            cur--;
        }
    }
};

3.快乐数

202. 快乐数 - 力扣(LeetCode)

快乐数和非快乐数的演变其实都可以看成形成环的问题:

不形成环的情况是不存在的,这可以用鸽巢定理来证明:

题目的条件中有1<=n<=,取一个大于的数9999999999,那么演变过程中最大的数为,那么经过最多811次之后,一定会出现重复的数

只需要判断快慢指针入环后的值是1还是非1即可区分快乐数和非快乐数。

代码如下:

cpp 复制代码
class Solution {
public:
    bool isHappy(int n) 
    {
        int slow=n;
        int fast=func(n);
        while(slow!=fast)
        {
            slow=func(slow);
            fast=func(func(fast));
        }        
        if(slow==1)
        {
            return true;
        }
        return false;
    }
    int func(int x)
    {
        int n=0;
        while(x)
        {
            n+=(x%10)*(x%10);
            x/=10;
        }
        return n;
    }
};

4.盛最多水的容器

11. 盛最多水的容器 - 力扣(LeetCode)

由于木桶原理,在宽度确定的情况下,决定盛水量的是较低的高度,先从两端看,左右已确定一个容量,则去除低的那端,因为低的那端再进行枚举,若遇到高的端,仍会取低的端;若遇到更低的端,则取更低的端。再加上本身宽度一定减小,枚举的结果都不会优于当前,故保留高端,低段向内移动。

代码如下:

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) 
    {
        int left=0,right=height.size()-1;
        int Area=0;
        while(left<right)
        {
            int sum=min(height[left],height[right])*(right-left);
            Area=max(Area,sum);
            if(height[left]<height[right])
            {
                left++;
            }
            else
            {
                right--;
            }
        }
        return Area;
    }
};

5.查找总价格为目标值的两个商品

从这道题开始,接下来的几道题基本都是同样的思想,但细节处理方面有不同,逐层递进。

LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

如果是暴力枚举,代码逻辑并不复杂,就是时间复杂度较高,用双指针的方法时间复杂度会低很多。首先将数组按升序排好序,数组头和尾分别作为left指针和right指针开始的地方,先判断此时left所指位置和right所指位置相加是否等于目标值,若大于目标值,则right--(因为根据数组的单调性,除left所指位置的值以外,其他位置的值与right所指位置的值相加必定大于目标值),若小于目标值,则left++(因为根据数组的单调性,除right所指位置的值以外,其他位置的值与left所指位置的值相加必定小于目标值),若等于目标值,则返回此时两个指针所指的值。

代码如下:

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) 
    {
        int right=price.size()-1;
        int left=0;
        while(left<right)
        {
            if(price[right]+price[left]<target)
            {
                left++;
            }
            else if(price[right]+price[left]>target)
            {
                right--;
            }
            else
            return {price[left],price[right]};
        }
        return {-1,-1};
    }
};

6.有效三角形的个数

611. 有效三角形的个数 - 力扣(LeetCode)

这道题用暴力枚举需要三个循环:

for(i=0;i<n;i++)

for(j=i+1;j<n;j++)

for(k=j+1;k<n;k++)

check(i,j,k);

用双指针的方法,则主要思想和上一题差不多,但这道题是需要三个数满足要求,则需要在固定一个数的基础上,其他两个数走上一道题的逻辑。

先排好序,固定最大的数(即最右边的数)c,除去最大的数之外,余下的数组的左右两端设为指针a、b,若a+b>c,那么中间的数加上b一定大于c,则b可以去掉,即端点向内移,有效三角形的个数由下标相减得到,若a+b<=c,那么中间的数加上a一定小于等于c,则a可以去掉,即端点向内移,有效三角形不算。再固定次大的数...形成循环。

代码如下:

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        int c=nums.size()-1;
        int sum=0;
        while(c>=2)
        {
            int left=0;
            int right=c-1;
            while(left<right)
            {
                if(nums[left]+nums[right]>nums[c])
                {
                    sum+=right-left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
            c--;
        }
        return sum;
    }
};

7.三数之和

15. 三数之和 - 力扣(LeetCode)

这道题也是需要三个数满足条件,但与上一题不同的是答案中不能有重复的三元组,这要求一些额外的细节处理,包括越界访问、避免漏查、避免找重复的。

代码如下:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums)
    {
        vector<vector<int>> ret;
        int k=nums.size()-1;
        sort(nums.begin(),nums.end());
        while(k>=2&&nums[k]>=0)
        {
            int i=0;
            int j=k-1;
            while(i<j)
            {
                if(nums[i]+nums[j]+nums[k]<0)
                {
                    i++;
                }
                else if(nums[i]+nums[j]+nums[k]>0)
                {
                    j--;
                }
                else
                {
                    ret.push_back({nums[i],nums[j],nums[k]});
                    i++;//防止漏查
                    while(i<j&&nums[i]==nums[i-1])//防止重复输出及越界访问
                    {
                        i++;
                    }
                    j--;//防止漏查
                    while(i<j&&nums[j]==nums[j+1])//防止重复输出及越界访问
                    {
                        j--;
                    }
                }
            }
            k--;
            while(k>=2&&nums[k]==nums[k+1])//处理越界访问及重复检查
            {
                k--;
            }
        }  
        return ret; 
    }
};

8.四数之和

18. 四数之和 - 力扣(LeetCode)

这道题需要四个数满足条件,无非是在三个数之和的基础上再加一个循环,基本原理是差不多的,需要根据题目的要求做一些修改。另外还有一个要注意的地方,这道题的测试范围如果很大,一些写了多个数相加的语句可能超出整形的储存范围,可以改为用long long类型储存。

代码如下:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        vector<vector<int>> ret;
        int l=nums.size()-1;
        sort(nums.begin(),nums.end());
        while(l>=3)
        {
            int k=l-1;
            while(k>=2)
            {
                int i=0;
                int j=k-1;
                while(i<j)
                {
                    long long aim=(long long)target-nums[k]-nums[l];
                    int sum=nums[i]+nums[j];
                    if(sum<aim)
                    {
                        i++;
                    }
                    else if(sum>aim)
                    {
                        j--;
                    }
                    else
                    {
                        ret.push_back({nums[i],nums[j],nums[k],nums[l]});
                        i++;//防止漏查
                        while(i<j&&nums[i]==nums[i-1])//防止重复输出及越界访问
                        {
                            i++;
                        }
                        j--;//防止漏查
                        while(i<j&&nums[j]==nums[j+1])//防止重复输出及越界访问
                        {
                            j--;
                        }
                    }
                }
                k--;
                while(k>=2&&nums[k]==nums[k+1])//处理越界访问及重复检查
                {
                    k--;
                }
            }
            l--;
            while(l>=3&&nums[l]==nums[l+1])//处理越界访问及重复检查
            {
                l--;
            }  
        }
        return ret; 
    }
};
相关推荐
淀粉肠kk3 小时前
C++11列表初始化:{}的革命性进化
c++
不绝1913 小时前
C#进阶:预处理指令/反射,Gettype,Typeof/关键类
开发语言·c#
无小道3 小时前
Qt-qrc机制简单介绍
开发语言·qt
zhooyu3 小时前
C++和OpenGL手搓3D游戏编程(20160207进展和效果)
开发语言·c++·游戏·3d·opengl
HAPPY酷3 小时前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python
大鹏说大话3 小时前
告别 MSBuild 脚本混乱:用 C# 和 Nuke 构建清晰、可维护的现代化构建系统
开发语言·c#
Mr_sun.4 小时前
Day09——入退管理-入住-2
android·java·开发语言
MAGICIAN...4 小时前
【java-软件设计原则】
java·开发语言
gpfyyds6664 小时前
Python代码练习
开发语言·python