链表相关问题
如何完成链表的逆序
问题一:已知链表头节点指针head,将链表逆序。(不可申请额外空间)
逆序就好比人们排队时的变换阵型的过程,比如,原本已经排好的队伍,我想要把排头的放在最后,排尾的放到前面来,完成队伍的翻转,这个过程就是把每次排头的人放在新队伍所有人的前面,从而做到原队伍的翻转。
上述的过程主要运用的是倒插法来解决问题,思路如下:
新建一个同类型的空指针new_head,在循环的操作下执行以下操作:一个原链表的头指针的下一节点next,将头指针head连接到空指针new_head的前面,new_head向前挪一格,即指向head所指向的地址,head指向next,也就是剩余原链表的头节点;直至头节点为空而停止操作。
代码如下:
#include<iostream>
#define _CRT_SECURE_NO_WARNINGS
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* reverseList(ListNode* head) {
ListNode *new_head = NULL;
while(head){
ListNode *next = head->next;
head->next = new_head;
new_head = head;
head = next;
}
return new_head;
}
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
ListNode *head = &a;
printf("Before reverse:\n");
while(head){
printf("%d\n", head->val);
head = head->next;
}
head = reverseList(&a);
printf("After reverse:\n");
while(head){
printf("%d\n", head->val);
head = head->next;
}
return 0;
}
问题二:选择性的逆序m到n的链表[1<=m<=n<=链表长度](进阶)
在上一个问题的基础上施加更多的要求,考验代码灵活应变的能力。
对于原来的链表,我要找到第m个节点和第n个节点,将他们逆序之后再连接回链表中,其中我们就要思考到关键性的问题——①当我们完成了m到n的节点逆序之后,我们该如何找到第m-1个节点和第n+1个节点,使得逆序的链表链接回原链表?②遍历到第m个节点之后,继续遍历,如何防止第m个节点位置的丢失?
这时,就涉及到对于关键位置的节点地址保存的问题,例如,建立pre_head指针在遍历到第m个节点时,指向第m-1个节点,同时建立指针m_Node指向第m个指针,在遍历到第n个节点时,建立指针new_head指向第n个节点,next指针将做为下一连接的节点。
代码如下:
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
int change_len = n - m + 1;
ListNode *pre_head = NULL;
ListNode *result = head;
while(head && --m){
pre_head = head;
head = head->next;
}
ListNode *modify_list_tail = head;
ListNode *new_head = NULL;
while(head && change_len){
ListNode *next = head->next;
head->next = new_head;
new_head = head;
head = next;
change_len--;
}
modify_list_tail->next = head;
if (pre_head){
pre_head->next = new_head;
}
else{
result = new_head;
}
return result;
}
};
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
Solution solve;
ListNode *head = solve.reverseBetween(&a, 2, 4);
while(head){
printf("%d ", head->val);
head = head->next;
}
return 0;
}
结果为:
1 4 3 5 2
链表的交点问题
问题一:两个链表之间的交点
思路一:我将其中一个链表的指针都放入到一个vector的容器中,循环headB指针,通过find函数,说明headB的指针是否存在于这个容器中,当存在,返回指针,不存在,返回NULL。(有存储空间上的使用)
思路二:先计算出两个链表的长度,把较长的链表指针头走到与另一链表相同长度的位置,再同时向后遍历,当两个指针同时指向同一地址时,说明有交点,返回指针,不存在交点,返回NULL。(无存储空间上的使用)
如图所示:
所以对比上述两种方法,显然思路二更加优良,这里提供思路二的解题方法。
代码如下:
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
int get_list_length(ListNode *head){
int len = 0;
while(head){
len++;
head = head->next;
}
return len;
}
ListNode *forward_long_list(int long_len,
int short_len, ListNode *head){
int delta = long_len - short_len;
while(head && delta){
head = head->next;
delta--;
}
return head;
}
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int list_A_len = get_list_length(headA);
int list_B_len = get_list_length(headB);
if (list_A_len > list_B_len){
headA = forward_long_list(list_A_len, list_B_len, headA);
}
else{
headB = forward_long_list(list_B_len, list_A_len, headB);
}
while(headA && headB){
if (headA == headB){
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
};
int main(){
ListNode a1(1);
ListNode a2(2);
ListNode b1(3);
ListNode b2(4);
ListNode b3(5);
ListNode c1(6);
ListNode c2(7);
ListNode c3(8);
a1.next = &a2;
a2.next = &c1;
c1.next = &c2;
c2.next = &c3;
b1.next = &b2;
b2.next = &b3;
b3.next = &c1;
Solution solve;
ListNode *result = solve.getIntersectionNode(&a1, &b1);
printf("%d\n", result->val);
return 0;
}
问题二:单个链表的交点(成环)
首先需要了解这样的一个有环链表具有什么样的特点:①最后没有NULL 进行结尾;②存在成环时的首节点(即环交点)。
思路一:在寻找环交点的过程也可以仿照上一问题的思路一,将链表从头节点进行遍历,建立一个存放链表指针的vector容器,每遍历一个指针就判断指针是否存在于容器中,在存入容器中,当链表指针成功将链表中的环遍历一遍,进行下一次时,首指针(即成环首交点处的地址)已存在容器中,则返回该指针,否则不存在环,返回NULL。(有存储空间的使用)
思路二:一个经典的方法:快慢指针法;我们建立两种指针,一次走两步的fast指针和一次走一步的slow指针,当fast指针与slow指针相遇时,从头指针head和相遇时的节点指针meet到环交点指针的距离相等。(无存储空间的使用)
a:头节点到环交点的距离
b:环交点到相遇节点的距离
c:相遇节点到换节点的距离
(字有点凌乱,见笑了)
代码如下:
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
ListNode *meet = NULL;
while(fast){
slow = slow->next;
fast = fast->next;
if (!fast){
return NULL;
}
fast = fast->next;
if (fast == slow){
meet = fast;
break;
}
}
if (meet == NULL){
return NULL;
}
while(head && meet){
if (head == meet){
return head;
}
head = head->next;
meet = meet->next;
}
return NULL;
}
};
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
ListNode f(6);
ListNode g(7);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
e.next = &f;
f.next = &g;
g.next = &c;
Solution solve;
ListNode *node = solve.detectCycle(&a);
if (node){
printf("%d\n", node->val);
}
else{
printf("NULL\n");
}
return 0;
}
链表的深拷贝
题目讲解:一个链表中,有两个链接链表的节点,一个next,按链表顺序链接链表,另一个random,随机链接链表的节点(包括自身)求这个链表的深拷贝
简单形式,如图所示:
这道题,我打算改变原有的罗列顺序,先将代码放出,后将解题的思路梳理出来,希望道友们能懂得看似复杂的代码背后,是一个简单、浅显的道理!
代码如下:
#include <stdio.h>
struct RandomListNode {
int label;
RandomListNode *next, *random;
RandomListNode(int x) : label(x), next(NULL), random(NULL) {}
};
#include <map>
#include <vector>
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
std::map<RandomListNode *, int> node_map;
std::vector<RandomListNode *> node_vec;
RandomListNode *ptr = head;
int i = 0;
while (ptr){
node_vec.push_back(new RandomListNode(ptr->label));
node_map[ptr] = i;
ptr = ptr->next;
i++;
}
node_vec.push_back(0);
ptr = head;
i = 0;
while(ptr){
node_vec[i]->next = node_vec[i+1];
if (ptr->random){
int id = node_map[ptr->random];
node_vec[i]->random = node_vec[id];
}
ptr = ptr->next;
i++;
}
return node_vec[0];
}
};
int main(){
RandomListNode a(1);
RandomListNode b(2);
RandomListNode c(3);
RandomListNode d(4);
RandomListNode e(5);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
a.random = &c;
b.random = &d;
c.random = &c;
e.random = &d;
Solution solve;
RandomListNode *head = solve.copyRandomList(&a);
while(head){
printf("label = %d ", head->label);
if (head->random){
printf("rand = %d\n", head->random->label);
}
else{
printf("rand = NULL\n");
}
head = head->next;
}
return 0;
看完上述的过程,道友们是否懂得了这个代码所要传达的主体思路了呢?
那么接下来,由我来梳理一下这个主题过程:
对于这样凌乱看似毫无章法的链表,首先要做的就是将其所包含的信息给梳理出来,比如这里就通过一个node_map容器储存链表指针和节点号的方式,梳理出了由next节点排序出的链表格式,第二个链表则为遍历地存储新链表节点,之后通过梳理出的node_map关系,重新把node_vec容器中的节点连接起来,形成新的链表进行输出,从而完成链表的深拷贝。
如图所示:(节点地址与节点序号对应)
致谢
本章知识点和思路由小象学院相关视频提供,由本人学习并梳理得出,希望自己加深记忆的同时,也能给大家提供更多有关于链表的知识点,谢谢支持!