IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【从零开始】双指针的配合 -> 正文阅读

[数据结构与算法]【从零开始】双指针的配合

双指针

双指针是出现常用的一种解题方法。两个指针就像兄弟一般完美配合,不论是左右指针还是快慢指针,都可以达到非常好的效果。

数组下的双指针

对于数组来说,在要求对重复元素移除等需要原地移除(也就是在原数组身上做改变,而不新建数组)

移除元素多种写法

先来看一道简单题:

leetcode 27 移除元素

原地移除所有数值等于val的元素。

这时我们就可以定义两个指针,一个快指针一个慢指针。

快指针,就是一直向前走的那一个。而慢指针则是遇到需要处理的情况才动身的那一个。

比如[0,1,2,2,3],val=2

快指针fast先判断,如果当前指向的元素不是val,那么把这个值给慢指针slow,然后fast走一步,slow走一步。

是val那么slow就不走。(这样做是为了跳过val,同时又能把val之后的值覆盖到val的位置)

我们先用最通俗的写法:

int removeElement(vector<int>& nums, int val) {
        int slow=0,fast=0;
        while(fast!=nums.size()){
            if(nums[fast]!=val){
                nums[slow]=nums[fast];
                fast++;
                slow++;
            }

            else
                fast++;
        }
        return slow;

    }

说一下返回值为什么是slow。最后要求返回长度,看上去slow是从0开始的,应该+1。但实际上我们在最后判断时让slow++了,所以就不用+1了。

这里可以进一步简化为:

int removeElement(vector<int>& nums, int val) {
        int slow=0,fast=0;
        while(fast!=nums.size()){
            if(nums[fast]!=val){
                nums[slow]=nums[fast];
                slow++;
            }
                fast++;
        }
        return slow;

    }

当然我们也可以把fast写到for循环里:

for(fast=0;fast<nums.size();fast++){
	if(nums[fast]!=val){
		nums[slow]=nums[fast];
        slow++;
	}
}

删除排序数组的重复值

这也是一道双指针问题,只是需要多思考一下赋值的过程

leetcode 26 删除有序数组中的重复项

比如[0,1,1,1,2,2,3,3],我们也需要fast和slow来帮忙。

但这种情况下没有val,所以需要fast和slow前后错开一位来判断是不是遇到了重复的元素。

int removeDuplicates(vector<int>& nums) {
        int fast=0,slow=0;
        while(fast!=nums.size()){
            if(nums[fast]!=nums[slow]){
                slow++;
                nums[slow]=nums[fast];
            }
            fast++;
        }
        return slow+1;

    }

和上一题最大的区别在于:这里是先给slow++再赋值的(这个过程画个图很好理解),因为是去重,所以需要保留一个元素。由于最后slow的位置就是数组下标,所以返回长度要+1

移动零

也是一道双指针的简单题目:

leetcode 283 移动零

把所有的零移动到后面,保持非零元素的顺序不变。

我们可以理解为先把零移除了,再把剩下的元素都变为零。

void moveZeroes(vector<int>& nums) {
        int slow=0,fast=0;
        while(fast<nums.size()){
            if(nums[fast]!=0){
                nums[slow]=nums[fast];
                slow++;
            }
            fast++;
            
        }
        for(int i=slow;i<nums.size();i++){
            nums[i]=0;
        }

    }

理,这里的slow已经+1了,所以赋值0的时候就是从下标slow开始的。

有序数组的平方

也有一些比较数学的问题,用双指针会方便很多。

leetcode 977 有序数组的平方

给一个数组[-4,-1,0,3,10],平方后排序,得[0,1,9,16,100]。当然也可以直接平方后排序,这里我们说一下双指针得方法。

**核心思想为:**最大的数不是在最左边就是最右边。(因为存在负数变为正的情况)

我们定义两个指针,从两端开始比较并填入平方的值即可。

vector<int> sortedSquares(vector<int>& nums) {
        vector<int>ans(nums.size(),0);//这个不太好原地覆盖了,需要定义一个新数组
        int n=nums.size()-1;
        int left=0;
        int right=nums.size()-1;
        while(left<=right){//需要等于才能把最后一个值填入
            if(nums[left]*nums[left]<nums[right]*nums[right]){
                ans[n--]=nums[right]*nums[right];
                right--;
            }
            else{
                ans[n--]=nums[left]*nums[left];
                left++;
            }
            
        }
        return ans;


    }

字符串的双指针

字符串其实本质上和数组大同小异。这有一道字符串的双指针题目可以用来锻炼问题分析能力。

leetcode 844 比较含退格的字符串

题目中增加了一个退格字符#,也就是可以把#前面的字符删掉。

为了方便使用双指针的解法,我们可以倒着遍历字符串,这样在遇到#时记录下来,然后当#数量大于0时跳过前面的字符(也相当于删除),再去比较。

(代码第一眼看上去有些长,结合注释其实很好理解)

bool backspaceCompare(string s, string t) {
        int i=s.size()-1;//字符串s的长度
        int j=t.size()-1;//字符串t的长度
        int skipS=0,skipT=0;//分别记录s和t中退格字符#的个数

        while(i>=0||j>=0){//保证至少有一个没有出界,有可能有一个字符串已经遍历完了
            while(i>=0){
                if(s[i]=='#'){//如果是退格符,记录,继续向前
                    skipS++;
                    i--;
                }
                else if(skipS>0){//如果不是退格符但是现在退格符数量不为0,可以删一个。当然多个#也可以删多个,比如ab##就为空
                    skipS--;
                    i--;
                }
                else 
                    break;
            }

            while(j>=0){//同理,统计字符串t的退格
                if(t[j]=='#'){
                    skipT++;
                    j--;
                }
                else if(skipT>0){
                    skipT--;
                    j--;
                }
                else 
                    break;

            }
//统计完退格,开始进入正式比较
            if(i>=0&&j>=0){//都在界内,如果字符不相等,那说明不一样
                if(s[i]!=t[j])
                    return false;
            }
            else if(i>=0||j>=0)//如果有一个出界了,那么说明不相等了。当然如果两个都出界那么就都为空,是相等的。
                return false;
                
    		//继续往前遍历            
            i--;
            j--;

            
        }
        return true;

    }

链表中的双指针

链表中双指针的应用也非常多,可以巧妙地解决一些问题。

链表的环

链表最常见的就是判断环的一些问题。

leetcode 141 环形链表

判断链表有没有环的关键在于:尾指针是不是指向null

这时候就可以用fast和slow来遍历。fast每次走两步,slow每次走一步,如果fast会和slow相遇,那么说明有环。

bool hasCycle(ListNode *head) {
        ListNode *fast,*slow;
        fast=slow=head;
        while(fast!=NULL&&fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
                return true;
        }

        
        return false;
    }

环的入口

上一道题的升级版就是这道判断环的入口。

leetcode 142 环形链表2

其实环的入口有点数学性质在里面。如下面的推导所示:

slow每次走一步,fast每次走两步。

在他们相遇时,a+b就是slow走的距离,而a+2b+c就是fast走的距离。

这时他们之间有两倍关系,所以可以得到a与c相等。

这个时候我们让一个和slow一样的指针从链表头走,当他们相遇时,就是入口的位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x6GjuydV-1647865949949)(…/…/…/Downloads/图片 2022-03-11 10-20-18.png)]

ListNode *detectCycle(ListNode *head) {
        ListNode *fast,*slow;
        fast=slow=head;
        while(fast!=NULL&&fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
            if(slow==fast)
                break;
        }
    //以上部分是让fast和slow相遇

        if(fast==NULL||fast->next==NULL) return NULL;
    
    //以下部分是利用a=c求环的入口
        slow=head;
        while(slow!=fast){
            slow=slow->next;
            fast=fast->next;
        }

        return slow;
        
    }

这里需要注意的是,我们前面的推论是在有环时才成立的。但是是否有环需要判断(也就是前面说的如果没环那么最后是NULL结尾)

找链表中的一些点

还有一类寻找链表的特殊点的题目,也是双指针的发挥空间。

leetcode 876 寻找链表的中点

寻找一个链表的中点,我们发现这里如果是偶数结点的话那么返回的是中间靠后的结点。

利用双指针可以很好地解决。fast一次走两步,slow一次走一步。当fast走到结尾的时候,slow刚好是这里要求的中点。

ListNode* middleNode(ListNode* head) {
        ListNode *fast,*slow;
        fast=slow=head;
        while(fast!=NULL&&fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
        }
        return slow;        

    }

还有这道:

leetcode 19 删除链表的倒数第N个节点

举个例子,比如1-2-3-4-5-6,删除倒数第三个也就是4

我们可以让fast指针走n步,也就是走3步,到达4

slow从头开始,然后fast和slow同时走,这样fast走2步到达尾部时,slow也走2步到了3,next就是需要删除的结点。

其实就是一个数学性质的小技巧。

ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *fast,*slow;
        fast=slow=head;
        while(n>0){
            fast=fast->next;
            n--;
        }
        if(fast==NULL)return head->next;

        while(fast!=NULL&&fast->next!=NULL){
            fast=fast->next;
            slow=slow->next;
        }
        slow->next=slow->next->next;
        return head;


    }

需要注意,如果倒数第n个正好是头结点,那么fast就会走到NULL,所以这种情况需要单独判断。

(关于链表的部分还是建议使用虚头结点)


还有很多双指针的例子,我们在后面的具体类型归纳中深入研究。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:49:54  更:2022-03-22 20:51:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:37:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码