链表相关的算法

最近在某站发现的一门质量挺高的课程,以下为课程笔记!

基本操作

节点结构

typedef struct{
    int val;
    Node* next;
}Node;

查找操作

Node* search(int val){
    Node* pNode = root; // 根结点
    while(pNode != nullptr)
         if(pNode->val == val){
             return pNode;
          }
         pNode = pNode->next;
        }
     return nullptr;
 }

删除操作–给出要删除结点的前一个结点

void delNode(Node* prev){
	Node* curr = prev->next;
	prev->next = curr->next;
	delete curr;
}

删除操作–给出要删除的结点

void delNode(Node* curr){
	if(curr->next != nullptr){
		Node* temp = curr->next;
		curr->val = temp->val;
		curr->next = temp->next;
		delete temp;
	}else{
		Node* pNode = root; // 头节点
		while(pNode->next != curr){
			pNode = pNode->next;
		}
		pNode->next = nullptr;
		delete curr;
	}
}

相关算法

链表元素去重

Remove Duplicates from Sorted List
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

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

Dummy Node技巧
考虑:
a. 哪个节点的next指针会受到影响,则需要修正该指针
b.如果待删除节点是动态开辟的内存空间,则需要释放这不部分内存(c/c++)
利用dummy node是一个非常好用的trick:只要涉及操作head节点,当头节点操作不确定的时候,不妨创建dummy node:
ListNode
dummy = new ListNode(0);
dummy->next = head;
*
.
.

Remove Duplicates from Sorted List ||
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

Node* deleteDuplicates(Node* head){
	if(head == nullptr){
		return nullptr;
	}
	Node* dummy = new Node(0);
	dummy->next = head;
	Node* node = dummy;
	while(node->next != nullptr && node->next->next != nullptr){
		if(node->next->val == node->next->next->val){
			int val_pre = node->next->val;
			while(node->next != nullptr && val_pre == node->next->val){
				Node* temp = node->next;
				node->next = temp->next;
				delete temp;
			}
		}else{
			node = node->next;
		}
	}
	return dummy->next;
}

给定一个链表和一个值,写一个reorder重排这个链表,使比值小的全在值左边,比值大的全在值右边

Node* partition(Node* head, int x){
	if(head == nullptr){
		return nullptr;
	}
	Node* dummyleft = new Node(0);
	Node* dummyright = new Node(0);
	Node* left = dummyleft;
	Node* right = dummyright;
	Node* node = head;
	while(node->next != nullptr){
		if(node->val < x){
			left->next = node;
			left = left->next;
		}else{
			right->next = node;
			right = right->next;
		}
		node = node->next;
	}
	left->next = dummyright->next;
	right->next = nullptr;
	return dummyleft->next;
}

追赶指针技巧

对于寻找list某个特定位置的问题,不妨用两个chaser和runner, 以不同的速度遍历list,找到目标位置: Node chaser = head,runner = head. 并且可以用一个简单的小test case来验证

寻找链表的中点

Node* midpoint(Node* head){
	if(head == nullptr){
		return nullptr;
	}
	Node* chaser = head, runner = head;
	while(runner && runner->next){
		runner = runner->next->next;
		chaser = chaser->next;
	}
	return chaser;
}

寻找链表倒数第k个结点

Node* findkthtoLast(Node* head, int k){
	if(head == nullptr || k < 0){
		return nullptr;
	}
	Node* runner = head;
	Node* chaser = head;
	while(k){
		runner = runner->next;
	}
	if(runner == nullptr){
		return nullptr;
	}
	while(runner->next != nullptr){
		runner = runner->next;
		chaser = chaser->next;
	}
	return chaser;
}

给定一个有环的链表,寻找环开始的结点

分析:寻找某个特定位置,用runner technique 。Runner以两倍速度遍历,假定有loop,那么runner与chaser一定能在某点相遇。相遇后,再让chaser从head触发再次追赶runner,第二次相遇的结点为loop开始的位置

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

将链表第k个元素之后的结点移到最前面

Example:
list = 10->20->30->40->50->60 k = 4
change to 50->60->10->20->30->40

void rotate(Node** root, int k){
	if(k == 0){
		return;
	}
	Node* current = root;
	for(int count = 1; count < k && current != nullptr;count++){
		current = current->next;
	}
	if(current == nullptr){
		return;
	}
	Node *kthNode = current;
	while(current->next != nullptr){
		current = current->next;
	}
	current->next = root;
	root = kthNode->next;
	kthNode->next = nullptr;
}

.
模式识别
1 . 在遍历linked list时,注意每次循环只处理一个或一对节点。核心的节点只处理当前这一个,否则很容易出现重复处理的问题。

.

反转链表

Reverse Linked List
Reverse the linked list and return the new head.

—递归版本—

Node* reverseList(Node* head){
	if(head == nullptr){
		return nullptr;
	}
	Node* prev = nullptr;
	while(head != nullptr){
		Node* curr = head;
		head = head->next;
		curr->next = prev;
		prev = curr;
	}
	return prev;
}

—非递归版本—

Node* reverseList(Node* head){
	if(head == nullptr){
		return head;
	}
	if(head->next == nullptr){
		return head;
	}
	Node* newHead = reverseList(head->next);
	head->next->next = head;
	head->next = nullptr;
	return newhead;
}

.
模式识别
2. Swap Node问题
3. 交换两个节点,不存在删除的话,两个节点的prev节点的next指针,以及这两个节点的next指针,会受到影响。总是可以
a. 先交换两个prev节点的next指针的值
b. 再交换这两个节点的next指针的值
无论这两个节点的相对位置和绝对位置如何,以上的处理方式总是成立

.

交换两个相邻的节点

Swap Adjacent Nodes
Given a linked list,swap every two adjacent nodes and return its head.

Node* swapPairs(Node* head){
	if(head == nullptr){
		return head;
	}
	Node* dummy = new Node(0);
	dummy->next = head;
	Node* prev = dummy;
	Node* node1 = head;
	Node* node2 = head->next;
	while(node1 && node1->next != nullptr){
		node2 = node1->next;
		prev->next = node1->next;
		
		node1->next = node2->next;
		node2->next = node1;

		prev = node1;
		node1 = prev->next;
	}
	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, whick 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.

Part a–

Node* addTwoNumbers(Node* l1, Node* l2){
	Node dummy(0);
	Node* p = &dummy;
	int cn = 0;
	while(l1 || l2){
		int val = cn + (l1 ? l1->val : 0) + (l2 ? l2->val : 0);
		cn = val / 10;
		val = val % 10;
		p->next = new Node(val);
		p = p->next;
		if(l1){
			l1 = l1->next;
		}
		if(l2){
			l2 = l2->next;
		}
	}
	if(cn != 0){
		p->next = new Node(cn);
		p = p->next;
	}
	return dummy->next;
}

Partb–
解题分析:对于a,靠前节点的解不依赖靠后节点,因此顺序遍历求解即可。对于b,靠前节点的解依赖于靠后节点(进位),因此必须用递归或栈处理。并且,subproblem返回的结果,可以是一个自定义的结构(进位 + sub-list)。当然,也可以reverse List之后再用a的解法求解。

合并两个或多个排序的链表

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

Node* mergeTwoLists(Node* l1, Node* l2){
	Node* dummy = new Node(0);
	Node* curr = dummy;
	while(l1 && l2){
		if(l1->val <= l2->val){
			curr->next = l1;
			l1 = l1->next;
		}{
			curr->next = l2;
			l2 = l2->next;
		}
		curr = curr->next;
	}
	curr->next = (l1 != nullptr) ? l1 : l2;
	return dummy->next;
}

Merge K Sorted List

Node* mergeKLists(vector<Node*>&lists){
	if(lists.size() == 0)return nullptr;
	Node* p = lists[0];
	for(int i = 0; i < l1sts.size(); i++){
		p = mergeTwoLists(p, lists[i]);
	}
	return p;
} 

未完!

发布了21 篇原创文章 · 获赞 0 · 访问量 163

猜你喜欢

转载自blog.csdn.net/qq_45227330/article/details/105060058