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;
}
}
增加
删除当前节点后一个点
注:操作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
- Create a heap to store ListNode*, which should sort list nodes in the ascending order or node values.
- Insert the first node(head) of each list into the heap, so that we store all the k entries in the heap.
- Get the top element in heap and add in to the merged list.
- If the top element of heap is the last node in a list, pop it and go to step 3.
- If the top element of heap has followers, pop it and push its following node into heap.
- 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;
}