链表算法笔记 2023.2.26复习

1 基本操作:

c初始化链表

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) {}
};
ListNode* listInit(vector<int>& vec) {
	ListNode* head = new ListNode(vec[0]);
	ListNode* cur = head;
	for (int i = 1; i < vec.size(); ++i) {
		cur->next = new ListNode(vec[i]);
		cur = cur->next;
	}
	return head;
}

void listPrint(ListNode* head) {
	while (head != NULL) {
		cout << head->val << " ";
		head = head->next;
	}
}

增加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vlHqfOal-1662777167395)(C:\Users\13415\AppData\Roaming\Typora\typora-user-images\image-20220812064946767.png)]

删除当前节点后一个点

在这里插入图片描述

注:操作Linked List时务必注意边界条件:curr == head, curr == tail 或者 curr == NULL

直接删除当前节点

在这里插入图片描述

删除排序链表中的重复元素

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

Given a sorted linked list, delete all duplicates such that each element appear only once. For example, Given 1->1->2, return 1->2. Given 1- >1->2->3->3, return 1->2->3.

ListNode* deleteDuplicates(ListNode* head) {
	if (head == NULL)
		return NULL;

	ListNode* node = head;
	while (node->next != NULL) {
		if (node->val == node->next->val) {
			ListNode* temp = node->next;
			node->next = node->next->next;
			delete temp;
		}
		else {
			node = node->next;
		}
	}
	return head;
}

2 Dummy Node技巧

考虑:
a. 哪个节点的next指针会受到影响,则需要修正该指针
b. 如果待删除节点是动态开辟的内存空间,则需要释放这部分空间(C/
C++)
利⽤dummy node是⼀个⾮常好⽤的trick:只要涉及操作head节点,当头节点操作不确定的时候,不妨创建dummy node:

ListNode *dummy = new ListNode(0); 
dummy->next = head; 

删除排序链表中的重复元素 II

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.

For example, Given 1->2->3->3->4->4->5, return 1->2->5. Given 1->1->1->2->3, return 2->3.

ListNode* deleteDuplicates(ListNode* head) {
	if (head == NULL)return NULL;

	ListNode* dummy = new ListNode(0);
	dummy->next = head;
	ListNode* node = dummy;
	while (node->next!=NULL && node->next->next !=NULL) {
		if ( node->next->val == node->next->next->val) {
			int val = node->next->val;
			while (node->next != NULL && node->next->val == val) {
				ListNode* temp = node->next;
				node->next = node->next->next;
				delete temp;
			}
		}
		else {
			node = node->next;
		}
	}
	return dummy->next;
}
将节点x的前部分都小于x,后部分都大于x Partition List

Given a linked list and a value x, write a function to reorder this list such that all nodes less than x come before the nodes greater than or equal to x.
解题分析:将list分成两部分,但两部分的head节点连是不是null都不确定。但总是可以创建两个dummy节点然后在此基础上append,这样就不⽤处理边界条件了。

ListNode* partition(ListNode* head, int x) {
	if (head == NULL)return NULL;

	ListNode* leftfDummy = new ListNode(0);
	ListNode* left = leftfDummy;
	ListNode* rightDummy = new ListNode(0);
	ListNode* right = rightDummy;
	ListNode* node = head;
	while (node != NULL) {
		if (node->val < x) {
			left->next = node;
			left = left->next;
		}
		else {
			right->next = node;
			right = right->next;
		}
		node = node->next;
	}
	right ->next= NULL;
	left->next = rightDummy->next;

	return leftfDummy->next;
}

3 追赶指针技巧

对于寻找list某个特定位置的问题,不妨⽤两个变量chaser与runner,以不同的速 度遍历list,找到⽬标位置: ListNode *chaser = head, *runner = head。并且可 以⽤⼀个简单的⼩test case来验证(例如长度为4和5的list)

找到链表的中点 Middle Point

Find the middle point of linked list.
解题分析: 寻找特定位置,runner以两倍速前进,chaser ⼀倍速,当runner到达tail时,chaser即为所求解。

ListNode* midpoint(ListNode* head) {
	ListNode* chaser = head, * runner = head;
	if (head == NULL)
		return NULL;

	while (chaser->next && runner->next->next) {
		chaser = chaser->next;
		runner = runner->next->next;
	}
	return chaser;
}
倒数第k个元素 kth to Last element

Find the kth to last element of a singly linked list
解题分析:之前类似。只是runner与chaser以相同倍速前进,但runner提前k步出发

ListNode* findkthtoLast(ListNode* head, int k) {
	ListNode* chaser = head;
	ListNode* runner = head;

	if (head == NULL)
		return NULL;
	if (k < 0)
		return NULL;

	for (int i = 0; i < k; i++)
		runner = runner->next;

	if (runner == NULL)
		return NULL;

	while (runner != NULL) {
		chaser = chaser->next;
		runner = runner->next;
	}
	return chaser;
}

如何判断⼀个单链表中有环?

Given a linked list, determine if it has a cycle in it.

在这里插入图片描述

如何判断⼀个单链表中有环? Circular List Node

Given a circular linked list, return the node at the beginning of the loop
解题分析:寻找某个特定位置,⽤runner technique。Runner以两倍速度遍历,假定有loop,那么runner与chaser⼀定能在某点相遇。相遇后,再让chaser从head出发再次追赶runner,第⼆次相遇的节点为loop开始的位置。
如何找到第⼀个相交的节点?
在这里插入图片描述
runner指针走过的距离都为chaser 指针的 2 倍
在这里插入图片描述
c(从相遇点到入环点的距离)+(n-1)圈的环长,恰好等于从链表头部到入环点的距离。

当发现runner 与chaser 相遇时,我们再额外使用一个指针ptr。起始,它指向链表头部;随后,它和chaser 每次向后移动一个位置。最终,它们会在入环点相遇。

判断两个单链表是否有交点?

先判断两个链表是否有环,如果⼀个有环⼀个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断⼀个链表上的Z点是否在另⼀个链表上。

如何找到第⼀个相交的节点?

求出两个链表的⻓度L1,L2(如果有环,则将Y点当做尾节点来算),假设L1<L2,⽤两个指针分别从两个链表的头部开始⾛,⻓度为L2的链表先⾛L2-L1,然后两个⼀起⾛,直到⼆者相遇。

K个链表元素进行翻转(后节点移到前面去) Rotate List

Given a list, rotate the list to the right by k places, where k is non-negative.

for example,list=10->20->30->40->50->60 change to 50 -> 60->10->20->30->40

ListNode* rotate(ListNode*& head_ref, int k) {
	if (k == 0)return head_ref;

	ListNode* current = head_ref;
	int cnt = 1;
	while (cnt<k && current!=NULL) {//防止指针为空时next报错
		current = current->next;
		cnt++;
	}
	if (current == NULL)
		return current;
	ListNode* kthNode = current;
	while (current->next !=NULL){
		current = current->next;
	}
	current->next = head_ref;
	head_ref = kthNode->next;
	kthNode->next = NULL;
	return head_ref;
}

4 模式识别

1.在遍历Linked list时,注意每次循环内只处理⼀个或⼀对节点。核⼼的节点只处理当前这⼀个,否则很容易出现重复处理的问题。

翻转链表 Reverse Linked List

Reverse the linked list and return the new head.

循环遍历linked-list, 每次只处理当前指针的next 变量。

⾮递归 vs 递归
在这里插入图片描述

ListNode* reverseList(ListNode*& head) {
	ListNode* pre = NULL;
	while (head != NULL) {
		ListNode* cur = head;
		head = head->next;
		cur->next = pre;
		pre = cur;
	}
	return pre;
}

递归版

在这里插入图片描述

对于递归的head节点和head->next节点的处理步骤都相同为:

1 把head->next指向head

2 把head的下一个节点指为空节点

ListNode* reverseList(ListNode*& head) {
	if (head == NULL)return NULL;
	if (head->next == NULL)return head;
	//一直先递归到最后在操作,返回值的始终是头节点
	ListNode* newhead = reverseList(head->next);
	head->next->next = head;
	head->next = NULL;
	return newhead;
}

2 Swap Node 问题

交换两个节点,不存在删除的话,两个节点的prev节点的next指针,以 及这两个节点的next指针,会受到影响。总是可以

a. 先交换两个prev节点的next指针的值;

b. 再交换这两个节点的next指针的值。

⽆论这两个节点的相对位置和绝对位置如何,以上的处理⽅式总是成⽴

两两交换链表中的节点 Swap Adjacent Nodes

在这里插入图片描述

Given a linked list, swap every two adjacent nodes and return its head.
输入:head = [1,2,3,4]
输出:[2,1,4,3]

ListNode* swapPairs(ListNode* head) {
	if (head == NULL)return head;
	ListNode* dummy = new ListNode(0);
	dummy->next = head;
	ListNode* pre = dummy;
	ListNode* node1 = dummy;

	while (node1->next != NULL && node1->next->next != NULL) {
		node1 = node1->next;
		ListNode* node2 = node1->next;
		pre->next = node2;
		node1->next = node2->next;
		node2->next = node1;
		pre = node1;
	}
	return dummy->next;
}

3 同时处理两个linked list的问题,循环的条件⼀般可以⽤ while( l1 && l2 ) ,再处理剩下非NULL 的list。这样的话,边界情况特殊处理,常规情况常规处理。

两个链表相加 Add List Sum

Given two linked lists, each element of the lists is a integer. Write a function to return a new list, which is the “sum” of the given two lists.

Part a. Given input (7->1->6) + (5->9->2), output 2->1->9.

Part b. Given input (6->1->7) + (2->9->5), output 9->1->2.

解题分析:

对于a,靠前节点的解不依赖靠后节点,因此顺序遍历求解即可。

对于b,靠前节点 的解依赖于靠后节点(进位),因此必须⽤递归或栈处理。并且,subproblem返回的结果,可 以是⼀个⾃定义的结构(进位 + sub-list)。当然,也可以reverse List之后再⽤a的解法求解。

Part a代码:

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
	ListNode* dummy = new ListNode(0);
	ListNode* current = dummy;
	int jin = 0;
	while (l1 || l2) {
		int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0)+jin;
		jin = sum / 10;
		int yu = sum % 10;
		ListNode* node = new ListNode(yu);
		current->next = node;
		current = node;
		if (l1)
			l1 = l1->next;
		if(l2)
			l2 = l2->next;;
	}
	if (jin != 0) {
		current->next = new ListNode(jin);
		current = current->next;
	}
	current->next = NULL;
	return dummy->next;
}
合并两个有序链表 Merge Two Sorted List

Merge two sorted linked lists and return it as a new list.

ListNode* mergeTwoSortedLists(ListNode* l1, ListNode* l2) {
	ListNode* dummy = new ListNode(0);
	ListNode* current= dummy;
	while (l1 && l2) {
		if (l1->val <= l2->val) {
			current->next = l1;
			l1=l1->next;
		}
		else {
			current->next = l2;
			l2 = l2->next;
		}
		current = current->next;
	}
	current->next=(l1==NULL ? l2 : l1);
	return dummy->next;
}
合并 K 个升序链表Merge K Sorted List
法一:不断合并两个不同的链表
ListNode* mergeTwoSortedLists(ListNode* l1, ListNode* l2) {
	ListNode* dummy = new ListNode(0);
	ListNode* current= dummy;
	while (l1 && l2) {
		if (l1->val <= l2->val) {
			current->next = l1;
			l1=l1->next;
		}
		else {
			current->next = l2;
			l2 = l2->next;
		}
		current = current->next;
	}
	current->next=(l1==NULL ? l2 : l1);
	return dummy->next;
}
ListNode *mergeKLists(vector<ListNode *> &lists) { 
	if(lists.size() == 0) return NULL; 
	ListNode *p = lists[0]; 		
    for(int i =1; i< lists.size(); i++) { 
		p = mergeTwoSortedLists(p, lists[i]); 
    } 
	return p; 
}
法二:小根堆

Better Solution?

HEAP

  1. Create a heap to store ListNode*, which should sort list nodes in the ascending order or node values.
  2. Insert the first node(head) of each list into the heap, so that we store all the k entries in the heap.
  3. Get the top element in heap and add in to the merged list.
  4. If the top element of heap is the last node in a list, pop it and go to step 3.
  5. If the top element of heap has followers, pop it and push its following node into heap.
  6. Go to step 3 until the heap is empty.
class greaterListNode {
public:
	bool operator()(ListNode* x, ListNode* y) { return x->val > y->val; }
};

ListNode* mergeKlists(vector<ListNode*>& list) {
	if (list.size() == 0)return NULL;
	if (list.size() == 1)return list[0];
	ListNode* dummy = new ListNode(-1);
	ListNode* current = dummy;
	priority_queue<ListNode*, vector<ListNode*>, greaterListNode> pq;
	for (auto& ls : list)
		pq.push(ls);

	while (!pq.empty()) {
		ListNode* temp=pq.top();
		current->next = temp;
		current = current->next;
		pq.pop();
		if (temp->next != NULL)
			pq.push(temp->next);
	}
	return dummy->next;
}

4 如果对靠前节点的处理必须在靠后节点之后,即倒序访问问题,则⽤ recursion(递归),或者等效地,stack来解决。

例题

Traverse the linked list reversely.

void traverse(ListNode *head) { 
 if (head == NULL) 
 	return; 
 traverse(head->next); 
 visit(head); 
}
重排链表 Reorder List

Given a singly linked list L: L0→L1→…→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…

You must do this in-place without altering the nodes’ values.

For example, Given 1->2->3->4->null, reorder it to 1->4->2->3->null

ListNode* reverse(ListNode* head) {
	ListNode* pre= NULL;
	while (head!=NULL){
		ListNode* temp = head->next;
		head->next = pre;
		pre = head;
		head = temp;
	}
	return pre;
}

ListNode* findMiddle(ListNode*& head) {//这里是将分为了两个链表
	if (NULL == head || NULL == head->next)
		return head;
	ListNode* chaser = head;
	ListNode* runner = head;
	ListNode* chaserpre = head;//这里是将分为了两个链表
	while (chaser != NULL && runner != NULL) {
		chaserpre = chaser;
		chaser = chaser->next;
		runner = runner->next->next;
	}
	chaserpre->next = NULL;//这里是将分为了两个链表
	return chaser;
}

ListNode* merge(ListNode* left, ListNode* right) {
	ListNode* dummy = new ListNode(0);
	ListNode* head = dummy;
	while (left != NULL & right != NULL) {
		dummy->next = left;
		left = left->next;
		dummy = dummy->next;
		dummy->next = right;
		right = right->next;
		dummy = dummy->next;
	}
	dummy->next = (left != NULL) ? left : right;
	return head->next;
}

ListNode* reorderList(ListNode*& head) {
	if (head==NULL || head->next == NULL || head->next->next == NULL)
		return NULL;
	
	ListNode* middle = findMiddle(head);
	middle = reverse(middle);
	ListNode* ans = merge(head, middle);
	return ans;

}

复制带随机指针的链表 Clone a linked list with next and random pointer

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

在这里插入图片描述
在这里插入图片描述

法一:

RandomListNode* copyRandomList(RandomListNode* head) {
	if (!head == NULL)return NULL;
	map<RandomListNode*, RandomListNode*> mp;
	mp.clear();
	RandomListNode* res = new RandomListNode(0);
	RandomListNode* p = head;
	RandomListNode* q = res;

	while (p) {
		RandomListNode* tmp = new RandomListNode(p->val);
		q->next = tmp;
		mp[p] = tmp;
		p = p->next;
		q = q->next;
	}
	p = head;
	q = res->next;

	while (p) {
		if (p->random == NULL) {
			q->random = NULL;
		}
		else {
			q->random = mp[p->random];
		}
		p = p->next;
		q = q->next;
	}
	return res->next;
}

在这里插入图片描述

法二:

RandomListNode* copyRandomList(RandomListNode* head) {
	RandomListNode* cur = head;
	//double list
	while (cur != NULL) {
		RandomListNode* temp = new RandomListNode(0);
		temp->next = cur->next;
		cur->next = temp;
		cur = temp->next;
	}
	//copy random pointer
	cur = head;
	while (cur != NULL) {
		RandomListNode* temp = cur->next;
		if (cur->random != NULL)
			temp->random = cur->random->next;
		cur = temp->next;
	}
	//decouple two links
	cur = head;
	RandomListNode* dup = head == NULL ? NULL : head->next;
	while (cur != NULL) {
		RandomListNode* tmp = cur->next;
		cur->next = tmp->next;
		if (tmp->next != NULL)
			tmp->next = tmp->next->next;
		cur = cur->next;
	}
	return dup;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值