LeetCode總結:雙指針在鏈表中的應用

  1. 刪除鏈表的倒數第n個節點——LeetCode 19
      思路:使用兩個指針,第一個指針first先走n步,若此時first爲空,則待刪除的節點是第一個節點;若first不爲空,則同時將first和second指針向前移動,並且first指針超前second指針n個節點,當first爲空時,second指向的節點即爲倒數第n個節點。需要注意的是,由於要刪除倒數第n個節點,故需要知道倒數第n+1個節點的位置,所以應對first->next是否爲空進行判斷。
ListNode* removeNthFromEnd(ListNode* head, int n) {
	if (!head) return NULL;

	ListNode *first = head;
	ListNode *second = first;

	/*first指針先走n步,此時若first爲空,則要刪除的節點是首節點*/
	for (int i = 0; i < n; i++) {
		first = first->next;
	}
	if (!first) {
		return head->next;
	}

	/*兩個指針同時走,當first->next爲空時,second指向的下一個節點即爲倒數第n個節點*/
	while (first->next) {
		first = first->next;
		second = second->next;
	}
	second->next = second->next->next;

	return head;
}
  1. 查找鏈表的中間節點——LeetCode 876
      思路:同樣使用兩個節點,兩個指針同時移動,第一個指針每次移動兩步,第二個節點每次移動一步,則第一個指針到達鏈表的最後一個節點時,第二個指針到達中間節點。
 ListNode* middleNode(ListNode* head) {
     if(!head) return NULL;
     
     ListNode *first = head, *second = head;
     while(first->next){
         first = first->next;
         second = second->next;  // 讓second提前走,可保證節點數爲偶數時,second指向中間靠後節點
         if(first->next) first = first->next;
     } 

     return second;
 }
  1. 判斷鏈表是否有環——LeetCode 141
      思路:使用兩個指針,first指針每次向前移動兩個節點。slow指針每次移動一個節點。若鏈表中有環,則兩個指針一定會相遇。
      完整的測試代碼如下:
#include<iostream>

using namespace std;

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

ListNode* MakeList();
void ListAppend(ListNode*, int);
ListNode* ListMakeCycle(ListNode*, int);
void Print(ListNode* head, ListNode* start);
bool hasCycle(ListNode *head);

int main() {
	ListNode *head = MakeList();
	int value;

	cout << "Please input the value of the node : " << endl;
	while (cin >> value) {
		ListAppend(head, value);
		if (cin.get() == '\n') {
			break;
		}
	}

	cout << "Please choose the start node of the cycle : " << endl;
	int n;
	cin >> n;
	ListNode *start = ListMakeCycle(head, n);
	cout << "The linked list with cycle : " << endl;
	Print(head, start);

	bool IsCycle = hasCycle(head->next);
	cout << "\nIs cycle ? " << IsCycle << endl;

	return 0;
}

/* 生成鏈表的表頭 */
ListNode* MakeList() {
	ListNode *head = new ListNode(-1);
	return head;
}

/* 在鏈表尾部插入節點 */
void ListAppend(ListNode* head, int value) {
	if (!head) return;

	ListNode *tmp = head;
	while (tmp->next) {
		tmp = tmp->next;
	}
	ListNode *node = new ListNode(value);
	tmp->next = node;
}

/* 將鏈表的最後一個節點接到第n個節點,生成環,並返回環的起點 */
ListNode* ListMakeCycle(ListNode* head, int n) {
	if (!head || !n) return NULL;

	ListNode *end = head->next;
	ListNode *start = end;

	int count = 1;
	while (end->next) {
		if (count != n) {
			start = start->next;
			count++;
		}
		end = end->next;
	}
	end->next = start;
	//cout << "\nstart node : " << start->val << endl;
	
	return start;
}

/* 打印鏈表,並將環打印兩遍 */
void Print(ListNode *head, ListNode *start) {
	if (!head) return;

	ListNode *tmp = head->next;
	int count = 0;
	while (true) {
		if (tmp == start) {
			count++;
			if (count == 3) {
				break;
			}
		}
		cout << tmp->val << ' ';
		tmp = tmp->next;
	}
}

/* 判斷鏈表是否有環 */
bool hasCycle(ListNode *head) {
	if (!head || !head->next) return false;

	ListNode *slow = head;
	ListNode *fast = head->next;

	while (fast != NULL && fast->next != NULL) {
		fast = fast->next->next;    // 快指針每次走兩步
		slow = slow->next;	        // 慢指針每次走一步
		if (fast == slow) {
			return true;
		}
	}
	return false;
}

  輸出結果:

結果截圖

  1. 尋找一個帶環鏈表的環的起點——LeetCode 142
      思路:分爲兩步進行,先判斷是否有環;若有環,再尋找環的起點。判斷是否有環和上一個例題相同,設置兩個快慢指針,快指針每次移動兩個節點,慢指針每次移動一個節點,若兩個指針相遇,則鏈表有環。設兩個指針相遇的節點爲c,如下圖所示:

示意圖
圖片來源:(Leetcode 142)Linked List Cycle (II) (快慢指針詳解)

  下面尋找環的起點。快指針繼續指向兩個指針第一次相遇的節點c,將滿指針指向鏈表的首個節點h,然後兩個指針每次同步向前移動一個節點,當兩個指針再次相遇時,相遇的節點即爲環的起點。
  

ListNode *detectCycle(ListNode *head) {
	if (!head || !head->next) return NULL;

	ListNode *fast = head;
	ListNode *slow = fast;

	while (fast && fast->next) {
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow) {		// 快慢指針相遇,則鏈表有環
			break;
		}
	}
	if (!fast || !fast->next) {	// 鏈表無環(節點數爲奇數時,fast爲空;爲偶數時,fast->next爲空)
		return NULL;
	}

	slow = head;	// 快指針從兩個指針相遇的地方出發,慢指針從鏈表的首個節點出發,同步移動
	while (fast != slow) {		// 兩個指針再次相遇的節點即爲環的起點
		fast = fast->next;
		slow = slow->next;
	}
	return fast;
}

  結果如下:(輸入輸出部分同上以例題)
在這裏插入圖片描述

  1. 單鏈表翻轉——LeetCode 206
      思路1:開闢新的內存,生成一個新的鏈表,採用尾插法,從後向前地將原鏈表的節點依次插入新的鏈表。這種方法比較簡單,但浪費空間,在此不贅述。
      思路2:採用兩個指針進行原地操作。原理入下:
    單鏈表翻轉
    (圖片來自中國大學MOOC浙江大學《數據結構》)

  上圖適用於翻轉鏈表中的K個節點。如上圖所示,指針new指向鏈表中已翻轉部分的首個節點,指針old指向鏈表中未翻轉鏈表的首個節點。指針向前遍歷的過程中,依次將old指向new,逐個翻轉節點,直至遍歷結束,即old爲空。(上圖中的鏈表是帶頭節點的,LeetCode中的鏈表均不帶頭節點,編寫代碼時需注意。)

ListNode* reverseList(ListNode* head) {
	ListNode *rev = NULL;			// 指向鏈表中已翻轉部分的頭部
	ListNode *unrev = head;			// 指向鏈表中未翻轉部分的頭部

	while (unrev) {
		ListNode *tmp = unrev->next;	// 保存未翻轉部分頭節點的位置
		unrev->next = rev;				// 節點翻轉
		rev = unrev;					// 更新rev
		unrev = tmp;					// 更新unrev
	}

	return rev;
}

  在本題中需要注意的是rev指針的初始位置,rev初始位置應指向NULL而不是鏈表的首個節點。若rev初始位置爲鏈表的首個節點,由於其不爲空,則整個鏈表翻轉完成後,以上圖爲例,最後一個節點1的下個節點不是空節點,而是翻轉之前的鏈表中1的下一個節點2,即翻轉後鏈表爲6,5,4,3,2,1,2,1,2,1… 。(自己就踩了這個坑。。。。。。)

  1. 翻轉單鏈表中的部分節點——LeetCode92
      思路與上一題完全相同,只需確定翻轉的起點和終點即可。
ListNode* reverseBetween(ListNode* head, int m, int n) {
	ListNode *rev = new ListNode(-1);	// 指向鏈表中已翻轉部分的頭部
	rev->next = head;
	
	int num = 0;
	while (num < m - 1) {
		if (rev->next) {
			rev = rev->next;
			num++;
		}
	}	
	ListNode *flag = rev;				// 此時rev指向待翻轉節點的前一個節點
	ListNode *unrev = rev->next;		// 指向鏈表中未翻轉部分的頭部

	int count = 0;
	int k = n - m + 1;
	while (count < k) {
		ListNode *tmp = unrev->next;	// 保存未翻轉部分頭節點的位置
		unrev->next = rev;				// 節點翻轉
		rev = unrev;					// 更新rev
		unrev = tmp;					// 更新unrev
		count++;
	}

	flag->next->next = unrev;
	flag->next = rev;

	if (m == 1) {
		return flag->next;
	}
	else {
		return head;
	}
}
  1. 交換每一對節點的位置——LeetCode24
      原理如下:
    在這裏插入圖片描述
    代碼:
ListNode* swapPairs(ListNode* head) {
	ListNode *ehead = new ListNode(-1);
	ehead->next = head;
	ListNode *pre = ehead, *cur = head;
	while (cur && cur->next) {
		pre->next = cur->next;
		cur->next = pre->next->next;
		pre->next->next = cur;

		pre = cur;
		cur = cur->next;
	}
	return ehead->next;
}
  1. 翻轉鏈表中的所有包含K個節點的一組節點——LeetCode25
      這一題是上一題的拓展。上一題是每次翻轉兩個節點,這一題是每次翻轉K個節點。所以這一題的思路與上一題類似,只是每一組節點需要做連續翻轉。除此之外,在進行翻轉之前,需確定是否滿足翻轉的條件,即待翻轉鏈表的長度要大於等於K。
ListNode* reverseKGroup(ListNode* head, int k) {
	ListNode *dummy = new ListNode(-1);
	dummy->next = head;
	ListNode *pre = dummy, *cur = pre->next;
	int len = 0;
	while (cur) {
		len++;
		cur = cur->next;
	}
	cur = pre->next;

	while (len >= k) {
		for (int i = 1; i < k; i++) {
			ListNode *t = pre->next;		// 記錄當前要翻轉節點翻轉後要指向的節點
			pre->next = cur->next;			// 記錄下一次要翻轉的節點翻轉後要指向的節點(即當前要翻轉的節點)
			cur->next = pre->next->next;	// 記錄下一次要翻轉的節點
			pre->next->next = t;	// 節點翻轉:每次翻轉的是cur指向的下一個節點
		}
		pre = cur;
		cur = pre->next;
		len -= k;
	}
	return dummy->next;
}
  1. 鏈表重排——LeetCode143
      先將鏈表的後半部分(不包括中間節點)翻轉,再 將這後半鏈表的節點依次插入到前半鏈表的相應位置。
void reorderList(ListNode* head) {
   if (!head || !head->next) return;

   ListNode *fast = head, *slow = head;
   while (fast->next && fast->next->next) {
   	slow = slow->next;	
   	fast = fast->next->next;
   }	// 此時first指向鏈表中點

   fast = slow->next;	// 從fast節點開始翻轉鏈表
   ListNode *rev = NULL;
   while (fast) {
   	ListNode *tmp = fast->next;
   	fast->next = rev;
   	rev = fast;
   	fast = tmp;
   }
   slow->next = rev;

   fast = head;		// 兩個指針分別從頭和中間向後遍歷
   while (slow->next && fast != slow) {
   	ListNode *t1 = fast->next;
   	ListNode *t2 = slow->next->next;
   	fast->next = slow->next;
   	fast->next->next = t1;
   	slow->next = t2;
   	fast = t1;
   }
}
  1. 判斷鏈表是否是迴文鏈表——LeetCode234
      要求O(n)時間和O(1)空間,則先將鏈表的前半部分或後半部分翻轉,再依次比較前後半個鏈表的節點是否相同。需要注意鏈表的節點數量的奇偶性,爲奇數時,尋找中間節點的slow指針指向節點中點,該節點不翻轉;爲偶數時,slow指針指向兩個中間節點的前一個,該節點需要翻轉。
bool isPalindrome(ListNode* head) {
	if (!head || !head->next) return true;

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

	bool isEven = false;
	if (fast->next) {		// fast爲倒數第二個節點,則節點數量爲偶數,要翻轉中間節點
		slow = slow->next;
		isEven = true;
	}

	fast = head;
	ListNode *rev = NULL;
	while (fast != slow) {	// 翻轉前半個鏈表		
		ListNode *tmp = fast->next;
		fast->next = rev;
		rev = fast;
		fast = tmp;
	}
	head->next = fast;		// 此時rev爲表頭

	//PrintList(rev);

	if (!isEven) {	// 節點數量爲奇數時,要從中點的後一個節點比較
		slow = slow->next;
	}	
	while (slow) {	// 比較翻轉後的鏈表的前後半部分是否相同 
		if (rev->val != slow->val) {
			return false;
		}
		else {
			rev = rev->next;
			slow = slow->next;
		}
	}
	return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章