记录剑指Offer中链表
相关的题目思路及解答。
文章会给出思路和代码,同时为了方便本地调试,还会提供相应的测试用例。
如果要在本地调试,需要先包含下面的头文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #ifndef MYLISTNODE #define MYLISTNODE
#include <iostream>
using namespace std;
struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} };
void printList(ListNode *head) { while (head != NULL) { cout << head->val << "->"; head = head->next; } cout << "NULL" << endl; } #endif
|
剑指Offer6:从尾到头打印链表
题目:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
思路1:使用栈先入后出的特性,实现反向打印
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| vector<int> reversePrint(ListNode *head) { stack<int> stk; vector<int> vec; while (head != nullptr) { stk.push(head->val); head = head->next; } while (!stk.empty()) { vec.push_back(stk.top()); stk.pop(); } return vec; }
|
思路2:递归的方法,递归本质就是一个栈结构,但是其时间空间复杂度较差
show me code
1 2 3 4 5 6 7 8 9
| vector<int> vec_recursive; vector<int> reversePrint(ListNode *head) { if (head == nullptr) return vec_recursive; reversePrint(head->next); vec_recursive.push_back(head->val); return vec_recursive; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int main(int argc, char **argv) { ListNode *node1 = new ListNode(1); ListNode *node2 = new ListNode(2); ListNode *node3 = new ListNode(3); node1->next = node3; node3->next = node2; cout << "before: "; printList(node1); cout << "after(recursive): "; vector<int> revList = reversePrint(node1); vector<int>::iterator iter; for (iter = revList.begin(); iter != revList.end(); iter++) { cout << *iter << " "; } cout << endl; }
|
剑指Offer18:删除链表的节点
题目:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
思路1:单指针扫描法
show me code
1 2 3 4 5 6 7 8 9 10 11 12
| ListNode* deleteNode(ListNode* head, int val) { if (head->val == val) { return head->next; } ListNode* node = head; while (head->next->val != val) { head = head->next; } head->next = head->next->next; return node; }
|
思路2:递归删除法
show me code
1 2 3 4 5 6 7 8 9
| ListNode *deleteNode(ListNode *head, int val) { if (head->val == val) { return head->next; } head->next = deleteNode(head->next, val); return head; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int main(int argc, char **argv) { ListNode *node4 = new ListNode(4); ListNode *node5 = new ListNode(5); ListNode *node1 = new ListNode(1); ListNode *node9 = new ListNode(9); node4->next = node5; node5->next = node1; node1->next = node9; cout << "原始链表: "; printList(node4); cout << "删除: "; printList(deleteNode(node4, 1)); }
|
剑指Offer22:链表中倒数第k个结点
题目:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,
本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,
从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是
值为4的结点。
思路1:由于是单向链表,不能回溯.因此两次遍历
第一次遍历确定链表长度,第二次遍历次数为长度-k
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ListNode* getKthFromEnd(ListNode* head, int k) { int length = 0; ListNode* list = head; while (head != NULL) { head = head->next; length++; } int cnt = 1; while (list != NULL) { if (cnt == length - k + 1) { break; } list = list->next; cnt++; } return list; }
|
思路2:快慢指针,一个指针先走k步,保证双指针的距离为k
当先走的指针指向NULL时,后走的指针指向倒数k个结点
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ListNode *getKthFromEnd(ListNode *head, int k) { if (head == NULL | k == 0) return NULL; ListNode *slow = head; ListNode *quick = head;
int cnt_k = 0; while (cnt_k < k) { quick = quick->next; cnt_k++; } while (quick != NULL) { slow = slow->next; quick = quick->next; } return slow; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int main(int argc, char **argv) { ListNode *node1 = new ListNode(1); ListNode *node2 = new ListNode(2); ListNode *node3 = new ListNode(3); ListNode *node4 = new ListNode(4); ListNode *node5 = new ListNode(5); node1->next = node2; node2->next = node3; node3->next = node4; node4->next = node5; cout << "原始链表: "; printList(node1); cout << "倒数第k个结点:"; ListNode *endk = getKthFromEnd(node1, 1); printList(endk); }
|
剑指Offer24:反转链表
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
思路1:# 剑指Offer书上,定义三个指针
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ListNode* reverseList(ListNode* head) { ListNode* res = NULL; ListNode* cur = head; ListNode* pre = NULL; ListNode* next = NULL; while (cur != NULL) { next = cur->next; if (next == NULL) { res = cur; } cur->next = pre; pre = cur; cur = next; } return res; }
|
思路2:递归
https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/solution/ru-guo-ni-kan-wan-ping-lun-he-ti-jie-huan-you-wen-/
show me code
1 2 3 4 5 6 7 8 9 10 11 12
| ListNode *reverseList(ListNode *head) { if (head == NULL | head->next == NULL) { return head; } ListNode *cur = reverseList(head->next); head->next->next = head; head->next = NULL; return cur; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int main(int argc, char **argv) { ListNode *node1 = new ListNode(1); ListNode *node2 = new ListNode(2); ListNode *node3 = new ListNode(3); ListNode *node4 = new ListNode(4); ListNode *node5 = new ListNode(5); node1->next = node2; node2->next = node3; node3->next = node4; node4->next = node5; cout << "原始链表: "; printList(node1); cout << "反转链表: "; printList(reverseList(node1)); }
|
剑指Offer25:合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
思路:迭代
由于一开始合并链表中无头节点,需要建立一个辅助结点dum
由于是有序链表,当一个链表遍历完了,只要把剩下的链表接到后面就好
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { if (l1 == NULL || l2 == NULL) { return l1 == NULL ? l2 : l1; } ListNode *dum = new ListNode(-1); ListNode *cur = dum; while (l1 != NULL && l2 != NULL) { if (l1->val >= l2->val) { cur->next = l2; l2 = l2->next; } else { cur->next = l1; l1 = l1->next; } cur = cur->next; } cur->next = l1 == NULL ? l2 : l1; return dum->next; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int main(int argc, char **argv) { ListNode *n11 = new ListNode(1); ListNode *n12 = new ListNode(2); ListNode *n14 = new ListNode(4); ListNode *n21 = new ListNode(1); ListNode *n23 = new ListNode(3); ListNode *n24 = new ListNode(4); n11->next = n12; n12->next = n14; n21->next = n23; n23->next = n24; cout << "链表1: "; printList(n11); cout << "链表2: "; printList(n21); cout << "合并后:"; printList(mergeTwoLists(n11, n21)); }
|
剑指Offer35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。
在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Node { public: int val; Node *next; Node *random;
Node(int _val) { val = _val; next = NULL; random = NULL; } };
|
思路1:
random输出的是到head的距离
第一轮遍历,先根据next把新链表连接起来,并建一个哈希表
再根据哈希表,确定新链表random的连接关系
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Node* copyRandomList(Node* head) { if (head == NULL) return NULL; unordered_map<Node*, Node*> match; Node* pos = head; Node* res_head = new Node(head->val); Node* res_pos = res_head; while (pos) { match[pos] = res_pos; if (!pos->next) { res_pos->next = NULL; } else { res_pos->next = new Node(pos->next->val); } pos = pos->next; res_pos = res_pos->next; } pos = head; res_pos = res_head; while (pos) { res_pos->random = match[pos->random]; res_pos = res_pos->next; pos = pos->next; } return res_head; }
|
思路2:
- 复制节点,复制节点插入在原始节点后面如:a->a’->b->b’
- 复制random指针,复制后的random指向原random的next
- 链表的偶数项就是复制后的链表
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| void copyNode(Node *head); void copyRandom(Node *head); Node *getCopy(Node *head);
Node *copyRandomList(Node *head) { if (!head) return NULL; copyNode(head); copyRandom(head); return getCopy(head); }
void copyNode(Node *head) { Node *pos = head; while (pos) { Node *copy = new Node(pos->val); copy->next = pos->next; pos->next = copy; pos = pos->next->next; } }
void copyRandom(Node *head) { Node *pos = head; while (pos) { if (pos->random) { pos->next->random = pos->random->next; } else { pos->next->random = NULL; } pos = pos->next->next; } }
Node *getCopy(Node *head) { Node *pos = head; Node *copy = pos->next; Node *res = copy; while (pos) { pos->next = copy->next; pos = pos->next; if (pos) { copy->next = pos->next; } copy = copy->next; } return res; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int main(int argc, char **argv) { Node *head = new Node(7); Node *two = new Node(13); Node *three = new Node(11); Node *four = new Node(10); Node *five = new Node(1); head->next = two; two->next = three; three->next = four; four->next = five; five->next = NULL;
head->random = NULL; two->random = head; three->random = five; four->random = three; five->random = head;
Node *res = copyRandomList(head); }
|
剑指Offer52:两个链表的第一个公共结点
题目:输入两个链表,找出它们的第一个公共结点。
思路1:哈希表
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { if (!headA || !headB) return nullptr; unordered_map<ListNode *, int> nodes; ListNode *n1 = headA; ListNode *n2 = headB; while (n1) { ++nodes[n1]; n1 = n1->next; } while (n2) { if (nodes[n2]) return n2; n2 = n2->next; } return nullptr; }
|
思路2
- headA的长度可以表示为 A+same
- headB的长度可以表示为 B+same
- 因此采用两个指针分别遍历 A+same+B 和 B+same+A
- 最后停下来的地方就是重复的节点
show me code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { if (!headA || !headB) return nullptr; ListNode *ptrA = headA, *ptrB = headB; while (ptrA != ptrB) { if (ptrA) ptrA = ptrA->next; else ptrA = headB; if (ptrB) ptrB = ptrB->next; else ptrB = headA; } return ptrA; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| int main(int argc, char **agrv) { ListNode *headA = new ListNode(4); ListNode *a1 = new ListNode(1); ListNode *headB = new ListNode(5); ListNode *b0 = new ListNode(0); ListNode *b1 = new ListNode(1); ListNode *n8 = new ListNode(8); ListNode *n4 = new ListNode(4); ListNode *n5 = new ListNode(5); n8->next = n4; n4->next = n5;
headA->next = a1; a1->next = n8; printList(headA); headB->next = b0; b0->next = b1; b1->next = n8; printList(headB);
cout << getIntersectionNode(headA, headB)->val << endl; }
|