算法模板-----链表

Posted 栋次大次

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法模板-----链表相关的知识,希望对你有一定的参考价值。

链表基础知识点

  • null异常的处理
  • dummy node节点
  • 快慢指针
  • 插入一个节点到排序链表
  • 从一个链表中移除一个节点
  • 反转链表
  • 合并两个链表
  • 找到链表的中间节点

通过下面的练习,大部分链表题都能可以应对。
remove-duplicates-from-sorted-list
remove-duplicates-from-sorted-list-ii
reverse-linked-list
reverse-linked-list-ii
merge-two-sorted-lists
partition-list
sort-list
reorder-list
linked-list-cycle
linked-list-cycle-ii
palindrome-linked-list
copy-list-with-random-pointer

常见题型

remove-duplicates-from-sorted-list

给定一个排序链表,删除所有重复的元素,使每个元素只出现一次

// 递归
ListNode* deleteDuplicates(ListNode* head)
	if(head == NULL || head->next == NULL) return head;
	head->next = deleteDuplicates(head->next);
	return head->val == head->next->val ? head->next : head;


// 非递归
ListNode* deleteDuplicates(ListNode* head)
	ListNode *cur = head;
	while(cur != NULL && cur->next != NULL)
		if(cur->val == cur->next->val)
			cur->next = cur->next->next; //跳过重复节点
		else
			cur = cur->next;
		
	
	return head;

remove-duplicates-from-sorted-list-ii

给定一个排序链表,删除所有重复的节点,只保留原始链表中没有重复出现的。
思路:链表的头节点可能会被删除,需要定义一个dummy node辅助进行删除过程
dummy node 的使用在面试中很常见

ListNode* deleteDuplicates(ListNode* head)
	if(head == NULL || head->next == NULL) return head;
	
	ListNode *dummy = new ListNode(-1); //定义dummy node节点进行辅助
	dummy->next = head;
	head = dummy;
	
	int temp;
	while(head->next != NULL && head->next->next != NULL)
		if(head->next->val == head->next->next->val)
			temp = head->next->val;
			while(head->next != NULL && head->next->val == temp)
				// 查找所有相同的节点进行删除
				head->next = head->next->next;
			
		else
			head = head->next;
		
	
	return dummy->next;

注意点:

  • A->B->C 删除B => A.next = C
    删除用一个dummy node节点进行辅助(允许头节点改变)
    访问X.next、X.value一定要保证X!=NULL(这个很重要,一定要细心)

reverse-linked-list

反转单链表
思路:用一个prev节点保存前向指针,temp保存后向的临时指针。
反转链表的套路要记住,可能在其他题中会使用

// 迭代
ListNode* reverseList(ListNode* head)
	ListNode* pre = NULL;
	ListNode* cur = head;
	while(cur != NULL)
		ListNode *later = cur->next;
		cur->next = pre;
		pre = cur;
		cur = later;
	
	return pre;



// 递归
ListNode* reverseList(ListNode* head)
	if(head == NULL || head->next == NULL) return head;
	
	ListNode *h = reverseList(head->next);
	head->next->next = head;
	head->next = NULL;
	return h;

reverse-linked-list-ii

反转从位置m到n的链表,使用一趟扫描完成。(1<=m<=n<=链表长度)
思路:先遍历到m处,反转,再拼接后边的。
需要使用dummy node,可能会改变head

ListNode* reverseBetween(ListNode* head, int m, int n)
	if(head == NULL || head->next == NULL) return head;
	ListNode *dummy = new ListNode(-1);
	ListNode *pre = dummy;
	dummy->next = head;
	
	for(int i = 0; i < m-1; i++)
		pre = pre->next;
	
	
	ListNode *cur = pre->next; // m-1 从此处开始反转
	for(int i = m; i < n; i++)
		ListNode *temp = cur->next;
		cur->next = temp->next;
		temp->next = pre->next;
		pre->next = temp;
	
	return dummy->next;

merge-two-sorted-lists

将两个升序链表合并为新的升序链表,在原链表上进行操作
思路:定义dummy node,链接各个元素

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
	if(l1 == nullptr) return l2;
	if(l2 == nullptr) return l1;
	
	if(l1->val <= l2->val) 
		l1->next = mergeTwoLists(l1->next, l2);
		return l1;
	 else
		l2->next = mergeTwoLists(l1, l2->next);
		return l2;
	

partition-list

分隔链表,使所有小于x的节点出现在大于或等于x的节点之前。
保留两个分区中每个节点的初始相对位置。
思路:将大于等于x的节点,放到另外一个链表中,最后链接这两个链表

ListNode* partition(ListNode* head, int x)
	if(!head) return head;
	
	ListNode *beforeNode = new ListNode(-1);
	ListNode *before = beforeNode;
	ListNode *afterNode = new ListNode(-1);
	ListNode *after = afterNode;
	
	while(head != NULL) 
		if(head->val < x)
			before->next = head;
			before = before->next;
		else
			after->next = head;
			after = after->next;
		
		head = head->next;
	
	after->next = NULL;
	before->next = afterNode->next;
	return beforeNode->next;

当头节点不确定的时候需要使用dummy node

sort-list

在O(nlogn)时间复杂度下,常数级空间复杂度下,对链表进行排序(升序)
思路:使用归并排序,找中点进行合并操作

ListNode* sortList(ListNode* head)
	if(head == NULL || head->next == NULL) return head;
	
	// 使用快慢指针找到中点
	ListNode *slow = head;
	ListNode *fast = head->next;
	while(fast != NULL && fast->next != NULL)
		slow = slow->next;
		fast = fast->next->next;
	
	ListNode *middleNode = slow->next;
	slow->next = NULL;
	
	ListNode *left = sortList(head);
	ListNode *right = sortList(middleNode);
	return mergeSortList(left, right); // 合并


ListNode* mergeSortList(ListNode* left, ListNode* right)
	// 升序合并两个序列
	ListNode *dummy = new ListNode(-1);
	ListNode *cur = dummy;
	
	while(left != NULL && right != NULL)
		if(left->val < right->val)
			cur->next = left;
			left = left->next;
		else
			cur->next = right;
			right = right->next;
		
		cur = cur->next;
	
	
	while(left != NULL) //将剩余的left接在后边
		cur->next = left;
		cur = cur->next;
		left = left->next;
	
	
	while(right != NULL)
		// 将剩余的right接在后边
		cur->next = right;
		cur = cur->next;
		right = right->next;
	
	
	return dummy->next;

注意:

  • 使用快慢指针的时候需要判断fast 及 fast->next 是否为null
  • 递归mergeSort需要断开中间节点
  • 递归返回条件为head = null 或者 head.next = null

reorder-list

给定一个单链表,L: L0->L1->…->Ln-1->Ln,将其重新排序为:L0->Ln->L1->Ln-1…
不能只是单纯改变节点的值,需要进行节点变换
思路,找到中间节点,反转后边部分,然后合并前后两个链表

void reorderList(ListNode* head)
	if(head == nullptr) return;
	
	// 快慢指针找到中点
	ListNode *slow = head;
	ListNode *fast = head->next;
	while(fast != nullptr && fast->next != nullptr)
		slow = slow->next;
		fast = fast->next->next;
	
	
	ListNode *middleNode = slow->next;
	ListNode *right = reverseList(slow->next); //反转后边的链表
	slow->next = nullptr;
	head = mergeTwoList(head, right);


ListNode *mergeTwoList(ListNode *left, ListNode *right)
	// 合并两个链表
	ListNode *dummy = new ListNode(-1);
	ListNode *cur = dummy;
	bool flag = true; // true: left, false: right
	while(left != nullptr && right != nullptr)
		if(flag)
			cur->next = left;
			left = left->next;
		else
			cur->next = right;
			right = right->next;
		
		flag = !flag;
		cur = cur->next;
	
	while(left != nullptr)
		// 拼接left剩余部分
		cur->next = left;
		cur = cur->next;
		left = left->next;
	
	while(right != nullptr)
		//拼接right剩余部分
		cur->next = right;
		cur = cur->next;
		right = right->next;
	
	return dummy->next;


ListNode *reverseList(ListNode *root)
	//反转链表
	ListNode *pre = nullptr;
	ListNode *cur = root;
	while(cur != nullptr)
		ListNode *temp = cur->next;
		cur->next = pre;
		pre = cur;
		cur = temp;
	
	return pre;

linked-list-cycle

给定一个链表,判断是否有环
思路:快慢指针,快慢指针相同,则有环。
如果有环,在环内每走一步快慢指针距离会减一

bool hasCycle(ListNode* head)
	if(head == NULL) return false;
	
	ListNode *slow = head;
	ListNode *fast = head->next;
	while(fast != NULL && fast->next != NULL)
		if(fast == slow) return true;
		fast = fast->next->next;
		slow = slow->next;
	
	return false;

linked-list-cycle-ii

给定一个链表, 判断入环的第一个节点,没有环则返回-1。
证明:
假设链表中环外部分的长度为a,slow进入环内又走了b的距离与fast相遇,此时fast已经走完了环的n圈,因此fast走过的距离为a+n(b+c)+b=a+(n+1)b+nc。
根据题意,fast走过的距离使slow走过距离的2倍。则:
a+(n+1)b+nc=2(a+b) => a = c+(n-1)(b+c)。
所以当slow与fast相遇时,我们再额外使用一个指针ptr,指向链表头部,随后它与slow每次向后移动一个位置。最终会在入环点相遇。

ListNode* detectCycle(ListNode* head)
	if(head == NULL) return head;
	
	ListNode *fast = head->next;
	ListNode *slow = head;
	while(fast != NULL && fast->next != NULL)
		if(fast == slow)
			//第一次相遇,判断有环
			//调整slow和fast的步长,等待第二次相遇
			slow = head;
			fast = fast->next;
			while(fast != slow)
				//第二次相遇 入环口
				fast = fast->next;
				slow = slow->next;
			
			return slow;
		
		fast = fast->next->next;
		slow = slow->next;
	
	return NULL;

注意:

  • 指针比较时直接比较指针,不要用值进行比较,链表中可能存在重复值
  • 第一次相遇后,快指针需要从下一个节点开始和头指针一起匀速运动

另一种方式是fast=head, slow=head。

ListNode *detectCycle(ListNode *head)
	if(head == NULL) return head;
	
	ListNode *fast = head;
	ListNode *slow = head;
	while(fast != NULL && fast->next != NULL)
		fast = fast->next->next;
		slow = slow->next;
		if(fast == slow)
			fast = head;
			while(fast != slow)
				//第二次相遇就是环的入口
				fast = fast->next;
				slow = slow->next;
			
			return slow;
		
	
	return NULL;

两个不同点:

  • fast初始化为head.next 则中点在slow.next
  • fast初始化为head,则中点在slow。
    一般使用fast=head.next较多,这样可以知道中点的上一个节点,可以进行删除操作。

palindrome-linked-list

判断一个链表是否是回文链表
找到中点,反转后边的链表,前后链表进行比较

bool isPalindrome(ListNode* head)
	if(head == NULL) return true;
	
	// 快慢指针找到中点
	// 中点是slow->next
	ListNode *slow = head, *fast = head->next;
	while(fast != NULL && fast->next != NULL)
		slow = slow->next;
		fast = fast->next->next;
	
	
	ListNode *tail = reverseList(slow->next);
	// 断开两个链表
	slow->next = NULL;
	while(head != NULL && tail != NULL)
		if(head->val != tail->val) return false;
		head = head->next;
		tail = tail->next;
	
	return true;


ListNode *reverseList(ListNode *head)
	// 反转链表
	if(head == NULL) return head;
	ListNode *pre = NULL;
	while(head != NULL)
		ListNode *temp = head->next;
		head->next = pre;
		pre = head;
		head = temp;
	
	return pre;

copy-list-with-random-pointer

给定一个链表,每个节点包含一个额外增加的随机指针,该指针指向链表中的任意节点或空节点,返回这个链表的深拷贝。
思路:用hash表存储指针,复制节点跟在原节点的后边。

Node *copyRandomList(Node* head)
	if(head == NULL) return head;
	
	// 复制节点跟在原节点后边
	// 1->1'->2->2'...
	Node *cur = head;
	while(cur != NULL)
		Node *clone = new Node(cur->val);
		clone->next = cur->next;
		cur->next = clone;
		cur = clone->next;
	
	
	//处理随机指针
	cur = head;
	while(cur != NULL)
		cur->next->random = (cur->random != NULL) ? cur->random->next : NULL;
		cur = cur->next->next;
	
	
	// 分离两个链表
	cur = head;
	Node *cloneRandom = cur->next;
	while(cur != NULL && cur->next != NULL)
		Node *temp = cur->next;
		cur->next = cur->next->next;
		cur = temp;
	
	//原始链表头 head
	//克隆链表头 cloneRandom
	return cloneRandom;

以上是关于算法模板-----链表的主要内容,如果未能解决你的问题,请参考以下文章

算法小抄题目(按章节)

算法系列——合并K个升序链表

算法系列——合并K个升序链表

算法系列——合并K个升序链表

算法系列——合并K个升序链表

算法leetcode|23. 合并K个升序链表(rust重拳出击)