【LeetCode热题100】双指针

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

题目分析 :对于数组分块/数组划分的问题,我们可以使用双指针算法进行求解(这道题的"指针"是数组下标)。我们使用两个"指针"dst和cur,将数组划分为三个区间:[0,dst]已处理过且不为0的区间、[dst+1,cur-1]已处理过且为0的区间、[cur,n-1]待处理的区间。两个指针的作用:cur--从左往右扫描数组,遍历数组;dst--已处理的区间内,非零元素的最后一个位置。cur向后依次遍历,可能会有两种情况:1.cur指向的元素为0,cur++ 2.cur指向的元素不为0,swap(nums[dst+1],nums[cur]),cur++,dst++。由于刚开始时没有已处理的区间,所以dst=-1,cur=0。

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) 
    {
        //1.找到要复写的最后一个数
        int cur = 0,dst = -1;
        while(cur < arr.size())
        {
            if(arr[cur] == 0) dst+=2;
            else dst++;
            if(dst >= arr.size()-1)
            {
                break;
            }
            cur++;
        }
        //处理一下边界情况:dst加了两下,加过了
        if(dst == arr.size())
        {
            arr[dst-1]=0 ;
            dst-=2;
            cur--;
        }
        //2.从后向前复写
        while(cur >= 0)
        {
            if(arr[cur] == 0)
            {
                arr[dst]=arr[dst-1]=0;
                dst -= 2;
                cur--;
            }
            else
            {
                arr[dst]=arr[cur];
                dst--;
                cur--;
            }
        }
    }
};

题目分析 :这道题是对数组元素进行操作,可以考虑双指针算法。整体思路就是先根据"异地"操作,然后优化成双指针下的"就地"操作。我们首先想到的是另外创建一个数组,进行异地操作,一个指针cur指向原数组,另一个指针dst指向新建数组,cur依次遍历每一个数,按照要求在新建数组进行复写。然而,这样的做法明显不符合"就地操作"的要求,因此,我们需要让cur和dst都指向原数组,如果它们都指向了起始位置,这样遇到复写0之后就会覆盖后面的元素,肯定会出问题。所以我们需要从后往前复写,dst需要指向数组最后一个元素,cur要指向最后一个"复写"的数,因此,我们总共有两步:1.先找到最后一个"复写"的数 2."从后向前"完成复写操作。经过多次实践,可以通过如下方法找到最后一个复写的数:1)先判断cur位置的值 2)决定dest向后移动一步或者两步 3)判断一下dest是否已经结束 4)cur++ 在开始之前,我们需要先让dst指向-1,cur指向0(因为最终dst指向最后一个要复写的位置,刚开始的时候并不知道最后一个位置在哪,因此设为-1),当这个循环退出时,cur指向了最后一个"复写"的数。此外,在找最后一个要复写的数的过程中,我们还可能因为cur最后一次指向0,dst需要加两次,跳到了arr的外面,即下标为n的位置,对于这种情况,我们需要单独处理一下,让arr[n-1]=0,然后dst回退两次,cur--即可。

cpp 复制代码
class Solution {
public:
    int bitSum(int n)
    {
        int sum = 0;
        while(n)
        {
            int lastbit = n%10;
            sum += lastbit*lastbit;
            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;
    }
};

题目分析 :给定一个正整数,按照题目给定的过程重复计算,会有两种结果:其一是这个数在某一次变成1,那么之后会一直循环1;第二种是这个数会在某个圈里无限循环,但是不是1。因此,一定会进入循环(鸽巢原理),我们可以通过判断当进入循环后,这个值是不是1来确定这个数是不是快乐数。

那么如何找到进入循环的某一个数呢?这个就需要用到快慢指针的思想:

(一)定义快慢指针

(二)慢指针每次向后移动一步,快指针每次向后移动两步

(三)判断相遇时候的值即可

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) 
    { 
        int max= 0;
        int i =0,j=height.size()-1;
        int v = 0;//体积
        while( i < j)
        {
            v = (j - i) * (height[i]<height[j]?height[i]:height[j]);
            if(v > max) max = v;
            if(height[i] < height[j]) 
                i++;
            else 
                j--;
        }

        return max;

    }
};

题目分析:第一种方法就是暴力求解,遍历每一个可能得组合,计算出它们的体积的最大值,但是这种方法是运行超时的,因此我们还要另辟蹊径。

第二种方法比较巧妙,我们先来看一个简单例子,如果数组是[6,2,5,4],那我们先计算最外面两个数的组合:6和4,宽度是3,高度是max(6,4)=4,体积=3*4=12,我们再往里面深入,如果是4和2组合,首先它们的宽度变成2(往里深入宽度必定减少),高度变成2(高度必定是≤4,),很显然,宽度必定减少,高度最多保持不变,那么4和往里面的树组合体积必定减少,因此就不用考虑4和里面任何数的组合,所以直接忽略4(两侧--6和4中较小的那一个值),数组变成[6,2,5],然后继续依照上面逻辑,先计算出最外侧两个数的体积,和上一个体积比较,最后直到整个数组被遍历完!

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

        return ret;
    }
        
};

题目分析:如果要判断的三个数是有序的(a < b < c),那么只需要判断一次即可,就是判断 a+b是否大于c,因为b+c肯定大于a,a+c也肯定大于b。

这道题有两种解法,一种是暴力解法,遍历这个数组得到三个数,依次判断是否符合,这种解法的时间复杂度很高。另外一种解法,也是利用双指针的思想,比如给出的数组是[2,3,5,5,7,8,9,12],先固定一个最大的c,也就是最后的12,然后让left指向剩下的最小的,即2,再让right指向剩下最大的,即9,判断left+right和固定最大值c=12的大小关系,那么就有两种情况:

1.left+right>c,那么right和left与right中间的任意一个组合都符合条件,不用继续遍历中间的,符合的组数为right-left,再让right--;

2.left+right<=c,那么,left和left与right中间任意一个组合都不符合条件,left++;

当left=right后,这一回合遍历结束,再让固定的最大值c向前调整一个,然后继续上述步骤即可。

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target)
    {
        vector<int> ret;
        int size = price.size();
        int left = 0;
        int right = size - 1;
        while(left < right)
        {
            if(price[left] + price[right] < target)
            {
                left++;
            }
            else if(price[left] + price[right] > target)
            {
                right--;
            }
            else
            {
                return {price[left],price[right]};//隐式类型转换
            }
        }
        //虽然我们知道这条语句不会被执行,但是为了照顾编译器,
        //需要在每条返回路径上都返回
        return {1,1};

    }
};

题目分析:和有效三角形的个数的思想非常类似,也是用到双指针的思想。刚开始的时候,左指针left指向数组最左,右指针right指向数组最右,整个数组是单调递增的,先判断left+right和target的关系,分为三种情况:

1.left+right > target,那么right和left与right之间的任意数之和都大于target,就没有必要继续遍历了,让right--;

2.left+right < target,那么left和left与right之间的任意数之和都小于target,也没有必要继续遍历了,让left++;

3.left+right > target,那么直接返回即可,走隐式类型转换。

需要注意的是,需要在循环外加一个return语句,这样编译器才不会报错,认为每条路径都会有返回值。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        int size = nums.size();
        vector<vector<int>> vv;

        for(int i = 0; i < size - 2; )
        {
            if(nums[i] > 0) break;
            int left = i + 1;
            int right = size - 1;
            while(left < right)
            {
                if(-1 * nums[i] < nums[left] + nums[right])
                {
                    right--;
                }
                else if(-1 * nums[i] > nums[left] + nums[right])
                {
                    left++;
                }
                else
                {
                    vv.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 < size && nums[i] == nums[i-1])
            {
                i++;
            }          
        }
        return vv;
    }
};

题目分析:这道题依然是有两种解法:暴力解法和优化解法,

暴力解法:先对数组进行排序,然后对数组中的每三个数进行遍历,当找到三个数的符合条件后,将这三个数的组合插入set中,这样方便去重,但是暴力解法的时间复杂度是O(N3)。

优化解法:先对数组进行排序,然后固定一个数a(从最左侧开始固定),在该数后面的区间内,利用上题"双指针"算法,快速找到这个区间内和为-a的两个数。

处理细节:

1.去重:找到一种结果之后,left和right指针要跳过重复元素,直到找到下一个不重复的元素,当使用完一次双指针算法后,a也要跳过重复元素。

2.不漏:找到一种结果之后,不要"停",缩小区间,继续寻找

3.小优化:只有a≤0时,才有可能在后面的区间找到两个数之和=-a,因此a只需要遍历非正值即可。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        //1.排序
        sort(nums.begin(),nums.end());
        //2.按"双指针"查找
        vector<vector<int>> ret;
        int n = nums.size();
        for(int i = 0;i<n;)//固定a
        {
            for(int j=i+1;j<n;)//固定b
            {
                long long val = (long long)target - nums[i] - nums[j];
                int left = j+1;
                int right = n-1;
                while(left < right)
                {
                    int sum = nums[left] + nums[right];
                    if(val > sum)
                    {
                        left++;
                    }
                    else if(val < sum)
                    {
                        right--;
                    }
                    else
                    {
                        ret.push_back({nums[i],nums[j],nums[left],nums[right]});
                        left++;
                        right--;
                        //去重1
                        while(left < right && nums[left] == nums[left-1]) left++;
                        while(left < right && nums[right] == nums[right+1]) right--;
                    }          
                }
                //去重2
                j++;
                while(j < n && nums[j-1] == nums[j]) j++;
            }
            //去重3
            i++;
            while(i < n && nums[i-1] == nums[i]) i++;
        }

        return ret;
    }
};

题目分析:这道题会借用上面三数之和的思想,首先还是对数组进行排序,然后先固定最左边第一个数a,然后从后面区间找出和为target-a的三个数,然后就是借助三数之和的思想,先固定剩下区间的第一个数b,再从剩下区间找和为target-a-b的两个数,然后继续遍历,要和三数之和一样要做到不重不漏。

相关推荐
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
师太,答应老衲吧2 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
捕鲸叉2 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer2 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown3 小时前
【数据结构】选择排序
数据结构·算法·排序算法
青花瓷4 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
观音山保我别报错4 小时前
C语言扫雷小游戏
c语言·开发语言·算法
幺零九零零5 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
TangKenny5 小时前
计算网络信号
java·算法·华为