Posts 「算法刷题」双指针法(快慢指针法)
Post
Cancel

「算法刷题」双指针法(快慢指针法)

一、顺序结构中的双指针法

顺序结构:这里主要指的是数组或字符串。

(1)普通数组

slow指向 当前所需元素数组的下一个空位。返回值为slow时,slow就代表目标数组的长度。

题目简述关键点解题思路力扣题目相关链接
删除数组中的某类元素同向移动,快慢指针同时出发;slow:指向当前所需元素数组的下一个空位。fast:遍历数组。 如果快指针遇到目标值则快指针直接跳过它一步,慢指针不动,如果快指针没有遇到目标值则将自己手中的数交给慢指针,然后各自前进一步。27 移除元素(简单难度)https://leetcode-cn.com/problems/remove-element/
移动数组中的某类元素到开头或者末尾同向移动,快慢指针同时出发;如果快指针遇到目标值则快指针直接跳过它一步,慢指针不动,如果快指针没有遇到目标值则将自己手中的数和慢指针交换,然后各自前进一步。283 移动零(简单难度)https://leetcode-cn.com/problems/move-zeroes/

(2)有序数组

无序数组可以直接使用sort()函数排序后变成有序数组。sort(nums.begin(),nums.end())

题目简述关键点解题思路力扣题目相关链接
删除有序数组中的重复元素同向移动,快指针先行一步,每次不断比较两个指针上的数是否相同。一个指针(fast)从头到尾遍历,另一个(slow)始终指示当前没有重复的元素数组的下一个空位置;相同则快指针直接跳过去多行一步,不相同则快指针将手中的数交给慢指针,然后快慢指针同时前进一步;(这时慢指针指的是当前没有重复元素的最后一个*:注意返回值是slow+1。)26 删除排序数组中的重复项(简单难度)https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
求有序数组中的众数(可能不止一个)同向移动,快指针先行一步,每次不断比较两个指针上的数是否相同。相同则频率计时器+1,不相同则频率计时器设为0;每次检查频率计时器是否超过最大计数,超过则记录当前快指针上的数为众数。vector数组排序:sort(nums.begin(),nums.end())169 多数元素(中等难度)https://leetcode-cn.com/problems/majority-element/
将有序数组中每个元素平方后再排序反向移动,左指针和右指针同时出发,每次比较左指针和右指针手上的数的平方;如果左指针手中数的平方更大则移动起始指针,如果末尾指针手中的数更大则移动末尾指针;当起始指针大于末尾指针时循环结束。(有序数组平方数组平方的最大值就在数组的两端,所以排序需要不断比较数组两端的数)977 有序数组的平方(简单难度)https://leetcode-cn.com/problems/squares-of-a-sorted-array/

169 多数元素(中等难度)

1、排序法

众数是指在数组中出现次数大于⌊ n/2 ⌋ 的元素。 先将nums排序, ,然后返回中间元素的值即可(众数的个数大于一半,排好序的nums中间元素一定是众数) 。

1
2
3
4
5
6
7
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};

2、摩尔投票法思路

候选人(cand_num)初始化为nums[0],票数count初始化为1。 当遇到与cand_num相同的数,则票数count = count + 1,否则票数count = count - 1。 当票数count为0时,更换候选人,并将票数count重置为1。 遍历完数组后,cand_num即为最终答案。

为何这行得通呢? 投票法是遇到相同的则票数 + 1,遇到不同的则票数 - 1。 且“多数元素”的个数> ⌊ n/2 ⌋,其余元素的个数总和<= ⌊ n/2 ⌋。 因此“多数元素”的个数 - 其余元素的个数总和 的结果 肯定 >= 1。 这就相当于每个“多数元素”和其他元素 两两相互抵消,抵消到最后肯定还剩余至少1个“多数元素”。

无论数组是1 2 1 2 1,亦或是1 2 2 1 1,总能得到正确的候选人。

作者:gfu 链接:https://leetcode-cn.com/problems/majority-element/solution/3chong-fang-fa-by-gfu-2/ 来源:力扣(LeetCode)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int cand=nums[0],count=1;
        for(int i=1;i<nums.size();i++){
            if(cand==nums[i]){
                count++;
            }
            else if(--count==0){
                cand=nums[i];
                count=1;
            }
        }
        return cand;
    }
};

二、非顺序结构中的双指针法

这里的非顺序结构主要指的是:链表或二叉树。

1、链表

链表删除某个元素的模板:

1、创建头结点和当前结点

2、删除结点

3、还原真实头结点,返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ListNode* removeElements(ListNode* head, int val) {
        //新建虚拟头结点
        ListNode * myHead = new ListNode(0);
        myHead->next=head;
        //循环检查链表的当前节点的下一节点
        ListNode* cur=myHead;//当前节点(一定不为空)
        while (cur->next != NULL) {
            if(cur->next->val == val) {
                cur->next = cur->next->next;//删除下一节点
            } else {
                cur = cur->next;//更新当前节点为下一节点
            }
        }
        //还原真实头结点
        head = myHead->next;
        return head;
    }

(1)链表的删除操作

有序链表中删除一个节点cur需要其前一个节点pre->next=cur->next; 所以其天生需要节点的先后关系。

所以在执行删除操作时,一般就需要(头结点)

题目简述关键点解题思路力扣题目相关链接
删除链表中的某类元素(简单难度)虚拟头结点;链表的循环操作和节点的删除操作同向移动,快指针从头结点先行一步,每次判断快指针是否符合删除条件,若符合则删除快指针并且重新为快指针赋值;否则则快指针和慢指针都前进一步。203 移除链表元素(简单难度)https://leetcode-cn.com/problems/remove-linked-list-elements/
删除链表的倒数第N个节点(中等难度)快指针先走N步之后进行判断同向出发,快指针从头结点先行N步,如果此时快指针不存在则直接返回,若存在则快指针和慢指针(从头结点)同时出发,当快指针不存在时删除慢指针之后的一个节点。19 删除链表的倒数第N个节点(中等难度)https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

19.删除链表的倒数第n个结点

(1)假设链表长度为m+n。

fast、slow开始指向虚拟头结点。

1、fast先走n步

2、fast和slow同时走,直到fast指向链表的最后一个。

​ 则slow走了m步。

(2)链表的删除操作,需要得到目标结点的前一个结点。

所以

1、fast先走n+1步。

2、fast和slow同时走,直到fast指向链表的最后一个的下一个位置:NULL。

​ 则slow走了m-1步,在倒数第n个结点前。

(2)链表的反转操作

###

题目简述关键点解题思路力扣题目相关链接
将一个链表全部反转过来相邻节点之间的反转操作同向移动,快指针从头结点先行一步;每次移动快节点和慢节点都进行一次反向206 反转链表(简单难度)https://leetcode-cn.com/problems/reverse-linked-list/
将一个链表中的[left,right]区域反转过来 慢指针从虚拟头结点出发找到第left个节点,然后快指针从慢指针的后一个节点出发,共同前进找到第right个节点92 反转链表II(中等难度)https://leetcode-cn.com/problems/reverse-linked-list-ii/
  根据快慢指针找到中点;反转后半段;比较前后两端节点值234 回文链表(简单难度)https://leetcode-cn.com/problems/palindrome-linked-list/

###

206、反转链表

1、保存后面链表

2、断开指针

3、重新规划pre、cur

234、回文链表

1、慢指针走一步,快指针走两步

2、pre记录慢指针的前一个,用来分割链表,如果链表长度为奇数,则后半部分多一个结点。

3、将后半部分反转,得cur2,前半部分为cur1.

4、按照cur1的长度,比较cur1和cur2的节点数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* slow=head;
        ListNode* fast=head;
        ListNode* pre=head;
        
        //慢指针走一步,快指针走两步
        while(fast && fast->next){
            pre=slow;
            slow=slow->next;
            fast=fast->next->next;
        }
        
        //pre断开链表
        pre->next=NULL;
        ListNode* cur1=head;
        //反转cur2
        ListNode* cur2=reverselist(slow);
        //按照cur1的长度,比较cur1,cur2
        while(cur1){
            if(cur1->val!=cur2->val){
                return false;
            }
            cur1=cur1->next;
            cur2=cur2->next;
        }
        return true;

    }
    ListNode* reverselist(ListNode* head){
        ListNode* cur=head;
        ListNode* temp=head;
        ListNode* pre=NULL;
        while(cur){
            //保存后面链表
            temp=cur->next;
            //断开原链表
            cur->next=pre;
            //重新规划pre,cur
            pre=cur;
            cur=temp;
        }
        return pre;
        
    }
};
This post is licensed under CC BY 4.0 by the author.