【C++算法题】8道题带你熟悉利用双指针解法

📚 博主的专栏

  🐧 Linux   |   🖥️ C++   |   📊 数据结构  | 💡C++ 算法 | 🅒 C 语言  | 🌐 计算机网络

目录

·283. 移动零 - 力扣(LeetCode)--- 数组划分(数组分块)使用双指针算法

1089. 复写零 - 力扣(LeetCode)--  使用双指针算法

202. 快乐数 - 力扣(LeetCode)

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

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

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

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

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


 

·283. 移动零 - 力扣(LeetCode)--- 数组划分(数组分块)使用双指针算法

题目解析:

特点:给一个数组,给定一个规则,需要将数组划分多个区间

算法原理:

规则:将非0元素移到左边,0移到右边 ---> 两个区间 

在数组中,利用数组下标,充当指针。

这个算法实际上是快排算法中,最核心的一步,数据划分。

两个指针的作用(dest,cur):

cur:从左往右扫描数组,遍历数组

dest:已处理的区间,非0元素的最后一个位置,用作分割线(左边是非0,右边0)

三个区间:[0,dest] (非0),[dest + 1,cur - 1](0),[cur,n - 1](未处理)

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

初始状态:dest = -1,cur = 0

1.当cur 遇到 0 ,则++cur;

2.当cur 遇到非0,dest++;

3.swap(dest + 1,cur);

4.dest++ ,cur++;

交换一次后:

第二次:

第三次:

 代码编写:

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]);
        }
    }
};

1089. 复写零 - 力扣(LeetCode)--  使用双指针算法

题目解析:

给一个数组,将数组中的0都多写一次,其余元素就不管,就直接写一遍

算法原理:

双指针算法,先根据“异地操作”,然后优化成双指针下的“就地"操作

异地操作

  • 1.定义两个指针一个cur指向原始数组的第一个元素,一个dest指向新数组的第一个元素
  • 2.cur非0,dest抄写,cur++,dest++
  • 3.cur为0,dest复写,cur++,dest++,当dest到数组size的时候退出。

就地操作:

从左向右的方法不可取,会导致非0值被0覆盖

删除数组中值为val的元素,这道题就能根据异地操作,直接在原数组修改成功

从右向左:让复写操作从左向右进行

1.cur先找到最后一个需要“抄写”的数,dest找到最后一个要被复写的"0"

2.从右向左完成复写操作

如何找到最后一个需要复写得数?--> 双指针

1.cur指向第一个元素,dest指向-1,先判断 cur 位置的值是否是需要复写的0

2.决定dest向后移动一步或者两步(cur 位置为 0 , dest+=2,否则 dest++)

3.判断dest是否已经到结束位置,到结束位置就退出(dest == n 就需要处理边界情况了)

4.dest没有到结束位置,cur++

这道题的特例:

移动时,dest在判断出当前位置为需要复写的0之后,向后移动两步的时候,直接越界了,cur的位置一定是正确的。

1.5. 因此还需要再dest要移动时,处理边界情况,单独处理这种情况

arr[ n - 1 ]= 0, cur--,dest -= 2;

代码编写:

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        // 1.先找到最后一个数
        int cur = 0, dest = -1;
        int len = arr.size();
        while(cur < len)
        {
            if(arr[cur]) dest++;
            else dest +=2;
            if(dest >= len-1) break;
            cur++;
        }
        //2.处理边界情况
        if(dest == len)
        {
            arr[len - 1] = 0;
            cur--;
            dest-=2;
        }

        //3.从右向左进行复写操作
        while(cur >= 0)
        {
            if(arr[cur]) arr[dest--] = arr[cur--];
            else //当cur位置就是0的时候
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            } 
        }
    }
};

202. 快乐数 - 力扣(LeetCode)

1.题目解析:

根据示例:

数在变化的时候,总会有环,在之前有讲解过环形链表:

链表oj--数据结构--c语言--环形链表(1)(2)(以及笔试题slow一次走1、n步,fast一次走2、3、m步一定会相遇吗讲解)-优快云博客

快乐数:环里的数都是1

非快乐数:环里的数无1

算法原理:快慢双指针

  1. 定义快慢指针,这里的意思是让快慢指针表示数

  1. 慢指针每次向后移动1步,快指针每次向后移动2步,只要两个指针进入环,他们一定在某个时间相遇。
  2. 判断相遇时候的值是否是1。

但是这道题,如果题目没有告诉会循环,我们还有可能会想到所有的数都是不重复的数,无限往下永远无法变成1。

证明,一定会有循环:鸽巢原理(抽屉原理)

        有n个巢穴,n+1个鸽子,至少有一个巢穴里面的鸽子数量>1

题目给出的数字范围是

  • 1 <= n <= 231 - 1,那么我们直接扩更大的范围,每一位都是9,那么表示的最大数就是810,那么就有1-810能不重复的值,假如现在有x值,在经历810次经历所有的不同的数之后,在第811次,这个数一定属于1-810,造成循环。

代码编写:

class Solution {
public:
    int bitSum(int n)
    {
        //返回n上数每一位的平方和
        int sum = 0;
        while(n)
        {
            int m = n%10;//1
            sum+=m*m;//81+1
            n/=10;//1 0
        }
        return sum; //82
    }


    bool isHappy(int n) {
        int slow = n, fast = bitSum(n);

        while(slow != fast){
           slow = bitSum(slow);
           fast = bitSum(bitSum(fast));//fast走两步
        }

        return slow == 1;
    }
};

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

题目解析:木桶效应

解法一,暴力解法:两层for循环,直接找盛水最多的容器,但是会超时O(n^2)

解法二:

利用单调性,使用双指针来解决问题,遍历数组一遍就能解决问题O(n)

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

题目解析:

省略

算法原理:

数学知识:给我们三个数,是否能构成三角形

获得三个数的大小顺序,仅需判断较小的两个数之和大于最大边

优化:先对整个数组排序

解法一:暴力枚举O(N^3)

伪代码:

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^2)

1.先排序然后,固定最大数(最右边) N次

2.在最大的数的左区间内,使用双指针算法,快速统计出符合要求的三元组的个数  N次

代码编写: 

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 1.优化:
        sort(nums.begin(), nums.end());
        // 2.利用双指针解决问题
        int len = nums.size();

        // int len = nums.size() - 1;
        int k = 0;
        // int left = 0, right = len - 1;
        for(int i = len - 1; i >= 2; --i)
        {
           int left = 0, right = i - 1;
           while(left < right)
           {
            if(nums[left] + nums[right] > nums[i])
            {
                k += right - left;
                --right;
            }
            else
            {
                ++left;
            }
           }
        }

        // while (len) {
        //     while (left < right) {
        //         if (nums[left] + nums[right] > nums[len]) {
        //             k += right - left;
        //             --right;
        //         } else {
        //             ++left;
        //         }
        //     }
        //     --len;
        //     left = 0;
        //     right = len - 1;
        // }
        return k;
    }
};

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

1.题目解析:

一个升序排序的数组,找出其中任意两个数的和能等于target就返回

2.算法原理

双指针

1.right先从末尾开始找,找到第一个比target小的数,left指向首位元素

2.left + right如果等于target就直接返回这两个数,若小于,left++,若大于,right--。

3.代码编写

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int right = price.size() - 1;
        int left = 0;
        // 1.找到刚好比target小的第一个数
        while (right) {
            if (price[right] < target)
                break;
            --right;
        }
        // 2.开始
        vector<int> res;
        while (left < right) {
            if (price[left] + price[right] == target) {
                res.push_back(price[left]);
                res.push_back(price[right]);
                break;
            } else if (price[left] + price[right] < target) {
                ++left;
            } else {
                --right;
            }
        }
        return res;
    }
};

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

题目解析:

三个下标不相同的数相加 = 0,返回这三个数的数组集,不能有相同的三个数

算法原理

解法一:排序 + 暴力枚举 + 去重        O(N^3)

如何更方便的去重?原本我们可能会得到多个数组中元素顺序不一样,但数值一样的多个数组,需要将他们排序再去重,这样麻烦并且消耗大,因此先排序,再进行寻找,所得到的都是数值顺序一样的数组。再使用set去重。

解法二:二分,双指针(优先选择)

处理细节问题

a.去重        b.不漏        c.避免越界

1.使用双指针,以及一个固定数i(<len - 2并且小于0),定数刚开始指向首元素,left为i+1,right为最后一个

2.left + right,若值>-i,right--;若值<-i,left++;若值=-i,则存入[i , left, right],再继续缩小区间(left++,right--)重复2步骤,直到left = right退出,找到所有情况

3.因为数组有序,在数组元素当中,相同的数都放在一起,当找到一种对应的数组后,利用双指针移动跳过重复元素来去重。

下一个i仍然是-4,因此直接跳过 :

越界情况

代码编写

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int len = nums.size();
        vector<vector<int>> ret;
        for (int i = 0; i < len - 2;) {

            if (nums[i] > 0) {
                break;
            }
            int left = i + 1;
            int right = len - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0) {
                    ++left;
                } else if (sum > 0) {
                    --right;
                } else {
                    ret.push_back({nums[i], nums[left], nums[right]});
                    ++left;
                    --right;
                    //去重l r
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
            }
            //去重i
            i++;
            while(i < len - 2 && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
};

这里最巧妙的就是去掉了for循环中的i++,就被坑在这里了

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

题目解析

实际上题目就类似于三数之和

算法原理

解法一:排序 + 暴力枚举 + set去重        O(N^4)

解法二:排序 + 双指针

1.依次固定a;

2.在a后面的区间找到三个数,计算有这三数之和,等于target - a

3.在后面的区间中即为求三数之和等于target - a,这里直接利用上一题三数之和的思路就可以

4.这道题与上一题不一样的地方在于,有数据溢出的风险。因此使用long long(使用的时候不仅在定义的时候用long long,后面任意一个数需要强转为long long类型)

代码编写

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        //1.先排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> res;
        //2.第一层for循环,固定的第一个数
        for (int i = 0; i < n - 3;) {
             //target - a
            //3.第二层for循环,固定第二个数
            for (int k = i + 1; k < n - 2;) {
                long long sub = (long long)target - nums[i] - nums[k];//target - a - b
                int left = k + 1;
                int right = n - 1;
                while (left < right) {
                    int sum = nums[left] + nums[right];
                    if (sum < sub) {
                        ++left;
                    } else if (sum > sub) {
                        --right;
                    } else {
                        res.push_back(
                            {nums[i], nums[k], nums[left], nums[right]});
                        ++left;
                        --right;
                        while (left < right && nums[left] == nums[left - 1])
                            ++left;
                        while (left < right && nums[right] == nums[right + 1])
                            --right;
                    }
                }
                ++k;
                while (k < n - 2 && nums[k] == nums[k - 1])
                    ++k;
            }
            ++i;
            while (i < n - 3 && nums[i] == nums[i - 1])
                ++i;
        }
        return res;
    }
};

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

   

         在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。               

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值