C++实现链表相关问题(反转单、双向链表,打印公共部分,判断回文结构,有没有环,链表相交)

目录

0 概述

题目一:反转单向链表和双向链表。

题目二 打印两个有序链表的公共部分​

题目三 判断一个单链表是否是回文结构

题目四 将单链表按某值划分成左边小,中间相等,右边大的形式。

题目五 复制含有随机指针节点的链表

题目六 两个单链表相交的一系列问题


0 概述

链表分为单向链表和双向链表,在C++容器中为forward_list和list。在解决链表的问题时,注意链表函数是否需要加返回值,如果链表的调整涉及到换表头的操作,返回值为node类型,不涉及的话,返回值可以为void。

面试时链表解题的方法论:

1) 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度;

2) 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法。

重要技巧:

1) 额外数据结构记录;

2) 快慢指针

题目一:反转单向链表和双向链表。

反转单向链表代码:

#include <iostream>
using namespace std;

//单链表节点
struct Node {
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
    Node(int x, Node *next) : val(x), next(next) {}
};

//反转单链表
Node* reverseForwardList(Node* head){
    Node* pre = nullptr;
    Node* post = nullptr;
    while (head != nullptr){
        post = head -> next;
        head -> next = pre;
        pre = head;
        head = post;
    }
    return pre;
}

//打印链表
void printForwardList(Node* head){
    while(head != nullptr){
        cout << head -> val << " ";
        head = head -> next;
    }
    cout << endl;
}

int main(){
    Node* head1 = new Node(1);
    head1 -> next = new Node(2);
    head1 -> next -> next = new Node(3);
    head1 -> next -> next -> next = new Node(4);
    printForwardList(head1);
    head1 = reverseForwardList(head1);
    printForwardList(head1);
    system("pause");
}

反转双链表代码:

#include <iostream>
using namespace std;

//双向链表节点
struct Node{
    int val;
    Node *last;
    Node *next;
    Node() : val(0), last(nullptr), next(nullptr) {}
    Node(int x) : val(x), last(nullptr), next(nullptr) {}
    Node(int x, Node *last, Node *next) : val(x), last(last), next(next) {}
};

//反转双向链表
Node* reverseDoubleLinkedList(Node* head){
    Node* pre = nullptr;
    Node* post = nullptr;
    while (head != nullptr){
        post = head -> next;
        head -> next = pre;
        head -> last = post;
        pre = head;
        head = post;
    }
    return pre;
}

//打印双向链表
void printDoubleLinkedList(Node* head){
    while(head != nullptr){
        cout << head -> val << " ";
        head = head -> next; 
    }
    cout << endl;
}

//test
int main(){
    Node* head2 = new Node(1);
    head2 -> next = new Node(2);
    head2 -> next -> last = head2;
    head2 -> next -> next = new Node(3);
    head2 -> next -> next -> last = head2 -> next;
    head2 -> next -> next -> next = new Node(4);
    head2 -> next -> next -> next -> last = head2 -> next -> next;
    
    printDoubleLinkedList(head2);
    head2 = reverseDoubleLinkedList(head2);
    printDoubleLinkedList(head2);
    system("pause");
}

题目二 打印两个有序链表的公共部分

 代码如下:

#include <iostream>
using namespace std;

//链表节点
struct Node{
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
    Node(int x, Node *next) : val(x), next(next) {}
};

//打印两个有序链表的公共部分
void printCommonPart(Node *head1, Node *head2){
    while (head1 != nullptr && head2 != nullptr){
        if (head1 -> val < head2 -> val){
            head1 = head1 -> next;
        }else if (head1 -> val > head2 -> val){
            head2 = head2 -> next;
        }else {
            cout << head1 -> val << " ";
            head1 = head1 -> next;
            head2 = head2 -> next;
        }
    }
    cout << endl;
}

//打印链表
void printLinkedList(Node *head){
    while (head != nullptr){
        cout << head -> val << " ";
        head = head -> next;
    }
    cout << endl;
}

//test
int main(){
    Node *head1 = new Node(1);
    head1 -> next = new Node(3);
    head1 -> next -> next = new Node(5);
    head1 -> next -> next -> next = new Node(7);

    Node *head2 = new Node(2);
    head2 -> next = new Node(3);
    head2 -> next -> next = new Node(5);
    head2 -> next -> next -> next = new Node(6);
    printLinkedList(head1);
    printLinkedList(head2);
    printCommonPart(head1, head2);
    system("pause");
}

题目三 判断一个单链表是否是回文结构

三种方法。

笔试时:借助栈,先将链表节点元素放到栈里,再一次遍历链表和栈顶元素进行比较,对应代码中的方法1。

更节省空间的方法,只将链表右边的部分放入栈中,这样能节省一半的空间。怎么只把右部分放到栈里?快慢指针,快指针一次走两步,慢指针一次走一步,快指针到了尾部,慢指针在中间,将慢指针后面的部分放到栈里。对应代码中的方法2。

如何实现空间复杂度O(1)?同方法2使用快慢指针找中点,慢指针到中点,后半部分的链表翻转,此时中间节点指向nullptr,然后一个指针从前往后遍历,一个指针从后往前遍历,进行比较,遇到不同时或者nullptr时终止。即:先找中间节点 一> 翻转后半部分链表 一> 从前往后和从前往前遍历链表的前半部分和后半部分进行比较 一> 恢复链表。对应代码中的方法3.

代码如下:

#include <iostream>
#include <stack>
using namespace std;

struct Node{
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
    Node(int x, Node *next) : val(x), next(next) {}
};

//1: nead n extra space
bool isPalindrome1(Node *head){
    stack<int> stack1;
    Node *cur = head;
    //put the value of linkedList into stack 
    while (cur != nullptr){
        stack1.push(cur -> val);
        cur = cur -> next;
    }
    //compare the value of linkedIst with the value of stack
    while (head != nullptr){
        if (head -> val != stack1.top()){
            return false;
        }
        stack1.pop();
        head = head -> next;
    }
    return true;
}

//2: need n/2 extra space
bool isPalindrome2(Node *head){
    if (head == nullptr || head -> next == nullptr){
        return true;
    }
    //find mid Node, is slow
    Node *slow = head;
    Node *fast = head;
    while (fast != nullptr && fast -> next != nullptr){
        slow = slow -> next;
        fast = fast -> next -> next;
    }
    //put the left half of link into stack
    stack<int> stack2;
    while(slow != nullptr){
        stack2.push(slow -> val);
        slow = slow -> next;
    }
    //compare the value of first half linkeList with the value of stack
    while (!stack2.empty()){
        if (head -> val != stack2.top()){
            return false;
        }
        stack2.pop();
        head = head -> next;
    }
    return true;
}

//3: need O(1) extra space
bool isPalindrome3(Node *head){
    if (head == nullptr || head -> next == nullptr){
        return true;
    }
    //find mid Node, is slow
    Node *slow = head;
    Node *fast = head;
    while (fast != nullptr && fast -> next != nullptr){
        slow = slow -> next;
        fast = fast -> next -> next;
    }
    //reverse the second half of the linkedList
    Node *pre = nullptr;
    Node *post = nullptr;
    while (slow != nullptr){
        pre = slow -> next;
        slow -> next = post;
        post = slow;
        slow = pre;        
    }
    //compare the value of first half linkedList with the value of second half LinkedList
    Node *rightHead = post;
    while (head != nullptr && rightHead != nullptr){
        if (head -> val != rightHead -> val){
            return false;
            break;
        }
        head = head -> next;
        rightHead = rightHead -> next;
    }
    //recover LinkedList
    rightHead = post;
    post = nullptr;
    pre = nullptr;
    while(rightHead != nullptr){
        pre = rightHead -> next;
        rightHead -> next = post;
        post = rightHead;
        rightHead = pre;
    }
    return true;
}

//test
int main(){
    Node *head1 = new Node(1);
    head1 -> next = new Node(2);
    head1 -> next -> next = new Node(3);
    head1 -> next -> next -> next = new Node(3);
    head1 -> next -> next -> next -> next = new Node(2);
    head1 -> next -> next -> next -> next -> next = new Node(1);
    cout << "1: " << boolalpha << isPalindrome1(head1) << endl;
    cout << "2: " << boolalpha << isPalindrome2(head1) << endl;
    cout << "3: " << boolalpha << isPalindrome3(head1) << endl;
    system("pause");
}

题目四 将单链表按某值划分成左边小,中间相等,右边大的形式。

两种方法:

1) 借助数组。申请一个名为node的数组,将链表节点至于数组中,再在数组上partation,然后用node串起来。如下面代码中的方法1。

2) 使用六个变量,分别表示小于部分的头、尾,等于部分的头、尾,大于部分的头、尾。将原链表中的节点根据value值依次放到相应的链表中,然后将三个链表依次串起来。注意三块重连的时候,一定要注意边界。如下面代码中的方法2。

#include <iostream>
#include <vector>
using namespace std;

struct Node{
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
    Node(int x, Node *next) : val(x), next(next) {}
};

//partition数组函数
void Arrpartition(vector<Node*> &nodeArr, int pivot){
    int small = -1;
    int big = nodeArr.size();
    int index = 0;
    while (index < big){
        if (nodeArr[index] -> val < pivot){
            swap(nodeArr[++small], nodeArr[index++]);
        }else if (nodeArr[index] -> val > pivot){
            swap(nodeArr[--big], nodeArr[index]);
        }else{
            index++;
        }
    }
}

//方法1
Node* listPartition1(Node *head, int pivot){
    if (head == nullptr){
        return head;
    }

    //遍历链表节点个数
    Node *cur = head;
    int i = 0;
    while (cur != nullptr){
        i++;
        cur = cur -> next;
    }

    //将链表节点依次放入数组中
    cur = head;
    vector<Node*> nodeArr(i);
    for (i = 0; i < nodeArr.size(); i++){
        nodeArr[i] = cur;
        cur = cur -> next;
    }
    //partition数组
    Arrpartition(nodeArr, pivot);
    //将数组中节点元素重新串成链表
    for (i = 1; i < nodeArr.size(); i++){
        nodeArr[i - 1] -> next = nodeArr[i];
    }
    nodeArr[i - 1] -> next = nullptr;
    return nodeArr[0];
}

//方法2
Node* listPartition2(Node *head, int pivot){
    Node *sH = nullptr; //小于部分的头
    Node *sT = nullptr; //小于部分的尾
    Node *eH = nullptr; //等于部分的头
    Node *eT = nullptr; //等于部分的尾
    Node *bH = nullptr; //大于部分的头
    Node *bT = nullptr; //大于部分的尾
    Node *pre = nullptr; 
    //将每个节点至于相应的三个链表中
    while (head != nullptr){
        pre = head -> next;
        head -> next = nullptr;
        if (head -> val < pivot){
            if (sH == nullptr){
                sH = head;
                sT = head;
            }else {
                sT -> next = head;
                sT = head;
            }
        }else if (head -> val == pivot){
            if (eH == nullptr){
                eH = head;
                eT = head;
            }else {
                eT -> next = head;
                eT = head;
            }
        }else {
            if (bH == nullptr){
                bH = head;
                bT = head;                
            }else {
                bT -> next = head;
                bT = head; 
            }
        }
        head = pre;
    }
    //连接small和equl部分
    if (sT != nullptr){
        sT -> next = eH;
        eT = eT == nullptr ? sT : eT;
    }
    //连接所有部分
    if (eT != nullptr){
        eT -> next = bH;
        bT = bT == nullptr ? eT : bT;
    }
    return sH != nullptr ? sH : eH != nullptr ? eH : bH;
}

//打印数组
void printLinkedList(Node *head){
    while (head != nullptr){
        cout << head -> val << " ";
        head = head -> next;
    }
    cout << endl;
}

//test
int main(){
    Node *head1 = new Node(1);
    head1 -> next = new Node(7);
    head1 -> next -> next = new Node(5);
    head1 -> next -> next -> next = new Node(2);
    head1 -> next -> next -> next -> next = new Node(3);
    head1 -> next -> next -> next -> next -> next = new Node(9);
    printLinkedList(head1);
    //head1 = listPartition1(head1, 5);
    head1 = listPartition2(head1, 5);
    printLinkedList(head1);
    system("pause");
}

题目五 复制含有随机指针节点的链表

两种方法: 

1) 哈希表,数据类型均是Node,key存老节点,value存老节点对应克隆出来的新节点。

2) 第一步生成克隆节点挂在老节点后面,第二步设置拷贝节点的rand节点,第三步分离原链表和拷贝的链表。

两种方法代码如下:

#include <iostream>
#include <map>
using namespace std;

struct Node{
    int val;
    Node *next;
    Node *rand;
    Node() : val(0), next(nullptr), rand(nullptr) {}
    Node(int x) : val(x), next(nullptr), rand(nullptr) {}
};

//方法1
Node* copylistWithRand1(Node *head){
    map<Node*, Node*> nodeMap;
    Node *cur = head;
    //拷贝节点
    while (cur != nullptr){
        nodeMap.insert({cur, new Node(cur -> val)});
        cur = cur -> next;
    }
    cur = head;
    //设置拷贝节点的next节点和rand节点
    while (cur != nullptr){
        nodeMap[cur] -> next = nodeMap[cur -> next];
        nodeMap[cur] -> rand = nodeMap[cur -> rand];
        cur = cur -> next;
    }
    return nodeMap[head];
}

//方法2
Node* copylistWithRand2(Node *head){
    if (head == nullptr){
        return head;
    }
    Node *cur = head;
    Node * pre = nullptr;
    //拷贝节点并将拷贝的节点连接在原节点的后面
    while (cur != nullptr){
        pre = cur -> next;
        cur -> next = new Node(cur -> val);
        cur -> next -> next = pre;
        cur = pre;
    }
    cur = head;
    Node *curCopy = nullptr;
    //设置拷贝节点的rand节点
    while (cur != nullptr){
        pre = cur -> next -> next;
        curCopy = cur -> next;
        curCopy -> rand = cur -> rand != nullptr ? cur -> rand -> next : nullptr;
        cur = pre;
    }
    Node *res = head -> next;
    cur = head;
    //分离拷贝链表和原链表
    while (cur != nullptr){
        pre = cur -> next -> next;
        curCopy = cur -> next;
        cur -> next = pre;
        curCopy -> next = pre != nullptr ? pre -> next : nullptr;
        cur = pre;
    }
    return res;
}

//打印链表
void printRandLinkedList(Node *head){
    Node *cur = head;
    while(cur != nullptr){
        cout << cur -> val << " ";
        cur = cur -> next;
    }
    cur = head;
    while (cur != nullptr){
        if (cur -> rand == nullptr){
            cout << "null" << " ";
        }else {
            cout << cur -> rand -> val << " ";
        }
        cur = cur -> next;
    }
    cout << endl;
}

//test
int main(){
    Node *head = nullptr;
    Node *res1 = nullptr;
    Node *res2 = nullptr;
    printRandLinkedList(head);
    res1 = copylistWithRand1(head);
    printRandLinkedList(res1);
    res2 = copylistWithRand2(head);
    printRandLinkedList(res2);

    head = new Node(1);
    head -> next = new Node(2);
    head -> next -> next = new Node(3);
    head -> next -> next -> next = new Node(4);
    head -> next -> next -> next -> next = new Node(5);
    head -> next -> next -> next -> next -> next = new Node(6);

    head -> rand = head -> next -> next -> next -> next -> next;
    head -> next -> rand = head -> next -> next -> next -> next -> next;
    head -> next -> next -> rand = head -> next -> next -> next -> next;
    head -> next -> next -> next -> rand = head -> next -> next;
    head -> next -> next -> next -> next -> rand = nullptr;
    head -> next -> next -> next -> next -> next -> rand = head -> next -> next -> next;

    printRandLinkedList(head);
    res1 = copylistWithRand1(head);
    printRandLinkedList(res1);
    res2 = copylistWithRand2(head);
    printRandLinkedList(res2);
    system("pause");
}

题目六 两个单链表相交的一系列问题

【首次考虑第一个问题:如何判断链表有没有环,如果有环,返回第一个入环节点。解决方法:1) 借助set结构(set中key为node类型)。依次遍历查询链表节点是否在set中,如不在,将当前节点放入set中,若在,第一个在的节点为入环节点。若一直走到空节点,则无环。2) 快慢指针 快指针一次走两步,慢指针一次走一步。快指针走到空,一定无环。快慢指针相遇,则有环(若有环,转的圈数不会超过两圈)。相遇后,快指针回到开头,慢指针留在原地,然后快慢指针一次都只走一步,则快慢指针一定会在入环节点处再次相遇。设该实现算法为loop。】

两个链表依次调用loop函数,有以下几种情况。1) 都是单链表(即loop1, loop2 == null)。先求两个链表的长度和判断两个链表的最后一个(end)节点是不是同一个,若end节点不是同一个,则直接判断出不相交。长链表先走差值(差值为长链表size() - 短链表size())步,然后和短链表一起走,节点相同时则有公共部分。2) 一个是有环,一个无环。不可能相交。3) 两个链表都有环。三种情况 ① 各自成环,互不干扰,没有公共部分;② 公用环,入环节点是相同的;③ 公用环,入环节点不是相同的。第二种类比于无环相交,将入环节点类比于end节点。将loop1继续往下走,在转回自己的过程中若遇到loop2则是第三种情况,若遇不到则是第一种情况。

代码如下:

#include <iostream>
#include <cmath>
using namespace std;

struct Node{
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
};

Node* getLoopNode(Node *head);
Node* noLoop(Node *head1, Node *head2);
Node* bothLoop(Node *head1, Node *loop1, Node *head2, Node *loop2);

Node* getIntersectNode(Node *head1, Node *head2){
    if (head1 == nullptr || head2 == nullptr){
        return nullptr;
    }
    
    Node *loop1 = getLoopNode(head1);
    Node *loop2 = getLoopNode(head2);
    

    if (loop1 == nullptr && loop2 == nullptr){
        return noLoop(head1, head2);

    }
    if (loop1 != nullptr && loop2 != nullptr){
        return bothLoop(head1, loop1, head2, loop2);
    }
    return nullptr;
}

//判断链表有没有环,如果有返回入环节点
Node* getLoopNode(Node *head){
    if (head == nullptr || head -> next == nullptr || head -> next -> next == nullptr){
        return nullptr;
    }
    //快慢指针
    Node *slow = head -> next;
    Node *fast = head -> next -> next;
    //快指针一次走两步,慢指针一次走一步,若有环,必相遇,无环返回nullptr
    while (slow != fast){
        if (fast -> next == nullptr || fast -> next -> next == nullptr){
            return nullptr;
        }
        fast = fast -> next -> next;
        slow = slow -> next;
    }
    //寻找入环节点
    fast = head;
    while (slow != fast){
        slow = slow -> next;
        fast = fast -> next;
    }
    return slow;
}

//判断两个无环链表是否相交
Node* noLoop(Node *head1, Node *head2){
    if (head1 == nullptr || head2 == nullptr){
        return nullptr;
    }
    Node *cur1 = head1;
    Node *cur2 = head2;
    int n = 0;
    while (cur1 -> next != nullptr){
        n++;
        cur1 = cur1 -> next;
    }
    while (cur2 -> next != nullptr){
        n--;
        cur2 = cur2 -> next;
    }
    if (cur1 != cur2){
        return nullptr;
    }
    cur1 = n > 0 ? head1 : head2; //谁长,谁的头变成cur1
    cur2 = cur1 == head1 ? head2 : head1;
    n = abs(n);
    while (n != 0){
        n--;
        cur1 = cur1 -> next;
    }
    while (cur1 != cur2){
        cur1 = cur1 -> next;
        cur2 = cur2 -> next;
    }
    return cur1;
}

//判断两个有环链表是否相交
Node* bothLoop(Node *head1, Node *loop1, Node *head2, Node *loop2){
    Node *cur1 = nullptr;
    Node *cur2 = nullptr;
    if (loop1 == loop2){
        cur1 = head1;
        cur2 = head2;
        int n = 0;
        while (cur1 != loop1){
            n++;
            cur1 = cur1 -> next;
        }
        while (cur2 != loop2){
            n--;
            cur2 = cur2 -> next;
        }
        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n = abs(n);
        while (n != 0){
            n--;
            cur1 = cur1 -> next;
        }
        while (cur1 != cur2){
            cur1 = cur1 -> next;
            cur2 = cur2 -> next;
        }
        return cur1;
    }else {
        cur1 = loop1 -> next;
        while (cur1 != loop1){
            if (cur1 == loop2){
                return loop1;
            }
            cur1 = cur1 -> next;
        }
        return nullptr;
    }
}

int main(){
    //1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> nullptr
    Node *head1 = new Node(1);
    head1 -> next = new Node(2);
    head1 -> next -> next = new Node(3);
    head1 -> next -> next -> next = new Node(4);
    head1 -> next -> next -> next -> next = new Node(5);
    head1 -> next -> next -> next -> next -> next = new Node(6);
    head1 -> next -> next -> next -> next -> next -> next = new Node(7);

    //0 -> 9-> 8 -> 6 -> 7 -> nullptr
    Node *head2 = new Node(0);
    head2 -> next = new Node(9);
    head2 -> next -> next = new Node(8);
    head2 -> next -> next -> next = head1 -> next -> next -> next -> next -> next;
    cout << getIntersectNode(head1 , head2) -> val << endl;
    
    //1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 4....
    head1 = new Node(1);
    head1 -> next = new Node(2);
    head1 -> next -> next = new Node(3);
    head1 -> next -> next -> next = new Node(4);
    head1 -> next -> next -> next -> next = new Node(5);
    head1 -> next -> next -> next -> next -> next = new Node(6);
    head1 -> next -> next -> next -> next -> next -> next = new Node(7);
    head1 -> next -> next -> next -> next -> next -> next -> next = head1 -> next -> next -> next;
    //0 -> 9 -> 8 -> 2....
    head2 = new Node(0);
    head2 -> next = new Node(9);
    head2 -> next -> next = new Node(8);
    head2 -> next -> next -> next = head1 -> next;
    cout << getIntersectNode(head1, head2) -> val << endl;

    system("pause");
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值