本文在不同平台收集了线性表相关的算法测试题,会持续更新。
当前内容如下
从尾到头打印链表
来源:剑指offer 牛客网
有三种思路:
第一就是利用栈先入后出的特性完成;
第二就是将链表元素顺序存下来,然后进行数组翻转;
第三是利用递归。
1.栈思路:
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> value;
ListNode *p=NULL;
p=head;
stack<int> stk;
while(p!=NULL){ //将链表元素顺序入栈
stk.push(p->val);
p=p->next;
}
while(!stk.empty()){ //出栈,元素保存至vector
value.push_back(stk.top());
stk.pop();
}
return value;
}
};
2.数组翻转:
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> value;
ListNode *p=NULL;
p=head;
while(p!=NULL){
value.push_back(p->val);
p=p->next;
}
//reverse(value.begin(),value.end()); //可以用C++自带的翻转函数
//也可以自己实现翻转
int temp=0;
int i=0, j=value.size()-1;
while(i<j){
temp=value[i]; //也可以用swap函数,swap(value[i],value[j]);
value[i]=value[j];
value[j]=temp;
i++; j--;
}
return value;
}
};
递归思路:
class Solution {
public:
vector<int> value;
vector<int> printListFromTailToHead(ListNode* head) {
ListNode *p=NULL;
p=head;
if(p!=NULL){
if(p->next!=NULL){
printListFromTailToHead(p->next);
}
value.push_back(p->val);
}
return value;
}
};
排序链表的合并
题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
来源:剑指offer
方法1:常规思路,建立一个新链表,通过依次比较两个输入链表的节点大小,连接到新链表。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(!pHead1)
return pHead2;
if(!pHead2)
return pHead1;
ListNode* pHead; //头指针
if( (pHead1->val) <(pHead2->val) ){
pHead = pHead1;
pHead1 = pHead1->next ;
}else{
pHead = pHead2;
pHead2 = pHead2->next ;
}
ListNode* cur_p = pHead ; //工作指针
while( pHead1 && pHead2 ){ //二者均不为空
if( (pHead1->val) <(pHead2->val) ){
cur_p->next = pHead1;
pHead1 = pHead1->next ;
cur_p = cur_p->next;
}else{
cur_p->next = pHead2;
pHead2 = pHead2->next ;
cur_p = cur_p->next;
}
}
if(pHead1 == NULL) //若链表1遍历完了
cur_p->next = pHead2;
if(pHead2 == NULL) //若链表2遍历完了
cur_p->next = pHead1;
return pHead;
}
};
方法2:递归方法
//来源:https://www.nowcoder.com/questionTerminal/d8b6b4358f774294a89de2a6ac4d9337?f=discussion
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* node=NULL;
if(pHead1==NULL){return pHead2;}
if(pHead2==NULL){return pHead1;}
if( pHead1->val > pHead2->val ){
node=pHead2;
node->next=Merge(pHead1,pHead2->next);
}else
{
node=pHead1;
node->next=Merge(pHead1->next,pHead2);
}
return node;
}
};
反转链表
来源:剑指offer
题目描述:输入一个链表,反转链表后,输出新链表的表头。
解法一:通过记录当前节点、当前节点的下一节点、当前节点的上一节点来遍历实现反转。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead==NULL) return NULL;
ListNode* pNode=pHead;//当前指针
ListNode* pPrev=NULL;//当前指针的前一个结点
while(pNode!=NULL){//当前结点不为空时才执行
ListNode* pNext=pNode->next;//链断开之前一定要保存断开位置后边的结点
pNode->next=pPrev;//指针反转
pPrev=pNode;
pNode=pNext;
}
//当pNode为空时,跳出上面的循环,此时pPrev就是新的头节点
return pPrev;
}
};
解法二:递归法
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
//如果链表为空或者链表中只有一个元素
if(pHead==NULL||pHead->next==NULL) return pHead;
//先反转后面的链表,走到链表的末端结点
ListNode* pReverseNode=ReverseList(pHead->next);
//反转指针,即将pHead下一节点的next点设置为pHead【p1->p2 ==> p1<-p2】
pHead->next->next=pHead;
pHead->next=NULL;
return pReverseNode;
}
};
输出单链表倒数第 K 个节点
题目:输入一个单链表,输出此链表中的倒数第 K 个节点。(去除头结点,节点计数从 1 开始)。
转自:链表算法面试问题,看我就够了
方法1:两次遍历法.O(2N)
(1)遍历单链表,遍历同时得出链表长度 N 。
(2)再次从头遍历,访问至第 N - K 个节点为所求节点。
方法2:递归法.O(2N)
int num;//定义num值
ListNode* findKthTail(ListNode* pHead, int k) {
num = k;
if(pHead == NULL)
return NULL;
//递归调用
ListNode* pCur = findKthTail(pHead->next, k);
if(pCur != NULL)
return pCur;
else{
num--;// 递归返回一次,num值减1
if(num == 0)
return pHead;//返回倒数第K个节点
return NULL;
}
}
方法3:双指针法.O(N)
(1)定义两个指针 p1 和 p2 分别指向链表头节点。
(2)p1 前进 K 个节点,则 p1 与 p2 相距 K 个节点。
(3)p1,p2 同时前进,每次前进 1 个节点。
(4)当 p1 指向到达链表末尾,由于 p1 与 p2 相距 K 个节点,则 p2 指向目标节点。
ListNode* findKthTail(ListNode *pHead, int K){
if (NULL == pHead || K == 0)
return NULL;
//p1,p2均指向头节点
ListNode *p1 = pHead;
ListNode *p2 = pHead;
//p1先出发,前进K个节点
for (int i = 0; i < K; i++) {
if (p1)//防止k大于链表节点的个数
p1 = p1->_next;
else
return NULL;
}
while (p1)//如果p1没有到达链表结尾,则p1,p2继续遍历
{
p1 = p1->_next;
p2 = p2->_next;
}
return p2;//当p1到达末尾时,p2正好指向倒数第K个节点
}
两个链表的第一个公共节点
假定 List1长度: a+n ;List2 长度:b+n, 且 a<b
那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部
接着p2 再走b-a 步到链表尾部,这时p1也走到List2的b-a位置,还差a步就到可能的第一个公共节点。
将p2 换成 List1头部,p2走a步也到可能的第一个公共节点。如果恰好p1==p2,那么p1就是第一个公共节点。 或者p1和p2一起走n步到达列表尾部,二者没有公共节点,退出循环。 同理a>=b.
时间复杂度O(n+a+b)
class Solution {
public:
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
while(p1 != p2) {
if(p1 != NULL) p1 = p1->next;
if(p2 != NULL) p2 = p2->next;
if(p1 != p2) {
if(p1 == NULL) p1 = pHead2;
if(p2 == NULL) p2 = pHead1;
}
}
return p1;
}
};
删除排序链表中重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/*
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* Head = new ListNode(0);
Head->next = pHead;
// pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索。
ListNode* pre = Head;
ListNode* last = Head->next;
while(last != NULL ){
if(last->next != NULL && last->val != last->next->val){
pre = last; //不重复的节点直接移动pre指针即可
}
else if(last->next != NULL && last->val == last->next->val){
while(last->next != NULL && last->val == last->next->val){
last = last->next; //重复的节点让它一直移动
}
pre->next = last->next;
}
last = last->next;
}
return Head->next;
}
};
链表中环的入口节点
题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:
参考链接:牛客网
首先给出两个结论:
1、设置快慢指针,假如有环,他们最后一定相遇。
2、两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇与环入口。
证明结论1:设置快慢指针fast和low,fast每次走两步,low每次走一步。假如有环,两者一定会相遇(因为low一旦进环,可看作fast在后面追赶low的过程,每次两者都接近一步,最后一定能追上)。
证明结论2:
设:
链表头到环入口长度为–a
环入口到相遇点长度为–b
相遇点到环入口长度为–c
则:相遇时
快指针路程=a+(b+c)k+b ,k>=1 其中b+c为环的长度,k为绕环的圈数(k>=1,即最少一圈,不能是0圈,不然和慢指针走的一样长,矛盾)。
慢指针路程=a+b
快指针走的路程是慢指针的两倍,所以:
(a+b)*2=a+(b+c)k+b
化简可得:
a=(k-1)(b+c)+c 这个式子的意思是: 链表头到环入口的距离=相遇点到环入口的距离+(k-1)圈环长度。其中k>=1,所以k-1>=0圈。所以两个指针分别从链表头和相遇点出发,最后一定相遇于环入口。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode *fast=pHead, *low=pHead;
while(fast&&fast->next){
fast=fast->next->next;
low=low->next;
if(fast==low)
break;
}
if(!fast||!fast->next)return NULL;
low=pHead;//low从链表头出发
while(fast!=low){//fast从相遇点出发
fast=fast->next;
low=low->next;
}
return low;
}
};
删除链表中节点,要求时间复杂度为O(1)
问题描述:
给定一个单链表中的表头和一个等待被删除的节点。请在 O(1) 时间复杂度删除该链表节点。并在删除该节点后,返回表头。
转自:链表算法面试问题,看我就够了
示例:
给定 1->2->3->4,和节点 3,返回 1->2->4。
解题思想:
最普通的方法就是遍历链表,复杂度为O(n)。
如果我们把删除节点的下一个结点的值赋值给要删除的结点,然后删除这个结点,这相当于删除了需要删除的那个结点。因为我们很容易获取到删除节点的下一个节点,所以复杂度只需要O(1)。
示例:
单链表:1->2->3->4->NULL
若要删除节点 3 。第一步将节点3的下一个节点的值4赋值给当前节点。变成 1->2->4->4->NULL,然后将就 4 这个结点删除,就达到目的了。 1->2->4->NULL
如果删除的节点的是头节点,把头结点指向 NULL。
如果删除的节点的是尾节点,那只能从头遍历到头节点的上一个结点。
图解过程:
代码实现:
void deleteNode(ListNode *pHead, ListNode* pDelNode) {
if(pDelNode == NULL)
return;
if(pDelNode->next != NULL){
ListNode *pNext = pDelNode->next;
//下一个节点值赋给待删除节点
pDelNode->val = pNext->val;
//待删除节点指针指后面第二个节点
pDelNode->next = pNext->next;
//删除待删除节点的下一个节点
delete pNext;
pNext = NULL;
}else if(*pHead == pDelNode)//删除的节点是头节点
{
delete pDelNode;
pDelNode= NULL;
*pHead = NULL;
} else//删除的是尾节点
{
ListNode *pNode = *pHead;
while(pNode->next != pDelNode) {
pNode = pNode->next;
}
pNode->next = NULL;
delete pDelNode;
pDelNode= NULL;
}
}