优选算法专题1:双指针

双指针

目录

双指针

试题1:移动零

算法原理

代码编写

试题2:复写零

算法原理

代码编写

试题3:快乐数

算法原理

编写代码

试题4:盛最多水的容器

算法原理

编写代码

试题5:有效三角形的个数

算法原理

代码编写

试题6:和为s的两个数

算法原理

代码编写

试题7:三数之和

算法原理

代码编写

试题8:四数之和

算法原理

代码编写


试题1:移动零

算法原理

数组划分(数组分块)

提供一个数组,给定一个规则,在这个规则下将数组划分为若干个区间

使用双指针算法(注:这里的指针是数组的下标)

两个指针的作用

**cur(current):**从左往右扫描数组,遍历数组

**dest(destination):**已处理的区间内,非零元素的最后一个位置

三个区间

0,dest:非0

dest + 1,cur - 1:0

cur,n - 1:待处理

cur从前往后遍历的过程中

遇到0元素:cur++

遇到非0元素:dest++,swap(dest,cur),cur++

**补充:**双指针算法(数据划分)是快排里面最核心的一步

代码编写

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

试题2:复写零

算法原理

双指针算法

根据异地操作,优化成双指针下的就地操作

异地操作

就地操作

Step1:先找到最后一个复写的数

使用双指针算法,先判断cur位置的值,决定dest向后移动一步或者两步

再判断dest是否已经走到结束为止,最后cur++,直到cur遍历整个数组

Step1.5:处理边界情况

将n-1下标的元素改为0,cur -= 1,dest -= 2

Step2:从后向前完成复写操作

总结:画图模拟过程,多次尝试,将大题化为多个小题

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    void duplicateZeros(vector<int>& arr) 
    {
        int cur = 0;
        int dest = -1;
        while(cur < arr.size())
        {
            if(arr[cur] == 0)
            {
                dest += 2;
            }
            else
            {
                dest++;
            }
            if(dest >= arr.size() - 1)
            {
                break;
            }
            cur++;
        }
        if(dest == arr.size())
        {
            dest -= 2;
            cur--;
            arr[arr.size() - 1] = 0;
        }
        while(cur >= 0)
        {
            if(arr[cur] == 0)
            {
                arr[dest] = 0;
                arr[dest - 1] = 0;
                dest -= 2;
            }
            else
            {
                arr[dest] = arr[cur];
                dest--;
            }
            cur--;
        }
    } 
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    void duplicateZeros(vector<int>& arr) 
    {
        //#1:先找到最后一个数
        int cur = 0,dest = -1,n = arr.size();
        while(cur < n)
        {
            if(arr[cur])
            {
                dest++;
            }
            else
            {
                dest += 2;
            }
            if(dest >= n - 1)
            {
                break;
            }
            cur++;
        }
        //#2:处理边界情况
        if(dest == n)
        {
            arr[n - 1] = 0;
            cur--;
            dest -= 2;
        } 
        //#3:从后向前完成复写操作
        while(cur >= 0)
        {
            if(arr[cur])
            {
                arr[dest--] = arr[cur--];
            }
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    } 
};

试题3:快乐数

算法原理

快慢双指针

定义快慢指针

慢指针每次向后移动一步

快指针每次向后移动两步

判断相遇时候的值即可

**补充:**鸽巢原理(抽屉原理)

n个巢穴,n+1个鸽子:至少有一个巢穴里的鸽子数大于1

2.1 * 10^9 -> 1,810(巢穴)

9999999999 -> 9^2 * 10 = 810

随机数x经过811次操作,必定会有一个值落在1,810,所以成环是必定的

编写代码

  • 个人版本
cpp 复制代码
class Solution {
public:

    int happydata(int n)
    {
        int sum = 0;
        while(n)
        {
            int x = n % 10;
            sum += x * x;
            n = n / 10;
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        int slow = n;
        int fast = n;
        do
        {
            slow = happydata(slow);
            fast = happydata(happydata(fast));
        }while(slow != fast);

        if(slow == 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int bitSum(int n)
    {
        int sum = 0;
        while(n)
        {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n)
    {
        int slow = n,fast = bitSum(n);
        while(slow != fast)
        {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
        return slow == 1;
    }
};

试题4:盛最多水的容器

算法原理

解法一:暴力枚举

两层for循环(超时)

解法二:双指针法

编写代码

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
        int maxv = 0;
        int left = 0;
        int right = height.size() - 1;
        while(left < right)
        {
            int v;
            if(height[left] < height[right])
            {
                v = (right - left) * height[left];
                if(v > maxv)
                {
                    maxv = v;
                }
                left++;
            }
            else
            {
                v = (right - left) * height[right];
                if(v > maxv)
                {
                    maxv = v;
                }
                right--;
            }
        }
        return maxv;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
       int left = 0,right = height.size() - 1,ret = 0;
       while(left < right)
       {
            int v = min(height[left],height[right]) * (right - left);
            ret = max(ret,v);
            if(height[left] < height[right])
            {
                left++;
            }
            else
            {
                right--;
            }
       }
       return ret;
    }
};

试题5:有效三角形的个数

算法原理

解法一:暴力枚举

时间复杂度:O(N^3)

直接枚举:3 * N^3

排序后枚举:N * logN + N^3

cpp 复制代码
for(i = 0;i < n;i++)
{
    for(j = i + 1;j < n;j++)
    {
        for(k = j + 1;k < n;k++)
        {
            check(i,j,k);
        }
    }
}

解法二:利用单调性,使用双指针算法

先固定最大的数O(N)

在最大的数的左区间内,使用双指针算法O(N)

快速统计出符合要求的三元组的个数

时间复杂度:O(N^2)

**补充:**给定三个数,判断是否能够构成三角形

已知三个数的大小顺序,判断较小的两个数之和大于第三个数,就能构成三角形

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int triangleNumber(vector<int>& nums) 
    {
        int count = 0;
        sort(nums.begin(),nums.end());
        for(int i = nums.size() - 1;i >= 2;i--)
        {
            int left = 0;
            int right = i - 1;
            int c = nums[i];
            while(left < right)
            {
                if(nums[left] + nums[right] > c)
                {
                    count += right - left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
        }
        return count;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int triangleNumber(vector<int>& nums) 
    {
        //#1:优化
        sort(nums.begin(),nums.end());

        //#2:利用双指针解决问题
        int ret = 0;
        int n = nums.size();
        for(int i = n - 1;i >= 2;i--)//先固定最大的数
        {
            //利用双指针快速统计符合要求的三元组个数
            int left = 0;
            int right = i - 1;
            while(left < right)
            {
                if(nums[left] + nums[right] > nums[i])
                {
                    ret += right - left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
        }
        return ret;
    }
};

试题6:和为s的两个数

算法原理

解法一:暴力枚举

cpp 复制代码
for(i = 0;i < n;i++)
{
    for(j = i + i;j < n;j++)
    {
        check(num[i] + nums[j] == t);
    }
}

解法二:利用单调性,使用双指针算法解决问题

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    vector<int> twoSum(vector<int>& price, int target) 
    {
        int left = 0;
        int 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};
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    vector<int> twoSum(vector<int>& price, int target) 
    {
        int left = 0; 
        int right = price.size() - 1;
        while(right > left)
        {
            int sum = price[left] + price[right];
            if(sum > target)
            {
                right--;
            }
            else if(sum < target)
            {
                left++;
            }
            else
            {
                vector<int> ret = {price[left],price[right]};
                return ret;
            }
        }
        return {-1,-1};
    }
};

试题7:三数之和

算法原理

解法一:暴力枚举

排序+枚举+利用unordered_set去重

时间复杂度:O(N^3)

解法二:排序+双指针(有序数组)

时间复杂度:O(N^2)

排序,固定一个数为a(a <= 0)

在该数后面的区间内,利用双指针算法

快速找到两个数的和为-a即可

处理细节问题:

去重

找到一种结果之后,left和right指针需要跳过重复元素

当使用完一次双指针算法之后,i也需要跳过重复元素

避免越界

不漏

找到一种结果之后,不要停,缩小区间,继续寻找

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
       vector<vector<int>> ret;
       sort(nums.begin(),nums.end());
       for(int i = 0;i < nums.size();i++)
       {
            if(nums[i] > 0)
            {
                break;
            }
            if(i > 0 && nums[i] == nums[i - 1])
            {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while(left < right)
            {
                if(nums[left] + nums[right] < -nums[i])
                {
                    left++;
                }
                else if(nums[left] + nums[right] > -nums[i])
                {
                    right--;
                }
                else
                {
                    ret.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--;
                }
            }
       }
       return ret;
    }
};

标准版本

cpp 复制代码
class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        vector<vector<int>> ret;

        //#1:排序
        sort(nums.begin(),nums.end());

        //#2:利用双指针解决问题
        int n = nums.size();
        for(int i = 0;i < n;)//固定数a
        {
            if(nums[i] > 0)//小优化
            {
                break;
            }
            int left = i + 1;
            int right = n - 1;
            int target = -nums[i];
            while(left < right)
            {
                int sum = nums[left] + nums[right];
                if(sum > target)
                {
                    right--;
                }
                else if(sum < target)
                {
                    left++;
                }
                else
                {
                    ret.push_back({nums[i],nums[left],nums[right]});
                    left++;
                    right--;
                    //去重操作 left 和 right
                    while(left < right && nums[left] == nums[left - 1])
                    {
                        left++;
                    }
                    while(left < right && nums[right] == nums[right + 1])
                    {
                        right--;
                    }
                }
            }
            //去重操作 i
            i++;
            while(i < n && nums[i] == nums[i - 1])
            {
                i++;
            }
        }
        return ret;
    }
};

试题8:四数之和

算法原理

解法一:暴力枚举

排序 + 暴力枚举 + 利用set去重

解法二:双指针法

排序 + 双指针

依次固定一个数a

在a后面的区间,利用三数之和找到三个数

使这三个数的和等于target - a即可

依次固定一个数b

在b后面的区间,利用双指针找到两个数

使这两个数的和等于target - a - b即可

处理细节问题:去重、不漏

时间复杂度:O(N^3)

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        vector<vector<int>> ret;
        sort(nums.begin(),nums.end());
        int n = nums.size();
        for(int a = 0;a < n;)
        {
            for(int b = a + 1;b < n;)
            {
                int left = b + 1;
                int right = n - 1;
                long long new_target = (long long)target - nums[a] - nums[b];
                while(left < right)
                {
                    long long sum = (long long)nums[left] + nums[right];
                    if(sum < new_target)
                    {
                        left++;
                    }
                    else if(sum > new_target)
                    {
                        right--;
                    }
                    else
                    {
                        ret.push_back({nums[a],nums[b],nums[left],nums[right]});
                        left++;
                        right--;
                        while(left < right && nums[left] == nums[left - 1])
                        {
                            left++;
                        }
                        while(left < right && nums[right] == nums[right + 1])
                        {
                            right--;
                        }
                    }
                }
                b++;
                while(b < n && nums[b] == nums[b - 1])
                {
                    b++;
                }
            }
            a++;
            while(a < n && nums[a] == nums[a - 1])
            {
                a++;
            }
        }
        return ret;
    }
};
  • 标准版本
cpp 复制代码
class 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;
                int 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;
    }
};
相关推荐
京东云开发者几秒前
一键调用!京东云率先上线MiniMax M3
算法
代码中介商1 分钟前
C++ 智能指针完全指南(二):shared_ptr 深度详解
开发语言·c++
智者知已应修善业2 分钟前
【proteus 74160实现模60计数器模41计数器】2024-5-27
驱动开发·经验分享·笔记·硬件架构·proteus·硬件工程
papership8 分钟前
入门级-数据结构-2、简单树:二叉树的遍历(前序、中序、后序)
数据结构·算法
WWW65269 分钟前
代码随想录 打卡第五十四天
数据结构·c++·算法
happymaker06269 分钟前
LeetCodeHot100——15.三数之和
数据结构·算法
墨白曦煜11 分钟前
算法实战笔记:空间换时间的黑魔法——单调栈全景解析(十一)
java·笔记·算法
大模型最新论文13 分钟前
小红书提出 RedKnot:分头处理 kv 缓存,延时降低 60%效果还提升
算法
问心无愧051314 分钟前
ctf show web入门157 158
前端·笔记
随意起个昵称18 分钟前
线性dp-LIS题目6(友好城市,二分优化)
算法·动态规划