面试题–链表

  1. 从尾到头输出链表
    Question:输入一个链表的头结点,从尾到头反过来打印出每个节点的值。
    典型的后进先出,可以使用栈实现。

    void Revers_Pring(ListNode* pHead)
    {
         std::stack<ListNode*> nodes;
         ListNode* pNode = pHead;
         while(pNode != NULL)
         {
             modes.push(pNode);
             pNode = pNode->pNext;
         }
         while(!nodes.empty())
         {
             pNode = nodes.top();
             printf("%d\t",pNode->data);
             nodes.pop();
         }
    }

    递归本质上就是栈,所以也可以用递归实现

    void ReversPrint(ListNode* pHead)
    {
         if(pHead!=NULL)
         {
              if(pHead->pNext != NULL)
              {
                   ReversPrint(pHead->pNext);
              }
              printf("%d\t",pHead->data);
         }     
    }
  2. 在O(1)时间删除链表结点
    题目:给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间删除该节点。
    分析:O(1)时间内把下一个节点的内存复制覆盖掉要删除的节点,并删除下一个节点。对于尾结点,仍需要顺序查找,复杂度O(n)。
    以下代码前提假设为要删除的结点确实在链表中。

    void DeleteNode(ListNode** pHead,ListNode* pDelete)
    {
    	if(!pHead || !pDelete)
    		return;
    	//要删除的节点不是尾节点
    	if(pDelete->pNext!=NULL)
    	{
    		ListNode* Next = pDelete->pNext;
    		pDelete->data = Next->data;
    		pDelete->pNext = Next->pNext;
    		delete Next;
    		Next = NULL;
    	}
    	//链表只有一个节点
    	else if(*pHead == pDelete)
    	{
    		delete pDelete;
    		pDelete = NULL;
    		*pHead = NULL;
    	}
    	//链表中有多个节点,删除尾节点,需要顺序查找
    	else
    	{
    		ListNode *Node = *pHead;
    		while(Node->pNext!=pDelete)
    			Node = Node->pNext;
    
    		Node->pNext = NULL;
    		delete pDelete;
    		pDelete = NULL;
    	}
    }
  3. 链表中的倒数第k个结点
    题目:输入一个链表,输出该链表中倒数第k个结点。假设链表的尾节点为倒数第一个结点。
    分析:两个指针差k-1步,但需要注意特殊情况:Head为空指针,结点数少于k个,参数k=0情况。
    延伸:求链表中间结点。定义两个指针,一个指针一次一步,一个指针一次两步,走得快的到尾部时,走的慢的正好在中间。同样,判断单向链表是否形成了环形,也是这样,最后如果走得快的指针追上了走得慢的指针,那么链表就是环形链表,如果走得快的走到了链表尾部都没有追上慢的,那么就不是环形链表。

    ListNode* FindKthToTail(ListNode* pHead,int k)
    {
    	if(pHead == NULL || k < 0)
    		return NULL;
    	ListNode* pA = pHead;//先移动k-1步
    	ListNode* pB = pHead;
    	for(int i = 0;i < k-1;++i)
    	{
    		if(pA == NULL)
    			return NULL;
    		else 
    			pA = pA->pNext;
    	}
    
    	while(pA->pNext!=NULL)
    	{
    		pA = pA->pNext;
    		pB = pB->pNext;
    	}
    	return pB;
    }
  4. 反转链表
    题目:定义一个函数,输入一个链表的头指针,反转该链表并输出反转后链表的头结点。
    分析:为防止链表断开,需定义三个指针,分别指向当前遍历到的结点、它的前一个结点及后一个结点。

    ListNode* ReverseList(ListNode* pHead)
    {
    	ListNode* ReversedHead = NULL;
    	ListNode* Node = pHead;
    	ListNode* Prev = NULL; //头结点反转后指向NULL
    	while(Node != NULL)
    	{
    		ListNode* Next = Node->pNext; //指向后一个结点
    		if(Next == NULL)
    			ReversedHead = Node;
    		Node->pNext = Prev;
    		Prev = Node;
    		Node = Next;
    	}
    	return ReversedHead;
    }
  5. 合并两个排序的链表
    题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。

    ListNode* Merge(ListNode* pHead1,ListNode* pHead2)
    {
    	if(pHead1 == NULL) return pHead2;
    	if(pHead2 == NULL) return pHead1;
    
    	ListNode* pMerge = NULL;
    	if(pHead1->data < pHead2->data)
    	{
    		pMerge = pHead1;
    		pMerge->pNext=Merge(pHead1->pNext,pHead2);
    	}
    	else
    	{
    		pMerge = pHead2;
    		pMerge->pNext=Merge(pHead1,pHead2->pNext);
    	}
    	return pMerge;
    }
  6. 两个链表的第一个公共结点(还有树的公共结点问题)
    题目:输入两个链表,找出它们的第一个公共结点。
    分析:如果两个链表有公共结点,那么公共结点出现在两个链表的尾部。从两个尾部开始向前比较,最后一个相同的借点就是我们要找的结点。分别将两个链表的结点放入两个栈,每次比较栈顶结点,相同就弹出比较下一个。(需要复制空间)
    或者
    我们先遍历一遍得到两个链表的长度,然后让长的链表先向前走几步,之后再同时遍历。
  7. 环形链表-圆圈中最后剩下的数字
    题目:0,1,2,…n-1这n个数排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字,求出这个圆圈中最后剩下的那个数字(约瑟夫问题)。例如,{0,1,2,3,4}从0开始删除第3个数字,那么删除的前四个数依次是2,0,4,1,最后剩下3.
    解法一:环形链表模拟
    用std::list模仿环形链表,扫描到链表尾部时记得吧迭代器移到头部。

    int LastRemain(unsigned int n,unsigned int m)
    {
    	if(n<1||m<1)
    		return -1;
    	list<int> numbers;
    	for(int i=0;i<n;++i)
    		numbers.push_back(i);
    
    	list<int>::iterator cur = numbers.begin();
    	while(numbers.size()>1)
    	{
    		for(int i=1;i<m;++i)
    		{
    			cur++;
    			if(cur == numbers.end())
    				cur = numbers.begin();
    		}
    		list<int>::iterator next = ++cur;
    		if(next == numbers.end())
    			next = numbers.begin();
    		--cur;
    		numbers.erase(cur);
    		cur=next;
    	}
    	return *(cur);
    }

    解法二:分析每次被删除的数字的规律直接计算出最后剩下的数字

    1) if we shift the ids by k, namely, start from k instead of 0, we should add the result by k%n
    2) after the first round, we start from k+1 ( possibly % n) with n-1 elements, that is equal to an (n-1) problem
    while start from (k+1)th element instead of 0, so the answer is (f(n-1, m)+k+1)%n
    3) k = m-1, so f(n,m)=(f(n-1,m)+m)%n.
    finally, f(1, m) = 0;Now this is a O(n) solution.

    int LastRemain(unsigned int n,unsigned int m)
    {
    	if(n<1||m<1)
    		return -1;
    	int last=0;
    	for(int i=2;i<=n;++i)
    		last = (last+m)%i;
    	return last;
    }
  8. 二叉搜索树与双向链表
    题目:输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建新的结点,只能调整树中节点指针的指向。

    分析:中序遍历,当遍历到根节点时,连接左子链表的最右结点和右子链表的最左结点。再递归调整左右子链表。

    struct BinaryTreeNode
    {
    	int nValue;
    	BinaryTreeNode* pLeft;
    	BinaryTreeNode* pRight;
    };
    
    void helper(BinaryTreeNode *&head,BinaryTreeNode *&tail,BinaryTreeNode *root)
    {
    	BinaryTreeNode *lt,*rh; //left tail,right head
    	if(root == NULL)
    	{
    		head = NULL;
    		tail = NULL;
    		return;
    	}
    	helper(head,lt,root->pLeft);
    	helper(rh,tail,root->pRight);
    	if(lt != NULL)
    	{
    		lt->pRight = root;
    		root->pLeft = lt;
    	}
    	else
    		head = root;
    	if(rh != NULL)
    	{
    		root->pRight = rh;
    		rh->pLeft = root;
    	}
    	else
    		tail = root;
    }
    
    BinaryTreeNode* treeToLinkedList(BinaryTreeNode *root)
    {
    	BinaryTreeNode *head,*tail;//生成双向链表的头指针和尾指针
    	helper(head,tail,root);
    	return head;
    }
  9. 复杂链表的复制
    题目:实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。复杂链表中,每个结点除了有一个指针指向下一个结点,还有一个指针指向链表中任意结点或者NULL。
    分析:

    struct ComplexNode
    {
    	int m_nValue;
    	ComplexNode* m_pNext;
    	ComplexNode* m_pSibling;
    };
    ComplexNode* Clone(ComplexNode* pHead) 
    {
    	if (pHead == NULL) 
    		return NULL;
    	preClone(pHead);
    	inClone(pHead);
    	return postClone(pHead);
    }
    void preClone(ComplexNode* pHead) 
    {
    	//复制原始链表的任意结点N并创建新结点N’,链接到N的后面
    	ComplexNode* pNode = pHead;
    	while(pNode != NULL)
    	{
    		ComplexNode* pClone = new ComplexNode();
    		pClone->m_nValue = pNode->m_nValue;
    		pClone->m_pNext = pNode->m_pNext;
    		pClone->m_pSibling = NULL;
    
    		pNode->m_pNext = pClone;
    		pNode = pClone->m_pNext;
    	}
    }
    
    void inClone(ComplexNode* pHead) 
    {
    	//原始链表上的节点N的m_pSibling指向S,则N'指向S的下一个节点S'
    	ComplexNode * pNode = pHead;
    	while(pNode != NULL)
    	{
    		ComplexNode* pClone = pNode->m_pNext;
    		if(pNode->m_pSibling != NULL)
    			pClone->m_pSibling = pNode->m_pSibling->m_pNext;
    		pNode = pClone->m_pNext;
    	}
    }
    
    ComplexNode * postClone(ComplexNode* pHead) 
    {
    	//拆分链表,奇偶
    	ComplexNode* pNode = pHead;
    	ComplexNode* pCloneHead = NULL;
    	ComplexNode* pCloneNode = NULL;
    
    	if(pNode != NULL)
    	{
    		pCloneHead = pCloneNode = pNode->m_pNext;
    		pNode->m_pNext = pCloneNode->m_pNext;
    		pNode = pNode->m_pNext;
    	}
    	while(pNode != NULL)
    	{
    		pCloneNode->m_pNext = pNode->m_pNext;
    		pCloneNode = pCloneNode->m_pNext;
    		pNode->m_pNext = pCloneNode->m_pNext;
    		pNode = pNode->m_pNext;
    	}
    	return pCloneHead;
    }
  10. 从一个未排序的单链表中删除重复元素
    题目:Write code to remove duplicates from an unsorted linked list.
    解法一:如果对空间复杂度没有要求,则使用哈希表,如果存在即删除,如果不存在将值写入hashtable。
    解法二:使用两个pointer进行遍历–current进行正常遍历,当runner每次从头开始查找(到current为止)是否有重复元素,如果重复,删除current。

    void deleteDups(ListNode* pHead)
    {
    	if(pHead == NULL) return;
    	ListNode* pre = pHead;
    	ListNode* current = pre->pNext;
    	while(current != NULL)
    	{
    		ListNode* runner = pHead;
    		while(runner != current)
    		{
    			if(runner->data == current->data)
    			{
    				//remove current
    				ListNode* tmp = current->pNext;
    				pre->pNext = tmp;
    				current = tmp;
    				break;//跳出的while循环
    			}
    			runner = runner->pNext;
    		}
    		if(runner == current)
    		{
    			//runner在0-current间遍历
    			//无重复
    			pre = current;
    			current = current->pNext;
    		}
    	}
    }
  11. You have two numbers represented by a linked list,where each node contains a single digit.The digits are stored in reverse order,such that the 1’s digit is at the head of the list.Write a function that adds the two numbers and returns the sum as a linked list.e.g. Input:(3->1->5),(5->9->2),Output:8->0->8
    分析:每一位的值相当于(链表1的值+链表2的值+前一位的进位)%10,如果该位所得的值超过10,带进位。

    ListNode* addLists(ListNode* p1,ListNode* p2,int flag)
    {
    	if(p1==NULL && p2==NULL) return NULL;
    	ListNode* result;
    	int value = flag;
    	if(p1!=NULL)
    		value += p1->data;
    	if(p2!=NULL)
    		value += p2->data;
    	result->data = value%10;
    	ListNode* more = addLists(p1 == NULL?NULL:p1->pNext,
    			p2==NULL?NULL:p2->pNext,
    			value>10?1:0);
    	result->pNext = more;
    	return result;
    }
  12. Given a circular linked list,implement an algorithm which returns node at the beginning of the loop.e.g. Input:A->B->C->D->E->C,output:C.
    分析:两个指针,一个每次移动1步,一个每次移动2步,如果最后能相遇,则表明链表有环。
    1)表头距离环开始处k个结点;2)p1p2相遇处距离环开始处也为k个结点;3)寻找两个点的中点即可。

    ListNode* FindBeginning(ListNode* pHead)
    {
    	ListNode* p1 = pHead;
    	ListNode* p2 = pHead;
    
    	while(p2->pNext!=NULL)
    	{
    		p1=p1->pNext;
    		p2=p2->pNext->pNext;
    		if(p1==p2)
    			break;
    	}
    	if(p2->pNext==NULL) 
    		return NULL;//no loop
    	p1 = pHead;
    	while(p1!=p2)
    	{
    		p1=p1->pNext;
    		p2=p2->pNext;
    	}
    	return p2;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *