链表
链表是一种动态的数据结构,当需要插入一个节点的时候,我们只需要为新创建的节点分配内存空间,将当前节点的next指向新创建的节点,并没有闲置的内存空间。
例题解答
本文中所有的链表的定义如下:
struct ListNode{
int val;
ListNode *next;
ListNode(int v):val(v),next(nullptr){}
};
从尾到头打印链表
题目:输入一个链表的头节点,从尾到头反过来打印每个节点的值
- 首先,逆序很容易想到栈这个数据结构,在遍历链表的时候,把每个节点压入栈中,打印时从栈中弹出来,就能逆着打印
- 其次,可以将值保存在
vector
中,调用reverse()
函数即可逆序打印结果
代码如下
void printReverseList(ListNode *root){
ListNode *p = root;
stack<ListNode*>nodes;
while(p != nullptr){
nodes.push(p);
p = p ->next;
}
while(!nodes.empty()){
ListNode *t = nodes.top();
printf("%d\t",t->val);
nodes.pop();
}
}
删除链表中的节点
在O(1)的时间内删除链表的某个节点
给定单向链表的头指针和一个节点的指针,定义一个函数在O(1)的时间内删除该节点
- 通常,删除链表中的节点需要获取该节点的前一个节点,然而获取这个前一个节点需要遍历整个链表,时间为O(n)
- 此方法中可以,将下一个节点复制到该节点中,再删除下一个节点,即可完成删除操作。
代码如下
void deleteNode(ListNode **root,ListNode *del_node){
//删除的节点不是尾节点
if(del_node->next != nullptr){
ListNode *p_next = del_node->next;
del_node->val = p_next->val;
del_node->next = p_next->next;
delete p_next;
}
//链表中只含有唯一一个节点
else if (*root == del_node){
delete del_node;
}
//删除链表中的尾节点
else{
ListNode *p = *root;
while(p->next != del_node){
p = p->next;
}
p->next = nullptr;
delete del_node;
}
}
链表中倒数第K个节点
输入一个链表,输出该链表中倒数第K个节点。
- 同样,常规的方法首先遍历链表,获取链表中节点的个数,计算下一次遍历时需要计算的次数。时间复杂度为O(n)
- O(1)的做法为设置2个指针,第一个指针向前走k-1步后,第二个指针不动。接着让第一个指针指向末尾时,第二个指针便指向倒数第k个节点
代码如下:
int findKNode(ListNode *root,int k) {
ListNode *p_node = root;
ListNode *p_sec = root;
for(int i = 0;i < k-1;i++){
p_node = p_node->next;
}
while(p_node->next != nullptr) {
p_node = p_node->next;
p_sec = p_sec->next;
}
return p_sec->val;
}
链表中环的入口节点
题目:如果一个链表中包含环,如何找出环的入口节点?
- 首先,要判断链表中是否有环,可以设置快、慢两个指针,如果他们相遇了则说明链表中存在环
- 要找到入口节点可以新建一个指针,同时慢指针继续以一次一步的速度继续遍历,相遇节点即为环的入口节点,证明如下:
假设快慢节点相遇在c点,慢节点在这个过程中移动的距离为s,链表长度为L,环的长度为r,则快节点移动距离为a+n*r,而快节点的步长为慢节点的2倍:
a+n*r = 2s
—> a+x = s
a+x = n*r
—> a+x = (n-1)*r + r
—>a+x = (n-1)*r + (L-a)
a = (n-1)*r + (L-a-x)
因此节点从h->d与节点从c->d会在d点相遇
因此环形链表可以分解成三个问题
1. 给定一个链表,判断链表中是否有环
2. 计算环的大小
3. 寻找环的入口
判断链表中是否有环
要点:设置快慢两个指针,慢指针一次走一步,快指针一次走两步,当他们相遇时,则说明链表中有环
代码如下;
bool hasCycle(ListNode *head) {
ListNode *slow,*fast;
if(head == nullptr)
return false;
slow = head->next;
if(slow == nullptr)
return false;
fast = slow->next;
if(fast == nullptr)
return false;
while(slow != nullptr && fast != nullptr) {
if(fast == slow)
return true;
slow = slow ->next;
fast = fast->next;
if(fast != nullptr)
fast = fast->next;
else
return false;
}
return false;
}
计算环的大小
在相遇的节点后,慢节点遍历一圈后回到当前节点,判断节点相同即可
寻找环的入口节点
需要第一个问题中的相遇的节点作为参数,代码如下
ListNode *detectCycle(ListNode *head) {
ListNode *circle_head = hasCycle(head);
if(circle_head == nullptr)
return nullptr;
ListNode *p = head;
while(p != circle_head) {
p = p->next;
circle_head = circle_head->next;
}
return p;
}
反转链表
题目:定义一个函数,输入一个链表的头节点,反转该链表并输入反转后的链表的头结点
为了防止链表的断裂,需要使用三个指针,表示当前节点、前一个节点、下一个节点。而反转之后的链表的头节点是反转之前next为null的节点。
代码如下:
ListNode* reverseList(ListNode *root) {
ListNode *p_reverse_head = nullptr;
ListNode *p_cur = root;
ListNode *p_pre = nullptr;
while(p_cur != nullptr) {
ListNode *p_next = p_cur->next;
if(p_next == nullptr)
p_reverse_head = p_cur;
p_cur -> next = p_pre;
p_pre = p_cur;
p_cur = p_next;
}
return p_reverse_head;
}
(LeetCode21) 合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
- 使用遍历的办法,找出2个链表中值较小的那个节点链接到新的表头中
- 使用递归的办法
代码如下:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == nullptr)
return l2;
else if(l2 == nullptr)
return l1;
ListNode *mergeHead = nullptr;
if(l1->val > l2->val) {
mergeHead = l2;
mergeHead->next = mergeTwoLists(l1,l2->next);
}else {
mergeHead = l1;
mergeHead->next = mergeTwoLists(l1->next,l2);
}
return mergeHead;
}
(LeetCode23)合并K个有序链表
合并 k 个排序链表,返回合并后的排序链表
- 可以每次两两排序,将排序后的链表重新加入到
lists
中去,并在lists
中删除以及排序过的链表
代码如下:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty())
return nullptr;
while(lists.size() > 1) {
lists.push_back(mergeTwoLists(lists[0],lists[1]));
lists.erase(lists.begin());
lists.erase(lists.begin());
}
return lists.front();
}
复杂链表的复制
- 为了实现O(1)的复杂度,先将链表在原链表中复制一遍
- 在新的节点上链接
siblingNodes
- 断开链接,选择偶数位置的节点
代码如下:
struct ListNode{
int val;
ListNode *next;
ListNode *sibling;
ListNode() = default;
ListNode(int v):val(v),next(nullptr),sibling(nullptr){}
};
void copyNodes(ListNode *root) {
ListNode *head = root;
while(head != nullptr) {
ListNode *temp = new ListNode();
temp->val = head->val;
temp->next = head->next;
head->next = temp;
head = temp->next;
}
}
void connectSiblingNodes(ListNode *root) {
ListNode *head = root;
while(head != nullptr) {
ListNode *cloned = head->next;
if(head->sibling != nullptr){
cloned->sibling = head->sibling->next;
}
head = cloned->next;
}
}
ListNode* reconnectNodes(ListNode *root){
ListNode *node = root;
ListNode *cloned_head = nullptr,*cloned_node = nullptr;
if(node != nullptr) {
cloned_head = cloned_node = node->next;
node ->next = cloned_node ->next;
node = node->next;
}
while(node != nullptr) {
cloned_node->next = node ->next;
cloned_node = cloned_node ->next;
node->next = cloned_node ->next;
node = node->next;
}
return cloned_head;
}
ListNode* clone(ListNode *head){
copyNodes(head);
connectSiblingNodes(head);
ListNode *root = reconnectNodes(head);
return root;
}