链表常见面试题
1、输入一个链表,输出该链表中倒数第k个结点。
如果该链表长度小于k,请返回空。
ListNode* FindKthToTail(ListNode* pHead, int k) {
//处理链表为空的情况
if(pHead == NULL)
return NULL;
ListNode* fast = pHead;
ListNode* slow = pHead;
//可能会有k大于链表节点的情况,所以多加个fast != null的判断
while(fast != NULL && k-- > 0){
fast = fast->next;
}
if(k > 0){
return NULL;
}
while(fast != NULL){
fast = fast->next;
slow = slow->next;
}
return slow;
}
2、给定一个无序单链表,实现单链表的排序(按升序排序)。
输入
[1,3,2,4,5]
返回值
{1,2,3,4,5}
实现代码:
struct ListNode {
* int val;
* struct ListNode *next;
* };
ListNode* sortInList(ListNode* head) {
// write code here
std::vector<ListNode*> nodes;
ListNode* pNode = head;
while(pNode){
nodes.emplace_back(pNode);
pNode = pNode->next;
}
auto cmp = [](ListNode* a, ListNode* b) ->bool{
return a->val < b->val;
};
std::sort(nodes.begin(), nodes.end(), cmp);
for(int i = 0; i < nodes.size()- 1; ++i){
nodes[i]->next = nodes[i + 1];
}
nodes[nodes.size() - 1]->next = nullptr;
return nodes[0];
}
- 将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的,且合并后新链表依然有序。
输入
{1},{2}
返回值
{1,2}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// write code here
//只要有一个为空,就返回另一个
if (l1 == nullptr || l2 == nullptr)
return l2 == nullptr ? l1 : l2;
//把小的赋值给first
ListNode* first = (l2->val < l1->val) ? l2 : l1;
first->next = mergeTwoLists(first->next, first == l1 ? l2 : l1);
return first;
}
4、旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
输入
head = {1, 2, 3, 4, 5}, k=2
返回值
{4, 5, 1, 2, 3}
思路
特殊情况
链表为空或只有一个节点,返回头节点即可;
判断 k == 0,如果等于 0,则直接返回头节点,否则求链表长度 L,再判断 k == NL,如果等于,也可直接返回头节点。
一般情况
采用 虚拟头节点 + 双指针 的方法
ListNode* rotateRight(ListNode* head, int k) {
/* 特殊情况判断 */
if (head == nullptr || head->next == nullptr || k == 0) {
return head;
}
/* 增加虚拟头节点 */
ListNode* dummy = new ListNode(0);
dummy->next = head;
/* 获取链表长度 */
int len = 0;
for (ListNode* node = head; node != nullptr; node = node->next) {
len++;
}
k %= len;
/* 判断 k 是否是 len 的整数倍 */
if (k == 0) {
return head;
}
/* 获取新链表的头尾节点 */
ListNode *fast = dummy, *slow = dummy;
for (int i = 0; i < k; ++i) {
fast = fast->next;
}
while (fast->next != nullptr) {
fast = fast->next;
slow = slow->next;
}
fast->next = head;
head = slow->next;
slow->next = nullptr;
/* 释放虚拟头节点 */
delete dummy;
return head;
}
- 给定一个链表,删除链表的倒数第 nn 个节点并返回链表的头指针
给出的链表为: 1-> 2-> 3 -> 4 -> 5 n= 2
删除了链表的倒数第 n个节点之后,链表变为1->2->3->5
public ListNode removeNthFromEnd (ListNode* head, int n) {
// write code here
if(head==null || n<1){
return null;
}
ListNode* fast= head;
ListNode* slow= head;
for(int i=0;i<n;i++){
fast = fast->next;
}
//如果n的值等于链表的长度,直接返回去掉头结点的链表
if(fast== null){
return head->next;
}
//走到倒数的第n+1个结点
while(fast->next != null){
fast = fast->next;
slow = slow->next;
}
//跳过倒数的第n个结点
slow->next = slow->next->next;
return head;
}
- 给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
从示例中可以看出,如果链表长度为奇数,要求的是中间节点;否则,要求的是下中点。也就是说如果链表长度是偶数(有两个中间节点),求第二个中间节点。
思路:
**可以采用两根(快慢)指针,fast/slow 刚开始均指向链表头节点,然后每次快节点走两步,慢指针走一步,直至快指针指向 null,此时慢节点刚好来到链表的下中节点。
**
ListNode* middleNode(ListNode* head) {
/* 链表只有头节点,直接返回 */
if (head->next == nullptr) {
return head;
}
/* 定义均指向链表头节点的快慢指针 */
ListNode *slow = head, *fast = head;
/* 每次快指针走两步,慢指针走一步, 直至快指针或快指针的下一节点为 null */
while ((fast != nullptr) && (fast->next != nullptr)) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
如果返回第一个中间节点呢
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第一个中间结点。
示例
输入:[1,2,3,4,5,6]
输出:此列表中的结点 3 (序列化形式:[3,4,5,6]) 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第一个结点。
思路:本题与上题唯一的不同在于:如果链表长度为偶数,则要求链表的第一个中点,也就是下中点。因此仍然可以采用上一题的快慢指针解法,只不过在快慢指针的初始化以及循环遍历的条件需要修改。
ListNode* middleNode(ListNode* head) {
/* 链表只有头节点,直接返回 */
if (head->next == nullptr) {
return head;
}
/* 定义快慢指针分别指向链表头节点的下下个节点和头节点的下一节点 */
ListNode *slow = head->next, *fast = head->next->next;
/* 快指针指向的节点的下一节点为空或下下一节点为空,快慢指针停止移动 */
while ((fast->next != nullptr) && (fast->next->next != nullptr)) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
输入两个无环的单链表,找出它们的第一个公共结点。
双指针法。创建两个指针p1和p2,分别指向两个链表的头结点,然后依次往后遍历。如果某个指针到达末尾,则将该指针指向另一个链表的头结点;如果两个指针所指的节点相同,则循环结束,返回当前指针指向的节点。比如两个链表分别为:1->3->4->5->6和2->7->8->9->5->6。短链表的指针p1会先到达尾部,然后重新指向长链表头部,当长链表的指针p2到达尾部时,重新指向短链表头部,此时p1在长链表中已经多走了k步(k为两个链表的长度差值),p1和p2位于同一起跑线,往后遍历找到相同节点即可。其实该方法主要就是用链表循环的方式替代了长链表指针先走k步这一步骤。
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if(pHead1 == nullptr || pHead2 == nullptr){
return nullptr;
}
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
while(p1 != p2)
{
p1 = p1 == nullptr ? pHead2 : p1->next;
p2 = p2 == nullptr ? pHead1 : p2->next;
}
return p1;
}
export ANDROID_SDK_ROOT=/home/niedonghui/Android/Sdk
export ANDROID_NDK_ROOT=
A
N
D
R
O
I
D
S
D
K
R
O
O
T
/
n
d
k
/
16.1.4479499
e
x
p
o
r
t
A
N
D
R
O
I
D
N
D
K
H
O
M
E
=
ANDROID_SDK_ROOT/ndk/16.1.4479499 export ANDROID_NDK_HOME=
ANDROIDSDKROOT/ndk/16.1.4479499exportANDROIDNDKHOME=ANDROID_NDK_ROOT
export ANDROID_HOME=
A
N
D
R
O
I
D
S
D
K
R
O
O
T
e
x
p
o
r
t
J
A
V
A
H
O
M
E
=
/
u
s
r
/
l
i
b
/
j
v
m
/
j
a
v
a
−
8
−
o
p
e
n
j
d
k
−
a
m
d
64
/
e
x
p
o
r
t
J
R
E
H
O
M
E
=
/
u
s
r
/
l
i
b
/
j
v
m
/
j
a
v
a
−
8
−
o
p
e
n
j
d
k
−
a
m
d
64
/
j
r
e
e
x
p
o
r
t
P
A
T
H
=
ANDROID_SDK_ROOT export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ export JRE_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre export PATH=
ANDROIDSDKROOTexportJAVAHOME=/usr/lib/jvm/java−8−openjdk−amd64/exportJREHOME=/usr/lib/jvm/java−8−openjdk−amd64/jreexportPATH=PATH:
A
N
D
R
O
I
D
S
D
K
R
O
O
T
e
x
p
o
r
t
P
A
T
H
=
ANDROID_SDK_ROOT export PATH=
ANDROIDSDKROOTexportPATH=PATH:
A
N
D
R
O
I
D
N
D
K
R
O
O
T
e
x
p
o
r
t
P
A
T
H
=
ANDROID_NDK_ROOT export PATH=
ANDROIDNDKROOTexportPATH={PATH}:
A
N
D
R
O
I
D
H
O
M
E
/
t
o
o
l
s
e
x
p
o
r
t
P
A
T
H
=
{ANDROID_HOME}/tools export PATH=
ANDROIDHOME/toolsexportPATH={PATH}:
A
N
D
R
O
I
D
H
O
M
E
/
p
l
a
t
f
o
r
m
−
t
o
o
l
s
e
x
p
o
r
t
G
R
A
D
L
E
H
O
M
E
=
/
h
o
m
e
/
n
i
e
d
o
n
g
h
u
i
/
.
g
r
a
d
l
e
/
w
r
a
p
p
e
r
/
d
i
s
t
s
/
g
r
a
d
l
e
−
5.6.4
−
b
i
n
/
b
x
i
r
m
19
l
n
f
z
6
n
u
r
b
a
t
n
d
y
y
d
u
x
/
g
r
a
d
l
e
−
5.6.4
e
x
p
o
r
t
P
A
T
H
=
{ANDROID_HOME}/platform-tools export GRADLE_HOME=/home/niedonghui/.gradle/wrapper/dists/gradle-5.6.4-bin/bxirm19lnfz6nurbatndyydux/gradle-5.6.4 export PATH=
ANDROIDHOME/platform−toolsexportGRADLEHOME=/home/niedonghui/.gradle/wrapper/dists/gradle−5.6.4−bin/bxirm19lnfz6nurbatndyydux/gradle−5.6.4exportPATH={PATH}:
G
R
A
D
L
E
H
O
M
E
/
b
i
n
e
x
p
o
r
t
P
A
T
H
=
{GRADLE_HOME}/bin export PATH=
GRADLEHOME/binexportPATH={PATH}:/home/niedonghui/.local/bin
export CMAKE_HOME=/home/niedonghui/Android/Sdk/cmake/3.10.2.4988404/bin
export PATH=
P
A
T
H
:
PATH:
PATH:CMAKE_HOME
export QTDIR=/home/niedonghui/Qt5.12.5/5.12.5/gcc_64
export PATH=
Q
T
D
I
R
/
b
i
n
:
QTDIR/bin:
QTDIR/bin:PATH
export LD_LIBRARY_PATH=
Q
T
D
I
R
/
l
i
b
:
QTDIR/lib:
QTDIR/lib:LD_LIBRARY_PATH