zoukankan      html  css  js  c++  java
  • 【持续更新】面试中的链表

    目录

    1、链表倒数第k个结点

    2、反转链表

    3、合并排序链表

    4、复杂链表的复制

    5、两个链表的第一个公共结点

    6、链表中换的入口

    7、删除链表中重复节点8、单链表的中间节点

    9、从尾到头打印链表

    10、删除指定节点

    11、 反转链表从N到M节点

    12、判断链表是否有环 


    链表是一种最基本的数据结构,在面试中常用来考察应聘者的基本功。指针操作是C/C++中的难点,也是易错点,而链表的操作就是一系列指针操作。所以,链表相关题目在面试中占据着很重要的地位。本文整理了剑指offer中的链表题目以及自己面试中遇到的链表题目进行了总结,希望能对找工作的同学有所帮助。

    //定义链表结构体
    struct ListNode {
    	int val;
    	struct ListNode *next;
    	ListNode(int x) :
    			val(x), next(NULL) {
    	}
    };

    1、链表倒数第k个结点

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

    思路:两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指正走(k-1)步,到达第k个节点。然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了

    class Solution {
    public:
        ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
            if (pListHead == NULL || k == 0) return NULL;
            ListNode* pFirst = pListHead;
            ListNode* pSecond = NULL;
            for (unsigned i = 0; i < k - 1; i++){ //第一个指针先向前走k-1步
                if (pFirst->next != NULL)
                    pFirst = pFirst->next;
                else return NULL;
            }
            pSecond = pListHead;
            while (pFirst->next != NULL){    //两个指针同步向后走,一直到第一个指针走到链表的末尾
                pFirst = pFirst->next;
                pSecond = pSecond->next;
            }
            return pSecond;
        }
    };

    2、反转链表

    题目描述:输入一个链表,反转链表后,输出新链表的表头。

    思路:从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)。

    class Solution {
    public:
        ListNode* ReverseList(ListNode* pHead) {
            //考虑到代码的鲁棒性,先来一步判定链表是否为空;
            if (pHead == NULL) return NULL;
    
            ListNode* pReverseHead = NULL;    //反转后的链表头结点
            ListNode* pNode = pHead;        //反转时的当前节点
            ListNode* pPrev = NULL;        //当前节点的前一个节点
            while (pNode != NULL){
                ListNode* pNext = pNode->next;    //当前节点的后一个节点
                if (pNext == NULL)
                    pReverseHead = pNode;
                 //以下三行是关键
                pNode->next = pPrev;       //指针反转
                pPrev = pNode;            
                pNode = pNext;
            }
            return pReverseHead;
        }
    };

    当然也可以使用递归。

    class Solution {
    public:
        ListNode* ReverseList(ListNode* pHead) {
            //如果链表为空或者链表中只有一个元素
            if(pHead==NULL||pHead->next==NULL) return pHead;
             
            //先反转后面的链表,走到链表的末端结点
            ListNode* pReverseNode=ReverseList(pHead->next);
             
            //再将当前节点设置为后面节点的后续节点
            pHead->next->next=pHead;
            pHead->next=NULL;
             
            return pReverseNode;
             
        }
    };

    3、合并排序链表

    题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

    思路:1)链表1的头结点的值小于链表2的头结点的值,因此链表1的头结点是合并后链表的头结点;

    2)在剩余的结点中,链表2的头结点的值小于链表1的头结点的值,因此链表2的头结点是剩

    3)余结点的头结点,把这个结点和之前已经合并好的链表的尾结点链接起来。

    class Solution {
    public:
        ListNode* Merge(ListNode* list1, ListNode* list2){
            if (list1 == NULL) return list2;
    		if (list2 == NULL) return list1;
    		ListNode *mergeHead = NULL,*current = NULL;
    		while (list1 != NULL && list2 != NULL){
    			if (list1->val <= list2->val){
    				if (mergeHead == NULL) mergeHead = current = list1;
    				else current->next = list1,current = current->next;
    				list1 = list1->next;
    			}
    			else {
    				if (mergeHead == NULL) mergeHead = current = list2;
    				else current->next = list2,current = current->next;
    				list2 = list2->next;
    			}
    		}
    		if (list1 == NULL) current->next = list2;
    		else current->next = list1;
    		return mergeHead;
        }
    };

    递归实现:

    class Solution {
    public:
        ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
        {
            ListNode* node=NULL;
            if(pHead1==NULL){return node=pHead2;}
            if(pHead2==NULL){return node=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;
             
        }
    };

    4、复杂链表的复制

    题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

    思路:

    class Solution {
    public:
        RandomListNode* Clone(RandomListNode* pHead)
        {
            if(!pHead) return NULL;
            RandomListNode *currNode = pHead;
            while(currNode){
                RandomListNode *node = new RandomListNode(currNode->label);
                node->next = currNode->next;
                currNode->next = node;
                currNode = node->next;
            }
            currNode = pHead;
            while(currNode){
                RandomListNode *node = currNode->next;
                if(currNode->random){              
                    node->random = currNode->random->next;
                }
                currNode = node->next;
            }
            //拆分
            RandomListNode *pCloneHead = pHead->next;
            RandomListNode *tmp;
            currNode = pHead;
            while(currNode->next){
                tmp = currNode->next;
                currNode->next =tmp->next;
                currNode = tmp;
            }
            return pCloneHead;
        }
        RandomListNode* pHead;
    };

    5、两个链表的第一个公共结点

    题目描述:输入两个链表,找出它们的第一个公共结点。

    思路:假定 List1长度: a+n  List2 长度:b+n, 且 a<b,那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部,接着p2 再走b+n-(n+a) =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;
    			else p1 = pHead2;
    			if (p2 != NULL)
    				p2 = p2->next;
    			else p2 = pHead1;
    		}
    		return p1;
    	}
    };

    6、链表中换的入口

    题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

    思路:

    假设x为环前面的路程(黑色路程),a为环入口到相遇点的路程(蓝色路程,假设顺时针走), c为环的长度(蓝色+橙色路程)

    当快慢指针相遇的时候:此时慢指针走的路程为Sslow = x + m * c + a,快指针走的路程为Sfast = x + n * c + a,2 Sslow = Sfast,
    2 * ( x + m*c + a ) = (x + n *c + a);
    从而可以推导出:x = (n - 2 * m )*c - a = (n - 2 *m -1 )*c + c - a
    即环前面的路程 = 数个环的长度(为可能为0) + c - a,什么是c - a?这是相遇点后,环后面部分的路程。(橙色路程)
    所以,我们可以让一个指针从起点A开始走,让一个指针从相遇点B开始继续往后走,2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x),从相遇点开始走的那个指针也一定刚好到达环入口点。所以2者会相遇,且恰好相遇在环的入口点。最后,判断是否有环,时间复杂度:O(n),空间复杂度:O(1)。

    class Solution {
    public:
        ListNode* EntryNodeOfLoop(ListNode* pHead)
        {
            if (!pHead->next)
                return NULL;
            ListNode* previous = pHead;
            ListNode* front = pHead ->next;
            while (front)
            {
                previous->next = NULL;
                previous = front;
                front = front->next;
            }
            return previous;
        }
    };

    7、删除链表中重复节点

    题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

    思路:

    1.)加一个头结点

    2.)两个临时指针p,q

    3.)找前后不相等的节点

    class Solution {
    public:
        ListNode* deleteDuplication(ListNode* pHead){
            if (pHead == NULL || pHead->next == NULL)
                return pHead;
            /*---------先为链表创建一个头结点---------*/
            int firstNumber = pHead->val;
            //假设我的头结点数值为-1
            int myFirst = -1;
            //万一链表的头结点也为-1,那么我就改成-2
            if (myFirst == firstNumber) myFirst = -2;
            ListNode *head = new ListNode(myFirst);
            head->next = NULL;
            head->next = pHead;
            ListNode *p = head;
            ListNode *q = head->next;
            while (q){
                while (q->next && (q->next->val == q->val))
                    q = q->next;
                if (p->next != q){
                     
                    q = q->next;
                    p->next = q;
                }
                else{
                    p = q;
                    q = q->next;
                }
            }
            //返回的时候,注意去掉头结点(自己创建的辅助节点)
            return head->next;
        }
    };

    8、单链表的中间节点

    题目描述:输入一个链表,找到链表中间节点。

    思路:设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度O(n)。

    class solution {
    public:
    	// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点
    	ListNode * GetMiddleNode(ListNode * pHead){
    		if (pHead == NULL || pHead->next == NULL) // 链表为空或只有一个结点,返回头指针
    			return pHead;
    		ListNode * pAhead = pHead;
    		ListNode * pBehind = pHead;
    		while (pAhead->next) {// 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
    			pAhead = pAhead->next;
    			pBehind = pBehind->next;
    			if (pAhead->next)
    				pAhead = pAhead->next;
    		}
    		return pBehind; // 后面的指针所指结点即为中间结点
    	}
    };

    9、从尾到头打印链表

    思路:利用栈先进后出,后进先出的特性。

    class solution{
    public:
        //使用栈
    	void PrintList(ListNode * pHead){
    		stack<ListNode*> s;
    		ListNode * pNode = pHead;
    		while (pNode){
    			s.push(pNode);
    			pNode = pNode->next;
    		}
    		while (!s.empty()){
    			pNode = s.top();
    			printf("%d	", pNode->val);
    			s.pop();
    		}
    	}
    
        //使用递归
        void PrintList2(ListNode * pHead){
    		if (pHead == NULL) return;
    		else{
    			PrintList2(pHead->next);
    			printf("%d	", pHead->val);
    		}
    	}
    };

    10、删除指定节点

    题目描述:给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted。

    思路:让该节点的前一个节点指向该节点的下一个节点,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)。

    class solution {
    public:
    	void Delete(ListNode * pHead, ListNode * pToBeDeleted)
    	{
    		if (pToBeDeleted == NULL) return;
    		if (pToBeDeleted->next != NULL){// 将下一节点的数据复制到本节点,然后删除下一个节点
    			pToBeDeleted->val = pToBeDeleted->next->val; 
    			ListNode * temp = pToBeDeleted->next;
    			pToBeDeleted->next = pToBeDeleted->next->next;
    			delete temp;
    		}
    		else { // 要删除的是最后一个节点
    			if (pHead == pToBeDeleted) { // 链表中只有一个节点的情况
    				pHead = NULL;
    				delete pToBeDeleted;
    			}
    			else{
    				ListNode * pNode = pHead;
    				while (pNode->next != pToBeDeleted) // 找到倒数第二个节点
    					pNode = pNode->next;
    				pNode->next = NULL;
    				delete pToBeDeleted;
    			}
    		}
    	}
    };

    11、 反转链表从N到M节点

    题目描述:输入一个链表,翻转链表从N到M个节点。

    输入描述:1,2,3,4,5,6         3,5

    输出描述:1,2,5,4,3,6

    typedef struct ListNode {
    	int val;
    	struct ListNode *next;
    	ListNode(int x) :
    		val(x), next(NULL) {
    	}
    }node;
     
    class Solution {
    public:
    	node *reverseBetween(node *head, int m, int n) {
    		if (head == NULL) return NULL;
    		node *dummy = new node(0);
    		dummy->next = head;
    		node *pre, *start = head;
    		for (int i = 1; i < m; i++) {
    			pre = start;
    			start = start->next;
    		}
    		for (int i = m; i < n; i++) {
    			node *temp = start->next;
    			start->next = temp->next;
    			temp->next = pre->next;
    			pre->next = temp;
    		}
    		return dummy->next;
    	}
    };

    12、判断链表是否有环 

    
    	bool hasCycle(ListNode* root) {
    		if (!root || !root->next)return false;
    		ListNode* fast = root;
    		ListNode* slow = root;
    		while (fast){
    			slow = slow->next;
    			for (int i = 0; i < 2; i++) {
    				if (fast->next == nullptr)return false;
    				fast = fast->next;
    				if (fast = slow)return true;
    			}
    		}
    		return false;
    	}

    参考一

    参考二

    天上我才必有用,千金散尽还复来!
  • 相关阅读:
    Oracle根据两点经纬度计算距离(转载)
    TCP小见解
    git describe功能实现
    UE中基本图形的原始大小是多大
    SQL多行合并与HTML组装,不转义特殊字符
    磁盘空间不足引起ftp报"553 Could not create file"
    一键安装包安装lnmp
    宝塔面板(Linux版)安装与使用
    Redhat7-yum本地源安装配置
    Oracle数据库多个表空间使用情况查询
  • 原文地址:https://www.cnblogs.com/lutaishi/p/13436248.html
Copyright © 2011-2022 走看看