【力扣】【双指针】

什么是双指针?

(对撞指针、快慢指针)

双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。

换言之,双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算。

对撞指针

对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。

对撞数组适用于 有序数组,也就是说当你遇到题目给定有序数组时,应该第一时间想到用对撞指针解题。

伪代码大致如下:

function fn (list) {
  var left = 0;
  var right = list.length - 1;

  //遍历数组
  while (left <= right) {
    left++;
    // 一些条件判断 和处理
    ... ...
    right--;
  }
}

例子:救生艇问题

881. 救生艇 - 力扣(LeetCode)


给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。

每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit

返回 承载所有人所需的最小船数


要使需要的船数尽可能地少,应当使载两人的船尽可能地多。

设 people 的长度为 n。

考虑体重最轻的人

若他不能与体重最重的人同乘一艘船,那么体重最重的人无法与任何人同乘一艘船,此时应单独分配一艘船给体重最重的人。从 people 中去掉体重最重的人后,我们缩小了问题的规模,变成求解剩余n−1 个人所需的最小船数,将其加一即为原问题的答案。

若他能与体重最重的人同乘一艘船,那么他能与其余任何人同乘一艘船,为了尽可能地利用船的承载重量,选择与体重最重的人同乘一艘船是最优的。从 people中去掉体重最轻和体重最重的人后,我们缩小了问题的规模,变成求解剩余 n−2 个人所需的最小船数,将其加一即为原问题的答案。

在代码实现时,我们可以先对 people 排序,然后用两个指针分别指向体重最轻和体重最重的人,按照上述规则来移动指针,并统计答案。

class Solution {
public:
    int numRescueBoats(vector<int>& people, int limit) {
       int ans=0;//记录船数 
       sort(people.begin(),people.end());//先排序     
        
        int light=0,heavy=people.size()-1; 
        
        while(light<=heavy)
        {
            if(people[light]+people[heavy]>limit)
            {
                --heavy;//heavy加上一个最小的都会超出限制,直接自己一个人坐船 ,ans++ 
                
            }else{//两个人可以一起坐船,ans++ 
            
                ++light;//两个人已经坐上了船,移动指针 
                --heavy;
            }
            ans++;
        }
        return ans; 
    }
};

快慢指针

快慢指针也是双指针,但是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如fast每次增长两个,slow每次增长一个。

例子:环形链表

141. 环形链表 - 力扣(LeetCode)


给你一个链表的头节点 head ,判断链表中是否有环

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。


Floyd判圈算法(龟兔赛跑算法) - 简书 (jianshu.com)

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。
当「乌龟」和「兔子」从链表上的同一个节点开始移动时,
如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;
如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。
等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,
即套了「乌龟」若干圈。

class Solution
{
public:
    bool hasCycle(ListNode *head)
    {
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast != nullptr && fast->next != nullptr)
        {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow)
            {
                return true;
            }
        }
        return false;
    }
};

总结

当遇到有序数组时,应该优先想到双指针来解决问题,因两个指针的同时遍历会减少空间复杂度和时间复杂度。

练手

1.盛最多水的容器

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

直接看题解就行,肯定比我写得好。

就是说学习双指针做法

class Solution {
public:
    int maxArea(vector<int>& height) {

        int l=0,r=height.size()-1; //双指针

        int ans=0;
        
        while(l<r)
        {
            int area=min(height[l],height[r])*(r-l);
            ans=max(ans,area);//ans记录目前最大面积 
            
            if(height[l]<=height[r])//小的指针进行移动 
            {
                ++l;
            }else{
                --r;
            }
            
        }
        return ans;    
    }
};

2.三数之和

最接近的三数之和 - 最接近的三数之和 - 力扣(LeetCode)

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        int best = 1e7;
        // 枚举 a
       for (int i = 0; i < n; ++i) {
        // 保证和上一次枚举的元素不相等
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        // 使用双指针枚举 b 和 c
        int j = i + 1, k = n - 1;
        while (j < k) {
            int sum = nums[i] + nums[j] + nums[k];
            // 如果和为 target 直接返回答案
            if (sum == target) {
                return target;
            }
            if (abs(sum - target) < abs(best - target)) {
                best = sum;
            }
            if (sum > target) {
                // 如果和大于 target,移动 c 对应的指针
                int k0 = k - 1;
                // 移动到下一个不相等的元素
                while (j < k0 && nums[k0] == nums[k]) {
                    --k0;
                }
                k = k0;
            } else {
                // 如果和小于 target,移动 b 对应的指针
                int j0 = j + 1;
                // 移动到下一个不相等的元素
                while (j0 < k && nums[j0] == nums[j]) {
                    ++j0;
                }
                j = j0;
            }
        }
    }
    return best;
    }
};

3.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。

283. 移动零 - 力扣(LeetCode)


解题思路

两次遍历

我们创建两个指针 i 和 j,第一次遍历的时候指针 j 用来记录当前有多少 非0 元素。即遍历的时候每遇到一个 非0 元素就将其往数组左边挪,第一次遍历完后,j 指针的下标就指向了最后一个 非0 元素下标。

第二次遍历的时候,起始位置就从 j 开始到结束,将剩下的这段区域内的元素全部置为 0。

动画演示:


class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int n=nums.size();

        int a=0; int b=0;

        for(a=0;a<n;a++)
        {//b从0位置开始,只要a的值不为0,nums[b]=nums[a]; 
            if(nums[a]!=0)
            {
                nums[b]=nums[a];
                b++;
            }
        }//循环结束b记录最后一个不为0的数,则后面全为0
        for(int i=b;i<n;i++)
        {
            nums[i]=0;
        }
    }
};

快慢指针找链表的中间节点

快慢指针(注意链表长度为偶数时,返回第 2 个结点的细节) - 链表的中间结点 - 力扣(LeetCode)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值