数据结构面试题及答案+字节跳动+百度+阿里巴巴高频面试题之链表专题(二)
高频面试题之链表专题公开课(二)
本节目标
-
1、逆置一个单链表。(2020年阿里巴巴二面原题)
-
2、判断单链表是否是回文结构。(2019年字节跳动二面原题)
-
3、删除一个有序单链表中的重复节点。(2019年字节跳动二面原题)
-
4、复杂链表的复制。(2020年百度二面原题)
1、逆置一个单链表。
OJ链接:https://leetcode-cn.com/problems/reverse-linked-list/description/
高频考察的大厂云图:
解题思路:
- 三指针翻转法:记录连续的三个节点,原地修改节点指向的方向。
- 头插法:取下原链表的每个节点都进行头插到新链表。
代码实现:
// 三个指针翻转的思路
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 链表为空或者只有一个节点不需要翻转
if(head == NULL || head->next == NULL)
return head;
ListNode* n1, *n2, *n3;
n1 = NULL;
n2 = head;
n3 = n2->next;
//中间节点不为空,继续修改指向
while(n2)
{
//中间节点指向反转
n2->next = n1;
//更新三个连续的节点
n1 = n2;
n2 = n3;
// 需要注意到最后两个节点时,n3已经为空
if(n3)
n3 = n3->next;
}
//返回新的头
return n1;
}
};
// 头插的思路
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* newhead = NULL;
ListNode* cur = head;
while(cur)
{
struct ListNode* next = cur->next;
//头插新节点,更新头
cur->next = newhead;
newhead = cur;
cur = next;
}
return newhead;
}
};
2、判断单链表是否是回文结构。
高频考察的大厂云图:
解题思路:
需要注意本题要求了复杂度为O(N),空间复杂度为O(1)
1、本题说明了链表的长度小于900,所以这里我们可以开辟一个900个空间的数组,将链表的数据放到数组中再进行判断。这种方法虽然能过OJ,但是实际面试中是不符合面试官的要求的。
2、通过slow走一步,fast走两步,找到中间节点。然后将后半段链表反转,反转后的链表跟原链表对比是否相同,相同则是回文结构。这样写才满足上面的对复杂度的要求。
代码实现:
/*
此题也可以先把链表中的元素值全部保存到数组中,然后再判断数组是否为回文。不建议使用这种解法,因为如果没有告诉链表最大长度,则不能同此解法
*/
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
// write code here
int a[900] = {0};
ListNode* cur = A;
int n = 0;
//保存链表元素
while(cur)
{
a[n++] = cur->val;
cur = cur->next;
}
//判断数组是否为回文结构
int begin = 0, end = n-1;
while(begin < end)
{
if(a[begin] != a[end])
return false;
++begin;
--end;
}
return true;
}
};
/*
解题思路:
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
*/
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
if (A == NULL || A->next == NULL)
return true;
ListNode* slow, *fast, *prev, *cur, *nxt;
slow = fast = A;
//找到中间节点
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
prev = NULL;
//后半部分逆置
cur = slow;
while (cur)
{
nxt = cur->next;
cur->next = prev;
prev = cur;
cur = nxt;
}
//逐点比对
while (A && prev)
{
if (A->val != prev->val)
return false;
A = A->next;
prev = prev->next;
}
return true;
}
};
3、删除一个有序单链表中的重复节点。
高频考察的大厂云图:
解题思路:
- 定义三个指针n1、n2、n3,使用n2和n3去找链表中的重复阶段区间段,找到以后,删除n2到n3中间的重复区间,然后让n1,链接上重复区间后面一段。再继续往后找重复区间,再重复上面的过程。ps:需要注意的是本题逻辑并不难,但是代码控制中有很多边界条件需要注意,控制不好,代码很容易崩溃。
代码实现:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
/*
解题思路:
此题可以先找出相同节点的区间,然后删除区间中的所有值,直到把链表遍历完结束
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == NULL || pHead->next == NULL)
return pHead;
ListNode* n1 = NULL;
ListNode* n2 = pHead;
ListNode* n3 = n2->next;
while(n3 != NULL)
{
//如果相邻节点不相同,则不需要删除,更新节点,继续向后遍历
if(n2->val != n3->val)
{
n1 = n2;
n2 = n3;
n3 = n3->next;
}
else
{
//如果相邻节点相同
//则n3去找第一个不相同的节点
while(n3 && n3->val == n2->val)
{
n3 = n3->next;
}
//重新链接,如果要删除的包括头节点,则更新头节点
if(n1)
n1->next = n3;
else
pHead = n3;
// 删除掉重复的节点
while(n2 != n3)
{
ListNode* next = n2->next;
delete n2;
n2 = next;
}
//更新节点
n2 = n3;
if(n3)
n3 = n3->next;
}
}
return pHead;
}
};
4、复杂链表的复制
OJ链接:https://leetcode-cn.com/problems/copy-list-with-random-pointer/description/
高频考察的大厂云图:
解题思路:
此题可以分三步进行:
- 拷贝链表的每一个节点,拷贝的节点先链接到原链表被拷贝节点的后面
- 处理拷贝节点的random指针:拷贝节点的random指针指向被拷贝节点随机指针的下一个位置
- 拆解链表,把拷贝的链表从原链表中拆解出来,再链接出拷贝链表
代码实现:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
// 1.拷贝链表,并插入到原节点的后面
Node* cur = head;
while(cur)
{
Node* copy = new Node(cur->val);
Node* next = cur->next;
// 插入
cur->next = copy;
copy->next = next;
// 迭代往下走
cur = next;
}
// 2.置拷贝节点的random
cur = head;
while(cur)
{
Node* copy = cur->next;
if(cur->random != NULL)
copy->random = cur->random->next;
else
copy->random = NULL;
cur = copy->next;
}
// 3.解拷贝节点,链接拷贝节点
Node* copyHead = NULL, *copyTail = NULL;
cur = head;
while(cur)
{
Node* copy = cur->next;
Node* next = copy->next;
// copy解下来尾插链接到拷贝节点
if(copyTail == NULL)
{
copyHead = copyTail = copy;
}
else
{
copyTail->next = copy;
copyTail = copy;
}
cur->next = next;
cur = next;
}
return copyHead;
}
};
/*思路跟上面的方法基本一致,差别不再是拷贝节点挂在原节点后面建立映射关系,而是通过将原节点和拷贝节点存储到map中建立映射关系,这样也是通过3步就可以复制出拷贝链表*/
// 1.拷贝链表,使用map建立原链表节点和拷贝节点的映射
// 2.置拷贝节点的random. 通过map找到node->random的拷贝节点,就可以值copy节点的random。
// 3.从map中找到拷贝节点,链接拷贝节点
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*, Node*> nodeCopyMap;
// 1.拷贝链表,使用map建立原链表节点和拷贝节点的映射
Node* cur = head;
while(cur)
{
Node* copy = new Node(cur->val);
nodeCopyMap[cur] = copy;
cur = cur->next;
}
// 2.置拷贝节点的random
cur = head;
while(cur)
{
Node* copy = nodeCopyMap[cur];
if(cur->random != NULL)
copy->random = nodeCopyMap[cur->random];
else
copy->random = NULL;
cur = cur->next;
}
// 3.从map中找到拷贝节点,链接拷贝节点
Node* copyHead = NULL, *copyTail = NULL;
cur = head;
while(cur)
{
// 找到cur映射的拷贝节点
Node* copy = nodeCopyMap[cur];
if(copyTail == NULL)
{
copyHead = copyTail = copy;
}
else
{
copyTail->next = copy;
copyTail = copy;
}
cur = cur->next;
}
return copyHead;
}
};
如果你看了以后不是很明白,你可以点击看下面的视频讲解:
视频讲解(鼠标点这里)
博主和团队推出一个免费的公众号栏目:IT笔试面试真题讲解,每天发布一个视频讲解IT公司笔试面试真题。
欢迎扫码关注哦