【算法题解】线性表相关

本文在不同平台收集了线性表相关的算法测试题,会持续更新。
当前内容如下

从尾到头打印链表

来源:剑指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(2
N)

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;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值