LeetCode - 链表快慢指针类总结

链表快慢指针

      快慢指针中的快慢指的是指针沿链表移动的步长,即每次向前移动速度的快慢。快指针每次沿链表向前移动两步fast=fast->next->next,慢指针每次向前移动一步slow=slow->next

一、回文链表

请判断一个链表是否为回文链表。用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题。

示例 1:输入: 1->2 ;输出: false
示例 2:输入: 1->2->2->1 ;输出: true

      分析:使用快慢指针来找到链表的中点: 首先我们设置两个指针slow和fast,slow指针每次移动一步,fast指针每次移动两步;如果链表中节点个数为奇数时,当快指针无法继续移动时,慢指针刚好指向中点;如果链表中节点个数为偶数时,当快指针走完,慢指针指向中点前一个节点。然后反转中间节点后的链表与中间节点前未反转的链表进行比较。

// 使用快慢指针来找到链表的中点:
ListNode *slow=head,*fast=head;
while(fast->next&&fast->next->next)
{
	slow=slow->next;//slow指针每次走一步
	fast=fast->next->next;//fast指针每次走两步
}
class Solution {
public:
    bool isPalindrome(ListNode* head) {//O(n)、O(1)
    ListNode* slow = head, *fast = head,  *prev = nullptr;
	while(fast->next&&fast->next->next)
	{
		slow=slow->next;//slow指针每次走一步
		fast=fast->next->next;//fast指针每次走两步
	}
    while (slow){//reverse
        ListNode* ovn = slow->next;
        slow->next = prev;
        prev = slow;
        slow = ovn;
    }
    while (head && prev){//check
        if (head->val != prev->val){
            return false;
        }
        head = head->next;
        prev = prev->next;
    }
    return true;
}
};

二、环形链表I

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。说明:不允许修改给定的链表。

      分析:快慢指针中,因为每一次移动后,快指针都会比慢指针多走一个节点,所以他们之间在进入环状链表后,不论相隔多少个节点,慢指针总会被快指针赶上并且重合,此时就可以判断必定有环。快指针每次走两步,慢指针每次走一步,那么存不存在一种情况,快指针直接“越过了”慢指针,到达了慢指针的下一个格子呢?答案是不可能的,我们假设这种状态存在,也就是快指针越过了慢指针,领先慢指针一个格子,逆推一步,慢指针倒退一格,快指针倒退两格,那么快慢指针依然相遇,也就是说,快指针永远不可能直接越过慢指针,到达后一格。
      利用快慢指针来判断链表中是否有环的思路就是

  1. 设置两个指针,慢指针每次走一步,快指针每次走两步;
  2. 如果快慢指针相遇,那么链表含有环;
  3. 如果快指针到达了链表尾部且没有与慢指针相遇,那么链表不含有环。如不存在环,fast遇到NULL退出。
 class Solution {
  public:
	  bool hasCycle(ListNode *head) {
		  if (head == nullptr)
			  return false;
		  ListNode* slow = head;
		  ListNode* fast = head;
		  while(fast->next&&fast->next->next)
		  {
			  slow = slow->next;
			  fast = fast->next->next;
			  if (slow == fast)
				  return true;
		  }
		  return false;
	  }
  };

      利用双指针来找出环形链表的环入口指针的思路就是:

  1. 先定义两个指针p1和p2指向链表的头结点
  2. 如果链表中有n个节点,则指针p1先在链表上向前移动n步,然后两个指针以相同的速度向前移动;
  3. 当第二个指针指向环的入口节点时,第一个指针已经围绕着环走了一圈,又回到了入口节点。
  class Solution {
  public:
	  ListNode *detectCycle(ListNode *head) {
		  //验证是否是环形链表
		  if (head == nullptr)
			  return head;
		  ListNode* slow = head;
		  ListNode* fast = head;
		  ListNode *meetNode= nullptr;
		  while(fast->next&&fast->next->next)
		  {
			  slow = slow->next;
			  fast = fast->next->next;
			  if (slow == fast) {
				  meetNode = slow;
				  break;
			  }
		  }
		  slow = head;
		  fast = head;
		  if (meetNode == nullptr)
			  return nullptr;
		  //得到环的数目
		  int cyclenum = 1;
		  ListNode* temp = meetNode;
		  while (temp->next!=meetNode) {
			  cyclenum++;
			  temp = temp->next;
		  }
		  //先移动fast节点cysclenum步
		  for (int i = 0; i < cyclenum; i++)
			  fast = fast->next;
		 //同时移动slow和fast
		  while (slow != fast) {
			  slow = slow->next;
			  fast = fast->next;
		  }
		  return slow;
	  }
  };



三、环形链表II

1、判断两个无环单向链表是否,并返回相交节点
思路: 如果链表都无环,则先判断链表的尾指针是否一样,如果不一样,则没有相交。如果一样,则找出两个链表的长度差,将两个链表从距离尾节点同样的距离进行扫描,如果相交,则必然有一处扫描节点相同。 见6.

2、判断有环单向链表是否相交,并返回相交节点
在这里插入图片描述

思路:

  • 先找到每个链表和环的相交节点,若两个相交节点一致,则两个链表相交。
  • 若两个链表和环的相交节点不一致,则判断从一个相交节点出发,沿环遍历是否能找到另一个相交节点,若找到则两个链表相交,若没找到则不相交。

四、删除链表倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:给定一个链表: 1->2->3->4->5, 和 n = 2。当删除了倒数第二个节点后,链表变为 1->2->3->5

      分析:利用双指针找到链表倒数第N个节点: 一个快指针fast,一个慢指针slow,让快指针先走n步,再让快慢指针一起走,当快指针走到链表最后,慢指针正好走在链表的倒数第n+1个位置,此时删除slow指针的后一个值,即删除了链表第n个位置的值。
      再考虑一些特殊情况,比如链表只有一个值或无值时,任意有效删除之后,这时链表为空。还有要注意的是可能是要删除第一个节点,这时提前走n步的快指针会最后变为空指针,这个时候可以直接返回head -> next。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		if(!head || !head -> next) return NULL;
		ListNode* slow = head;
		ListNode* fast = head;
		for (int i = 0; i < n; i++)
		{
			if (!fast->next)
			{
				 return head -> next; //要注意的是可能是要删除第一个节点,这个时候可以直接返回head -> next
			}
			fast = fast->next;
		}
		while (fast->next){
			slow = slow->next;
			fast = fast->next;	
		}
		slow->next = slow->next->next;
        return head;
	}
};



五、反转链表

反转一个单链表。

示例:输入: 1->2->3->4->5->NULL; 输出: 5->4->3->2->1->NULL

     分析:采用双指针迭代的方法反转链表。 慢指针prev作为快指针反转后的next节点,同时每次反转完一个节点后,更新快慢指针分别向前移动一步。直到快指针为nullptr,返回prev作为反转后的头节点。

class Solution {
  public:
	  ListNode* reverseList(ListNode* head) {
		  if (head == nullptr || head->next == nullptr)
			  return head;
		  ListNode* fast = head;
		  ListNode* prev = nullptr;
		  while (fast)
		  {
			  ListNode* temp = fast->next;
			  fast->next = prev;
			  prev = fast;
			  fast = temp;
		  }
		  return prev;
	  }
  };

六、相交链表

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表,相交的起始节点为c1。


在这里插入图片描述

分析:

  1. 先让A链表和B链表分别走的各自链表的结尾计算两个链表的长度。
  2. 若两个链表的最后的节点不相等的话,则说明链表不重合,返回nullptr。
  3. 否则存在链表相交。计算两个链表的长度差为s,先让长链表走s步。
  4. 长链表和短链表同时向前移动,若节点指针相同时,即找到了相交节点。
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA=0;
        int lenB=0;
        
        ListNode* curA = headA;
        ListNode* curB = headB;
        
        while(curA && curA->next)
        {
            lenA++;
            curA=curA->next;
        }
        while(curB && curB->next)
        {
            lenB++;
            curB=curB->next;
        }
       //此时curA 与curB均走到最后,若两个链表相交则curA等于curB
        //1、不相交,返回NULL
        //2、相交,求交点
        if(curA != curB)
        {
            return NULL;
        }
        else
        {
            int gap = abs(lenA-lenB);//求两个链表长度差值的绝对值
            ListNode* longlist = headA;
            ListNode* shortlist = headB;
            if(lenB > lenA)
            {
                longlist = headB;
                shortlist = headA;
            }
            
            while(gap--)
            {
                longlist = longlist->next;
            }
            
            while(1)
            {
                if(longlist == shortlist)
                {
                    return longlist;
                }
                else
                {
                    longlist = longlist->next;
                    shortlist = shortlist->next;
                }
            }
        }
        
    }
};

七、 K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:给你这个链表:1->2->3->4->5;当 k = 2 时,应当返回: 2->1->4->3->5;当 k = 3 时,应当返回: 3->2->1->4->5。

说明:你的算法只能使用常数的额外空间。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
 public:
	 ListNode* reverseKGroup(ListNode* head, int k) {
		 int length = 0;
		 for (ListNode* tmp = head; tmp != nullptr; tmp = tmp->next)
			 length++;
		 if (length < k||k==0)
			 return head;
		 int swapNum =0;
		 ListNode* pre = nullptr;
		 ListNode* begin = nullptr;
		 ListNode* end = nullptr;
		 ListNode* res = nullptr;
		 int num = 1;
		 for (ListNode* tmp = head; tmp != nullptr; ) {
			 if (num == 1) {
				 begin = tmp;
			 }
			 if (num == k)
			 {
				 end = tmp;
				 tmp = end->next;
				 reverselist(begin, end);//交换完链表后begin指向反转链表的尾节点,end指向反转完链表的首节点。
				 if (res == nullptr)
					 res = end;
				 if(pre!=nullptr)
					pre->next = end;
				 pre = begin;
				 num = 1;
				 swapNum++;
				 if (swapNum == length / k) {
					 pre->next = tmp;
					 break;
				 }
				 continue;	 
			 }
			 num++;
			 tmp = tmp->next;
		 }
		 return res;
	 }
	 //反转从begin到end的链表 //交换完链表后begin指向反转链表的尾节点,end指向反转完链表的首节点。  1 2 3 4
	 void reverselist(ListNode* begin, ListNode* end) {
		 ListNode* s = begin;
		 ListNode* pre = nullptr;
		 ListNode* tmp = nullptr;
		 while (pre!= end) {
			 tmp = s->next;
			 s->next = pre;
			 pre = s;
			 s = tmp;
		
		 }
	 }
 };

八、链表指定区间反转

将一个链表m 位置到n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL,m=2,n=4,
返回1→4→3→2→5→NULL.
注意:
给出的 m,n 满足以下条件:1≤m≤n≤链表长度

ListNode *reverseBetween(ListNode *head, int m, int n) {
        if(head == NULL || head->next == NULL) return head;
         
        ListNode *q = NULL, *p = head;
        for (int i = 0; i < m - 1; i++)
        {
            q = p;
            p = p->next;
        }
     
        // p此时指向第m个结点
        ListNode *end = p;
        ListNode *pPre = p, *nxt = NULL;
        p = p->next;
     
        // m->n之间的结点逆序
        for (int i = m + 1; i <= n; ++i)
        {
            nxt = p->next;
            p->next = pPre;
            pPre = p;
            p = nxt;
        }
        // p指向原链表中第n个结点的下一个结点
        // end表示逆序子链表的尾结点
        end->next = p;
         
        // pPre指向的是逆序后的子链表头
        // q指向的是第m个结点前一个结点 注意有可能是NULL
        if (q)
            q->next = pPre;  // 不是NULL 链接起来即可
        else
            head = pPre;    // 是NULL 头结点即为子链表头
     
        return head;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值