双指针一般运用于数组和链表当中。在数组当中,我们通常利用2个下标去进行操作(如:删除数组中的某个元素,二分查找等);在链表当中,我们会去用2个指针(快指针fast和慢指针slow)对链表结构进行操作(如:求链表的中间结点,求相交链表的交点,判断链表是否成环等)。
双指针在数组中的应用:
1.原地删除数组中值为val的元素,要求时间复杂度为O(N),空间复杂度为O(1)
这道题由于时间复杂度的限制,没办法用双重循环去删除数组中的重复元素,因此这里采用了双下标去删除的做法。一个下标为slow,另一个为fast,它们都从0开始,当某位置的元素不是val,将fast下标的值赋值给slow后,fast和slow一起++;如果某位置的元素是val,则slow不进行任何操作,fast++。当fast = 数组大小 -1的时候结束循环。
int removeElement(int* nums, int numsSize, int val)
{
//定义2个下标变量
int slow = 0;
int fast = 0;
while(fast < numsSize)
{
//判断是否是要删除的元素
if(nums[fast] == val)
{
fast++;
}
else
{
nums[slow] = nums[fast];
fast++;
slow++;
}
}
//返回删除后的元素个数
return slow;
}
2.逆置数组(反转数组)
定义2个下标left和right,left从0开始,right从数组的大小-1开始,2个下标所对应的元素进行交换。最终当left >= right的时候停止交换。
void reverse(int* nums, int size)
{
int left = 0;
int right = size - 1;
while (left < right)
{
//交换left和right对应的元素
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
双指针在链表中的应用:
1.求链表的中间结点(如果有2个中间结点,返回第2个)
定义快指针fast和慢指针slow,fast每次走2步,slow每次走1步,这样当fast到尾结点或者NULL的时候,slow正好处于链表的中间位置。
下图是对应2种情况,fast->next为NULL和fast为NULL。
struct ListNode* middleNode(struct ListNode* head)
{
//定义2个指针,快指针1次走2格,慢指针1次走1格
struct ListNode* fast = head;
struct ListNode* slow = head;
//注意判断条件,fast为NULL时fast在NULL处,fast->next=NULL时fast在尾结点处。
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
2.求链表的倒数第k个结点
定义快指针fast和慢指针slow,要求倒数第k个结点,也就是这个结点是距离尾结点的距离为k-1【距离NULL为k】。此时让fast指针先走k步,然后fast和slow一起走,当fast==NULL时候,slow所指向的结点就是倒数第k个结点。
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
struct ListNode* fast = pListHead;
struct ListNode* slow = pListHead;
//这里需要注意,如果k给的值大于链表的长度,则也要结束循环,最后判断返回NULL
while(k && fast)
{
fast = fast->next;
k--;
}
if(fast == NULL && k)
return NULL;
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
3.求2个相交链表的交点
定义2个指针用来去求2条链表的长度,然后根据链表的长度定义一个指向长链表的指针LongList和指向短链表的指针ShortList。让LongList先走2条链表的长度差的绝对值的步数,然后LongList和ShortList一起走,并且同时进行判断,如果LongList == ShortList那么第一个相等的点就是2条链表的交点。
struct ListNode* getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//思路:先让长的链表的指针走它比短的长多少的步数,然后两个指针同时移动进行比较
struct ListNode* curA = headA;
struct ListNode* curB = headB;
int LA = 0;
int LB = 0;
while(curA)
{
LA++;
curA = curA->next;
}
while(curB)
{
LB++;
curB = curB->next;
}
//定义一个长链表的指针和一点短链表的指针
struct ListNode* LongList = LA>LB?headA:headB;
struct ListNode* ShortList = LA<=LB?headA:headB;
//求出间隔的步数
int gap = abs(LA-LB);//abs求的是绝对值
while(gap--)
{
LongList = LongList->next;
}
//找第一个相同结点,然后return该点
while(LongList)
{
if(LongList == ShortList)
return LongList;
LongList = LongList->next;
ShortList = ShortList->next;
}
//两条链表不相交,返回NULL
return NULL;
}
4.判断链表是否成环
定义快指针fast和慢指针slow,快指针每次走2步,慢指针每次走1步,如果快指针再次与慢指针相遇(fast == slow),则该链表成环,否则不成环。【这里快指针的步数可以改变,但要注意有可能会出现快指针永远追不上慢指针的情况。】
快指针每次和慢指针间距缩小1,所以如果该链表成环的话,快指针会与慢指针再次重合。
bool hasCycle(struct ListNode *head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
//通过定义一个快指针和慢指针,如果快指针能追上慢指针,则该链表有环,否则该链表无环
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
//快指针追上了慢指针
if(fast == slow)
return true;
}
//如果快指针遇到NULL,则该链表肯定不成环
return false;
}
5.求成环链表进环的第一个结点(入环点)
在上一个题的基础上,我们可以求出fast和slow相遇点。然后定义一个指针meet指向该结点,再定义一个指向头结点的指针head,head和meet一起走,它们的值相同的那个点就是入环点。
struct ListNode* detectCycle(struct ListNode *head)
{
//通过head和fast与slow的交点(meet)这两个指针进行查找入环点
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
break;
}
if(fast == NULL || fast->next == NULL)
return NULL;
struct ListNode* meet = fast;
while(head != meet)
{
head = head->next;
meet = meet->next;
}
return head;
}