一,双指针大概了解
双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。
1,快慢指针扫描过程中,一般有两种情况
1)快指针进行逐个扫描,慢指针依赖快指针进行更新,慢指针则作为一个记录变量辅助完成对快指针扫描到的元素的操作,或者说慢指针只是依赖于快指针单纯进行更新,等到循环的最后才会使用到更新完毕的慢指针的值。(即:快指针与慢指针之间有一个核心的关系,这个关系决定了慢指针如何更新)例如leetcode第206题:反转链表;leetcode第27题:移除元素
2)快指针扫描步长>慢指针扫描步长,从而达到一些目的
总之,快慢指针一般用于处理整个数组或者链表对象,最后使其形成新的对象。例如leetcode第142题:环形链表II
2,对撞指针适用于有序数组,一般用于从整个数组或者链表当中提取出符合条件的元组,即更加快速地提取出数组当中的数据。例如leetcode第15题:三数之和;leetcode第18题:四数之和;LeetCode第881题:救生艇
总结:双指针法充分使用了数组有序这一特征, 从而在某些情况下能够简化一些运算。
下面分别通过实践这些题目深入认识双指针算法。
二,快慢指针
2.1 leetcode第206题:反转链表;
2.1.1 题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
2.1.2 思路
观察这个链表,我们发现只需要让每一个节点的指针的next指向都改变为指向他的前节点,该链表就可以完成反转。我们可以使用O(n)的方法来解决这道题,使用双指针的思路。
1,其中now指针沿着链表依次遍历每一个元素,而per指针则记录now指针的上一个元素。
2,在每次循环遍历到某个元素的时候,只需要让now->next指向per,就完成了此处的链表反转,然后再让新的per=now,处理下一个元素。
3,需要注意的是,下一次循环处理的下一个节点不能通过now->next找到了,因为我们在处理过程中已经改变了now->next的指向,因此在本次处理now元素之前,首先要用临时变量存储now->next。方便我们进行下一次循环。
2.1.3 代码
(注意,题目中注释里面给的链表一个节点的样子,我们默认这个链表是不带头节点的)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* per=nullptr;
ListNode* now=head;
ListNode* tmp;
while(now!=nullptr){
tmp=now->next;
now->next=per;
per=now;
now=tmp;
}
head=per;
return head;
}
};
2.1.4 总结
本题中使用到的双指针可以认为是快慢指针,其中快指针为now,而慢指针为per;快指针的目的是依次遍历链表元素,而慢指针记录了now->父亲的值,在本题中是为了辅助完成对now指针指向的元素的操作。
2.2 leetcode第27题:移除元素;
2.2.1 题目:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
2.2.2 思路
这道题目的要求比较简单,总的来说就是要求读者删除数组当中的指定元素,返回最后得到的数组长度以及数组内容即可。——使用暴力算法即可完成,算法复杂度为O(n^2)。需要注意的是,