目录
题目四 将单链表按某值划分成左边小,中间相等,右边大的形式。
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");
}