链表面试习题总结(一)

前言:
最近在复习数据结构方面的知识,总结知识, 同时也温故而知新!
本章介绍链表往年面试常考题集一 (共6道题)。 希望能够帮助到您!

一、移除链表元素 – leetcode

题目链接

描述:
        删除链表中等于给定值val的所有节点。
示例:
        输入: 1->2->6->3->4->5->6, val = 6
        输出: 1->2->3->4->5

分析:
        这道题思路很简单, 我们采用双指针法。 一个指针指向删除的结点A, 另一个指针指向被删除结点的前一个结点B。 然后结点B指向结点A的下一个结点, 删除结点A。 更新结点A、结点B。
在这里插入图片描述
源码:

ListNode* removeElements(ListNode* head, int val) {
        
        ListNode* cur = head;
        ListNode* prev = nullptr;

        while(cur) {

            if(cur->val == val) {
                //找到了 - 删除 - 连接

                if(!prev) {
                    //删除的是头节点 - 更新头结点信息
			        
			        ListNode* tmp = cur->next;  	//保存下一个结点    
			        head = tmp;						//头节点偏移		
                    delete cur;						//释放该结点
				    cur = tmp;						//更新cur
                }
                else {
                    //删除非头节点

                    ListNode* tmp = cur->next;		//保存下一个结点
                    prev->next = tmp;				//连接
			        delete cur;						//释放该结点                   
                    cur = tmp;						//更新
                }
            }
            else {
                prev = cur;							//prev后移
                cur = cur->next;					//cur后移
            }

        }
        return head;

测试结果:
在这里插入图片描述

二、反转链表 – leetcode

题目链接

描述:
        反转一个链表
示例:
        输入:1->2->3->4->5->NULL
        输出:5->4->3->2->1->NULL

分析:
        反转一个链表, 我们使用三指针法, prev、cur、next三个指针, 这三个指针分别连续指向三个节点。 cur指针指向prev, 这样就反转了一个结点。 然后更新prev、cur、next三个结点信息, 以此类推!
在这里插入图片描述
源码:

 ListNode* reverseList(ListNode* head) {
        
        ListNode* prev = nullptr;
        ListNode* cur = head;
        ListNode* next = head;

        while(cur) {          
            next = cur->next;           //更新next           
            cur->next = prev;			//更改指向
            prev = cur;					//更新prev
            cur = next;					//更新cur
        }  
        	
        head = prev;					//更新head指向
        return head;
    }

测试结果:
在这里插入图片描述

三、链表的中间结点 – leetcode

题目链接

描述:
        给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
        如果有两个中间结点,则返回第二个中间结点。
示例1:
        输入:[1,2,3,4,5]
        输出: 3
示例2:
        输入:[1,2,3,4,5,6]
        输出:4

分析:
这道题是有规律的, 我们定义两个指针slow、fast,一开始都指向head。 slow指针一次偏移1步, fast指针一次偏移2步。 当fast走到头时, 此时slow指向的结点就是中间结点。

当fast->next结点不为空时, 还需要偏移。(个人设计不一样!)
在这里插入图片描述
源码:

 ListNode* middleNode(ListNode* head) {
        //头节点为空
        if(head == nullptr) {
            return nullptr;
        }

        ListNode* slow = head;
        ListNode* fast = head;

        while(fast->next) {         
            fast = fast->next->next;		//fast偏移
            slow = slow->next;				//slow偏移

            if(!fast) {						//fast指针为空
                break;
            }
        }
        return slow;
    }

测试结果:
在这里插入图片描述

四、链表中倒数第K个结点 – 牛客网

题目链接

描述:
         输入一个链表,输出该链表中倒数第k个结点。

分析:
        我们可以定义两个快慢指针fast、slow, 让fast一开始偏移K步, 然后一起偏移, 最终slow指向的结点就是我们想要的结点。(当然也可以让fast一开始偏移k-1步, 个人设计不同!)
在这里插入图片描述
如果一开fast偏移k-1步, 最后只不过是循环fast判断条件不同罢了!

源码:

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        
        if(!pListNode) {
        	return nullptr;
        }
        
        ListNode* slow = pListHead;
        ListNode* fast = pListHead;
        
        for(int i = 0; i < k; ++i) {
            if(!fast) {					//如果fast为空, 证明该链表没有K这么长。
                return nullptr;			//则这道题没有意义。返回nullptr
            }
            fast = fast->next;
        }
        
        while(fast) {
            fast = fast->next;			//fast偏移
            slow = slow->next;			//slow偏移
        }       
        return slow;
    }

测试结果:
在这里插入图片描述

五、合并两个有序链表 – leetcode

题目链接

描述:
        将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
         输入:1->2->4, 1->3->4
         输出:1->1->2->3->4->4

分析:
在数据结构教科书中链表就有这道题, 这道题也不难, 理清思路很关键!
我们先从这两个链表中找起始值最小的作为我们最终连接的主链表。(这样做的好处不需要开辟空间,缺点就是原来的这两条链表不能再用了, 因为是在这两个链表上做的。)
在这里插入图片描述
我画了最繁琐的, 好需要考虑一个链表走完了,另一个链表没有走弯,好需要链接没有走完的。

源码:

 ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        
        if(l1 == nullptr) {					//l1链表为空,直接返回l2
            return l2;
        }
        else if(l2 == nullptr) {			//l2链表为空,直接返回l1
            return l1;
        }

        ListNode* head  = nullptr;			//head为连接后合并链表头节点
        ListNode* tmp1 = l1;
        ListNode* tmp2 = l2;
        
        if(l1->val < l2->val) {				//选取起始结点最小值链表
            head = l1;
            tmp1 = tmp1->next;
        }
        else {
            head = l2;
            tmp2 = tmp2->next;
        }

        ListNode* tmp = head;				//head结点的偏移结点。-- 连接节点

        while(tmp1 && tmp2) {				
            if(tmp1->val <= tmp2->val) {	//l1->val < l2->val
                //连接tmp1
                tmp->next = tmp1;			//连接
                tmp = tmp1;					//更新tmp
                tmp1 = tmp1->next;			//tmp1指向下一个结点
            }
            else {
                //连接tmp2
                tmp->next = tmp2;			//连接
                tmp = tmp2;					//更新tmp
                tmp2 = tmp2->next;			//tmp2指向下一个结点
            }
        }

        if(!tmp1) {							//l1走完了
            tmp->next = tmp2;				//连接l2剩余结点
        }
        else if(!tmp2) {					//l2走完了
            tmp->next = tmp1;				//连接l1剩余结点
        }
        return head;						//返回合并链表头结点head
    }

测试结果:
在这里插入图片描述

六、链表分割 – 牛客网

题目链接

描述:
        编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。
给定一个链表的头指针 ListNode* pHead,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。

分析:
        我们的思路是创建两个链表头结点L1,L2, L1链表连接的是小于x的结点。 L2链表连接的是大于等于x的结点。 最后将L1、L2链表合并。
在这里插入图片描述
源码:

 ListNode* partition(ListNode* pHead, int x) 
    {
        // write code here
        if(!pHead){
            return nullptr;
        }
        
        //LF == L1,  RF == L2
        //LB为LF的连接指针, LB为头指针
        //RB为RF的连接指针, RB为头指针
        ListNode *LF, *LB, *RF, *RB;
        LF = nullptr;
        RF = nullptr;
        LB = LF;
        RB = RF;
        
        while(pHead) {
            if(pHead->val < x) {			//LF链表添加一个
                //连接在LF
                if(!LF) {					//一开始LF为空。
                    LF = pHead;
                    LB = LF;
                }
                else {
                    LB->next = pHead;		//连接
                    LB = LB->next;			//偏移
                }
            }
            else {
                //连接在RF
                if(!RF) {
                    RF = pHead;				//一开始RF为空
                    RB = RF;				
                }
                else {
                    RB->next = pHead;		//连接
                    RB = RB->next;			//偏移
                }
            }
            pHead = pHead->next;			//偏移
        }
        
        if(!LF) {							//LF为空,代表没有小于x的值
            RB->next = nullptr;
            return RF;						//返回RF
        }
        
        if(!RF) {							//RF为空,代表没有大于等于x的值
            LB->next = nullptr;
            return LF;						//返回LF
        }
        
        LB->next = RF;						//连接LF和RF两个链表
        RB->next = nullptr;     			//RF链表最后结点置为nullptr
        return LF;							//返回LF
    }

测试结果:
在这里插入图片描述

        本章到此结果, 本人可能组织语言不太好, 有些知识真的是会,但是讲不出, 有种只会意会不可言传的感觉。 请见谅!

如果有误, 欢迎指导!!!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值