链表面试题
- 删除链表中等于给定值 val 的所有节点。
- 反转一个单链表。
- 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
- 输入一个链表,输出该链表中倒数第k个结点。
- 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
- 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
- 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
- 链表的回文结构。
- 输入两个链表,找出它们的第一个公共结点。
- 给定一个链表,判断链表中是否有环。
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
- 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深度拷贝。
- 删除链表中等于给定值 val 的所有节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* removeElements(struct ListNode* head, int val){
assert(&head);
if(NULL == head)
return NULL;
Node* pPre = NULL;
Node* pcur = head;
while(pcur)
{
if(val == pcur->val)
{
//删除
if(NULL == pPre)
{
//第一个节点
head = pcur->next;
free(pcur);
pcur = head;
}
else
{
//非第一个节点
pPre->next = pcur->next;
free(pcur);
pcur = pPre->next;
}
}
else
{
pPre = pcur;
pcur = pcur->next;
}
}
return head;
}
//总结:我们用结构体封装链表struct list,其实就是在封装了一层地址, 然后传的参数其实都是默认二级指针
//的,因为不带头链表,在改变头节点指向的时候必须传二级指针,才能改变头节点指针的指向(原因就类似于
//Swap函数,传一级指针的地址才能改变一级指针的指向)。 而我们平时在使用的时候用s->_pHead 来访问一
//级指针,其实就是给s(二级指针解引用了一次)。
- 反转一个单链表。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* reverseList(struct ListNode* head){
Node* pPre = NULL;
Node* pNext = NULL;
Node* pcur = head;
while(pcur)
{
pNext = pcur->next;//首先标记pcur的下一个节点
pcur->next = pPre;//然后断开pcur与后面连接的指针,让其指向前(第一次的时候为空)
pPre = pcur;//让前一个节点往后走一个
pcur = pNext;//让pcur往后走一个
}
return pPre;最后一次pcur走到NULL了,所以要返回前一个节点(连接的是反转后的链表)
}
- 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* middleNode(struct ListNode* head){//head 是第一个有效节点
Node* pFast = head;//标记快的一次走两步
Node* pSlow = head;//标记慢的一次走一步
while(pFast && pFast->next)//两个条件有一个满足,循环即停止,中间要用&&。
{
pSlow = pSlow->next;//返回第二个中间结点:让慢指针先走;返回第一个中间结点:让快指针先走
pFast = pFast->next->next;
}
return pSlow;
}
- 输入一个链表,输出该链表中倒数第k个结点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if( pListHead == NULL || k == 0 )//判空
{
return NULL;
}
ListNode* pSlow = pListHead;
ListNode* pFast = pListHead;
while(--k)//k是无符号数,故不能取到负值 这里循环k-1步 画图理解,倒数第k个节点 快的指针先走k-1步
{
if(pFast == NULL)
return NULL;
pFast = pFast->next;
}
if(pFast == NULL)
return NULL;
while(pFast->next != NULL)
{
pFast = pFast->next;
pSlow = pSlow->next;
}
return pSlow;
}
};
- 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
Node head;//建立一个头节点去连接,最后返回头节点的后一个就OK
Node* pL1 = l1;
Node* pL2 = l2;
Node* pTail = &head;
if(pL1 == NULL)
return pL2;
else if(pL2 == NULL)
return pL1;
else if(pL1 == NULL && pL2 == NULL)
return NULL;
else
{
while(pL1 && pL2)
{
if(pL1->val <= pL2->val)//谁小pTail走到谁
{
pTail->next = pL1;
pL1 = pL1->next;
}
else
{
pTail->next = pL2;
pL2 = pL2->next;
}
pTail = pTail->next;
}
if(pL1 == NULL)
pTail->next = pL2;//第一个链表为空了,pTail在其上,然后把pTail连接到另一个上
else
pTail->next = pL1;//同理
return head.next;
}
}
- 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
if(pHead == NULL)//一开始就需要判空
return NULL;
ListNode Head1;//建立一个新的头节点用来连接比x小的链表
ListNode* pTail1 = &Head1;//用来标记比x小的链表的最后一个节点
ListNode Head2;//建立一个新的头节点用来连接比x大或等的链表
ListNode* pTail2 = &Head2;//用来标记比x大或等的链表的最后一个节点
ListNode* pCur = pHead;//用来遍历原始链表
while(pCur)//建立循环遍历原始链表
{
if(pCur->val < x)
{
pTail1->next = pCur;
pTail1 = pTail1->next;
}
else
{
pTail2->next = pCur;
pTail2 = pTail2->next;
}
pCur = pCur->next;
}
pTail1->next = NULL;//新建立的两个链表最后都必须连上NULL才是一个完整链表
pTail2->next = NULL;//新建立的两个链表最后都必须连上NULL才是一个完整链表
pTail1->next = Head2.next;//将新建立的两个链表尾头相连
return Head1.next;
}
};
从这开始对答案
- 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(NULL == pHead)
return NULL;
if(pHead != NULL && NULL == pHead->next)
return pHead;
ListNode* Head = NULL;
ListNode* pPre = Head;
ListNode* pCur = pHead;
ListNode* p = pCur->next;
while(pCur->next)
{
int Flag = 0;
ListNode* pPre1 = pCur;
while(p)
{
if(pCur->val == p->val)
{
pPre1->next = p->next;
free(p);
p = pPre1->next;
Flag = 1;
}
else
{
p = p->next;
pPre1 = pPre1->next;
}
}
if(Flag == 0)
pCur = pCur->next;
else
{
pPre->next = pCur->next;
free(pCur);
pCur = pPre->next;
}
}
return Head->next;
}
};
- 链表的回文结构。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
// write code here
if(NULL == A)
return true;
int* p = (int*)malloc(sizeof(int)*900);//将链表中的所有元素放置于新建数组中
ListNode* pCur = A;
int size = 0;
while(pCur)
{
p[size++] = pCur->val;
pCur = pCur->next;
}
int begin = 0;//然后用数组的头尾指针移动去比较数组里的所有元素是否为回文结构
int end = size - 1;
while(begin < end)
{
if(p[begin] != p[end])
{
free(p);
return false;
}
begin++;
end--;
}
free(p);
return true;
}
};
- 输入两个链表,找出它们的第一个公共结点。
前提是链表不带环
链表相交的概念:说明链表的尾节点一定重合
思路一:(1)haedA 或 headB有null直接返回null
(2)分别测A和B全长,并对比尾节点是否一致,不一致直接返回null
(3)计算两个链长度差d,设置两个指针分别在链头,长链头指针先移动d步.然后两个链指针同步,指针地址第一次相等时即为交点.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *a = headA;
struct ListNode *b = headB;
struct ListNode *tmp = NULL;
int len_a =0;
int len_b =0;
//计算链表a的长度
while(a)
{
if (a->next)
{
a = a->next;
} else
{
break;
}
len_a++;
}
//计算链表b的长度
while(b)
{
if (b->next)
{
b = b->next;
} else
{
break;
}
len_b++;
}
//调整两个链表长度
a = headA;
b = headB;
if (len_a > len_b)
{
int times = len_a - len_b;
while(times--)
{
a = a->next;
}
} else
{
int times = len_b - len_a;
while(times--)
{
b = b->next;
}
}
//遍历找出入口
while (a && b) //两个节点的比较
{
if(a==b)//节点(指针)可以直接相等
{
tmp = a;
break;
}
a = a->next;
b = b->next;
}
return tmp;
}
思路二:建环,将链表中的一个连成环
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null)
{
return null;
}
ListNode last = headB;
while (last.next != null)
{
last = last.next;
}
last.next = headB;
ListNode fast = headA;
ListNode slow = headA;
while (fast != null && fast.next != null)
{
slow = slow.next;
fast = fast.next.next;
if (slow == fast)
{
slow = headA;
while (slow != fast)
{
slow = slow.next;
fast = fast.next;
}
last.next = null;
return fast;
}
}
last.next = null;
return null;
}
}
- 给定一个链表,判断链表中是否有环。
快慢指针,从链表头开始往后走,如果有环必相遇
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
if(head == NULL)
return false;
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)//链表的快慢节点相遇
return true;
}
return false;
}
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
思路:分两个步骤,首先通过快慢指针的方法判断链表是否有环;
接下来如果有环,则寻找入环的第一个节点。具体的方法为,
首先假定链表起点到入环的第一个节点A的长度为a【未知】,
到快慢指针相遇的节点B的长度为(a + b)【这个长度是已知的】。
现在我们想知道a的值,注意到快指针p2始终是慢指针p走过长度的2倍,
所以慢指针p从B继续走(a + b)又能回到B点,如果只走a个长度就能回到节点A。
但是a的值是不知道的,解决思路是曲线救国,注意到起点到A的长度是a,
那么可以用一个从起点开始的新指针q和从节点B开始的慢指针p同步走,相遇的地方必然是入环的第一个节点A。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *fast=head;
struct ListNode *slow=head;
bool have_cycle = false;
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast && fast==slow){
have_cycle = true;
break;
}
}
if (have_cycle) {
fast = head; // 复用快指针
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
return NULL;
}
- 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深度拷贝。
这道题用C++(后面学习在回来理解一下)
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node() {}
Node(int _val, Node* _next, Node* _random) {
val = _val;
next = _next;
random = _random;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head==NULL) return NULL;
copy1(head);
copy2(head);
return copy3(head);
}
void copy1(Node* head)//第一步,复制每一个节点,连接在原节点的后面,random为空
{
Node* node = head;
while(node!=NULL)
{
Node* copynode = new Node(node->val,node->next,nullptr);
node->next = copynode;
node = copynode->next;
}
}
void copy2(Node* head)//第二部,复制random节点
{
Node* node = head;
while(node!=NULL)
{
Node* copynode = node->next;
if(node->random!=nullptr)
{
copynode->random = node->random->next;
}
node = copynode->next;
}
}
Node* copy3(Node* head)//第三步,拆分为两个链表
{
Node* node = head;
Node* copyhead = nullptr;
Node* copynode = nullptr;
if(node!=nullptr)
{
copyhead = copynode = node->next;
node->next = copynode->next;
node = node->next;
}
while(node!=nullptr)
{
copynode->next = node->next;
copynode = copynode->next;
node->next = copynode->next;
node = node->next;
}
return copyhead;
}
};