1. 程式人生 > >【演算法精練】輕鬆get單鏈表面試題

【演算法精練】輕鬆get單鏈表面試題

刪除無頭單鏈表的非尾節點(不能遍歷單鏈表)

分為兩種情況:①刪除的位置pos為Null,直接返回 ②刪除的位置不為空時,實現如下圖所示: 在這裡插入圖片描述

void DeleteNotTailNode(PNode pos)
{
	if(NULL == pos)
	{
		return;
	}
	PNode pDel = pos->next;
	pos->data = pDel->data;
	pos->next = pDel->next;
	free(pDel);
}

在無頭單鏈表非頭節點前插入值為data的節點

分為兩種情況:①插入的位置pos為Null,直接返回 ②插入的位置不為空時,實現如下圖所示:在這裡插入圖片描述

void InsertNotHeadNode(PNode pos,DataType _data)
{
	if(NULL == pos)
		return;
	PNode NewNode = BuyNode(pos->data);
	if(NewNode)
	{
		NewNode->next = pos->next;
		pos->next = NewNode;
		pos->data = _data;
	}
}

用單鏈表實現約瑟夫環

分為兩種情況:①連結串列為空時,直接返回Null ②連結串列不為空時,實現如下圖所示:

在這裡插入圖片描述

PNode JosephCircle
(PNode pHead,size_t M) { if(NULL =pHead) return NULL; PNode pCur = pHead; while(pCur != pCur->next) { size_t k = M; while(--k) { pCur = pCur->next; } PNode pDel = pCur->next; pCur->data = pDel->data; pCur->next = pDel->next; free(pDel); } return pCur; }

逆置單鏈表(利用三個指標進行逆置)

分為兩種情況:①連結串列為空或者連結串列中僅有一個節點,直接返回pHead ②連結串列中有多個節點時,實現如下圖所示:在這裡插入圖片描述

PNode ReverseList(PNode pHead)
{
	if(NULL == pHead || NULL == pHead->next)
		return pHead;
	PNode pPre = pHead;
	PNode pCur = pPre->next;
	PNode pSul = pCur->next;
	while(pSul)
	{
		pCur->next = pPre;
		pPre = pCur;
		pCur = pSul;
		if(pSur)
			pSul= pSul->next;
	}
	pCur->next = pPre;
	pHead->next = NULL;
	return pCur;
}

逆置單鏈表(頭插法)

分為兩種情況:①連結串列為空或者連結串列中僅有一個節點,直接返回pHead ②連結串列中有多個節點時,實現如下圖所示: 在這裡插入圖片描述

PNode ReverseList_P(PNode pHead)
{
	if(NULL == pHead || NULL == pHead->next)
		return pHead;
	PNode pCur = pHead->next;
	PNode NewNode = NULL;
	while(pCur)
	{
		pHead->next = NewNode;
		NewNode = pHead;
		pHead = pCur;
		pCur = pCur->next;
	}
	pHead->next = NewNode;
	NewNode = pHead;
	return NewNode;
}

對單鏈表進行氣泡排序(升序)

分為兩種情況:①連結串列為空或者連結串列中僅有一個節點,直接返回 ②連結串列中有多個節點時,實現如下圖所示: 在這裡插入圖片描述

void BubbleSort(PNode pHead)
{
	if(NULL == pHead || NULL == pHead->next)
		return;
	int flag = 0;
	PNode pCur = pHead;
	PNode pTail = NULL;
	while(pTail != pHead)
	{
		pCur = pHead;
		while(pCur->next != pTail)
		{
			if(pCur->data > pCur->next->data)
			{
				DataType temp = pCur->data;
				pCur->data = pCur->next->data;
				pCur->next->data = temp;
				flag = 1;
			}
			pCur = pCur->next;
			if(!flag)
				return;
		}
		pTail = pCur;
	}
}

查詢單鏈表的中間節點(只能遍歷一次連結串列)

①偶數節點,如下圖所示:在這裡插入圖片描述 ②奇數節點,如下圖所示:在這裡插入圖片描述

PNode FindMidNode(PNode pHead)
{
	if(NULL == pHead || NULL == pHead->next)
		return pHead;
	PNode pSlow = pHead;
	PNode pFast = pHead;
	while(pFast && pFast->next)
	{
		pSlow = pSlow->next;
		pFast = pFast->next->next;
	}
	return pSlow;
}

查詢單鏈表的倒數第K個節點(只能遍歷一次連結串列)

分為兩種情況:①連結串列為空或K=0,直接返回Null ②連結串列有一個或多個結點,並且K>0時,如下圖所示: 在這裡插入圖片描述

PNode FindLastKNode(PNode pHead, size_t K)
{
	if(NULL ==pHead || K == 0)
		return NULL;
	PNode pSlow = pHead;
	PNode pFast = pHead;
	while(--K)
	{
		if(NULL == pFast)
			return NULL;
		pFast = pFast->next;
	}
	while(pFast->next)
	{
		pSlow = pSlow->next;
		pFast = pFast->next;
	}
	return pSlow;
}

刪除單鏈表倒數第K個節點

刪除單鏈表可以參照上面的查詢程式及圖例,道理同上。

PNode DeleteLastKNode(PNode pHead, size_t K)
{
	if(NULL == pHead || K == 0)
		return NULL;
	PNode pos = FindLastKNode(pHead,K);
	Erase(&pHead,pos);
}

合併兩個有序單鏈表,合併之後依然有序

分為三種情況:①pHead1為空時,返回pHead2 ②pHead2為空時,返回pHead1 ③當pHead1與pHead2都不為空時,實現如下圖所示: 在這裡插入圖片描述

//法一:
PNode MergeList(PNode pHead1,PNode pHead2)
{
	if(NULL == pHead1)
		return pHead2;
	if(NULL == pHead2)
		return pHead1;
	PNode NewNode = NULL;
	PNode pTail = NULL;
	if(pHead1->data < pHead2->data)
	{
		pTail =pHead1;
		pHead1 = pHead1->next;
	}
	else
	{
		pTail = pHead2;
		pHead2 = pHead2->next;
	}
	NewNode = pTail;
	while(pHead1 && pHead2)
	{
		if(pHead1->data < pHead2->data)
		{
			pTail->next = pHead1;
			pTail = pHead1;
			pHead1 = pHead1->next;
		}
		else
		{
			pTail->next = pHead2;
			pTail = pHead2;
			pHead2 = pHead2->next;
		}
	}
	if(pHead1)
		pTail->next = pHead1;
	else
		pTail->next = pHead2;
	return NewNode;

}
//法二:
PNode MergeList(PNode pHead1,PNode pHead2)
{
	if(NULL == pHead1)
		return pHead2;
	if(NULL == pHead2)
		return pHead1;
	PNode NewNode = (PNode)malloc(sizeof(Node));
	NewNode->data = -1;
	PNode pTail = NewNode;
	
	while(pHead1 && pHead2)
	{
		if(pHead1->data < pHead2->data)
		{
			pTail->next = pHead1;
			pHead1 = pHead1->next;
		}
		else
		{
			pTail->next = pHead2;
			pHead2 = pHead2->next;
		}
		pTail = pTail->next;
	}
	if(pHead1)
		pTail->next = pHead1;
	else
		pTail->next = pHead2;
	return NewNode->next;

}
//遞迴
PNode MergeList(PNode pHead1,PNode pHead2)
{
	if(NULL == pHead1)
		return pHead2;
	if(NULL == pHead2)
		return pHead1;
	PNode NewNode = (PNode)malloc(sizeof(Node));
	NewNode->data = -1;
	PNode pTail = NewNode;

	if(pHead1->data < pHead2->data)
	{
			pTail = pHead1;
			pTail->next = MergeList(pHead1->next,pHead2);
	}
	else
	{
			pTail = pHead2;
			pTail->next = MergeList(pHead1,pHead2->next);
	}
	
	return NewNode->next;

}

求單鏈表中結點的個數

這是最最基本的了,應該能夠迅速寫出正確的程式碼,注意檢查連結串列是否為空。時間複雜度為O(n)。參考程式碼如下:

	// 求單鏈表中結點的個數
    unsigned int GetListLength(ListNode * pHead)
    {
    	if(pHead == NULL)
    		return 0;
     
    	unsigned int nLength = 0;
    	ListNode * pCurrent = pHead;
    	while(pCurrent != NULL)
    	{
    		nLength++;
    		pCurrent = pCurrent->m_pNext;
    	}
    	return nLength;
    }

判斷一個單鏈表中是否有環

這裡也是用到兩個指標。如果一個連結串列中有環,也就是說用一個指標去遍歷,是永遠走不到頭的。因此,我們可以用兩個指標去遍歷,一個指標一次走兩步,一個指標一次走一步,如果有環,兩個指標肯定會在環中相遇。時間複雜度為O(n)。參考程式碼如下:

bool HasCircle(ListNode * pHead)
    {
    	ListNode * pFast = pHead; // 快指標每次前進兩步
    	ListNode * pSlow = pHead; // 慢指標每次前進一步
    	while(pFast != NULL && pFast->m_pNext != NULL)
    	{
    		pFast = pFast->m_pNext->m_pNext;
    		pSlow = pSlow->m_pNext;
    		if(pSlow == pFast) // 相遇,存在環
    			return true;
    	}
    	return false;
    }
在這裡插入程式碼片

判斷兩個單鏈表是否相交

如果兩個連結串列相交於某一節點,那麼在這個相交節點之後的所有節點都是兩個連結串列所共有的。也就是說,如果兩個連結串列相交,那麼最後一個節點肯定是共有的。先遍歷第一個連結串列,記住最後一個節點,然後遍歷第二個連結串列,到最後一個節點時和第一個連結串列的最後一個節點做比較,如果相同,則相交,否則不相交。時間複雜度為O(len1+len2),因為只需要一個額外指標儲存最後一個節點地址,空間複雜度為O(1)。參考程式碼如下:

 bool IsIntersected(ListNode * pHead1, ListNode * pHead2)
    {
            if(pHead1 == NULL || pHead2 == NULL)
                    return false;
     
    	ListNode * pTail1 = pHead1;
    	while(pTail1->m_pNext != NULL)
    		pTail1 = pTail1->m_pNext;
     
    	ListNode * pTail2 = pHead2;
    	while(pTail2->m_pNext != NULL)
    		pTail2 = pTail2->m_pNext;
    	return pTail1 == pTail2;
    }

求兩個單鏈表相交的第一個節點

對第一個連結串列遍歷,計算長度len1,同時儲存最後一個節點的地址。 對第二個連結串列遍歷,計算長度len2,同時檢查最後一個節點是否和第一個連結串列的最後一個節點相同,若不相同,不相交,結束。 兩個連結串列均從頭節點開始,假設len1大於len2,那麼將第一個連結串列先遍歷len1-len2個節點,此時兩個連結串列當前節點到第一個相交節點的距離就相等了,然後一起向後遍歷,知道兩個節點的地址相同。 時間複雜度,O(len1+len2)。參考程式碼如下:

 ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2)
    {
    	if(pHead1 == NULL || pHead2 == NULL)
    		return NULL;
     
    	int len1 = 1;
    	ListNode * pTail1 = pHead1;
    	while(pTail1->m_pNext != NULL)
    	{
    		pTail1 = pTail1->m_pNext;
    		len1++;
    	}
     
    	int len2 = 1;
    	ListNode * pTail2 = pHead2;
    	while(pTail2->m_pNext != NULL)
    	{
    		pTail2 = pTail2->m_pNext;
    		len2++;
    	}
     
    	if(pTail1 != pTail2) // 不相交直接返回NULL
    		return NULL;
     
    	ListNode * pNode1 = pHead1;
    	ListNode * pNode2 = pHead2;
            // 先對齊兩個連結串列的當前結點,使之到尾節點的距離相等
    	if(len1 > len2)
    	{
    		int k = len1 - len2;
    		while(k--)
    			pNode1 = pNode1->m_pNext;
    	}
    	else
    	{
    		int k = len2 - len1;
    		while(k--)
    			pNode2 = pNode2->m_pNext;
    	}
    	while(pNode1 != pNode2)
    	{
    		pNode1 = pNode1->m_pNext;
    		pNode2 = pNode2->m_pNext;
    	}
            return pNode1;
    }

已知一個單鏈表中存在環,求進入環中的第一個節點

首先判斷是否存在環,若不存在結束。在環中的一個節點處斷開(當然函式結束時不能破壞原連結串列),這樣就形成了兩個相交的單鏈表,求進入環中的第一個節點也就轉換成了求兩個單鏈表相交的第一個節點。參考程式碼如下:

 ListNode* GetFirstNodeInCircle(ListNode * pHead)
    {
    	if(pHead == NULL || pHead->m_pNext == NULL)
    		return NULL;
     
    	ListNode * pFast = pHead;
    	ListNode * pSlow = pHead;
    	while(pFast != NULL && pFast->m_pNext != NULL)
    	{
    		pSlow = pSlow->m_pNext;
    		pFast = pFast->m_pNext->m_pNext;
    		if(pSlow == pFast)
    			break;
    	}
    	if(pFast == NULL || pFast->m_pNext == NULL)
    		return NULL;
     
    	// 將環中的此節點作為假設的尾節點,將它變成兩個單鏈表相交問題
    	ListNode * pAssumedTail = pSlow; 
    	ListNode * pHead1 = pHead;
    	ListNode * pHead2 = pAssumedTail->m_pNext;
     
    	ListNode * pNode1, * pNode2;
    	int len1 = 1;
    	ListNode * pNode1 = pHead1;
    	while(pNode1 != pAssumedTail)
    	{
    		pNode1 = pNode1->m_pNext;
    		len1++;
    	}
    	
    	int len2 = 1;
    	ListNode * pNode2 = pHead2;
    	while(pNode2 != pAssumedTail)
    	{
    		pNode2 = pNode2->m_pNext;
    		len2++;
    	}
     
    	pNode1 = pHead1;
    	pNode2 = pHead2;
    	//法一:用公式法計算
    	//法二:先對齊兩個連結串列的當前結點,使之到尾節點的距離相等
    	if(len1 > len2)
    	{
    		int k = len1 - len2;
    		while(k--)
    			pNode1 = pNode1->m_pNext;
    	}
    	else
    	{
    		int k = len2 - len1;
    		while(k--)
    			pNode2 = pNode2->m_pNext;
    	}
    	while(pNode1 != pNode2)
    	{
    		pNode1 = pNode1->m_pNext;
    		pNode2 = pNode2->m_pNext;
    	}
        return pNode1;
    }

給出一單鏈表頭指標pHead和一節點指標pToBeDeleted,O(1)時間複雜度刪除節點pToBeDeleted

對於刪除節點,我們普通的思路就是讓該節點的前一個節點指向該節點的下一個節點,這種情況需要遍歷找到該節點的前一個節點,時間複雜度為O(n)。對於連結串列,連結串列中的每個節點結構都是一樣的,所以我們可以把該節點的下一個節點的資料複製到該節點,然後刪除下一個節點即可。要注意最後一個節點的情況,這個時候只能用常見的方法來操作,先找到前一個節點,但總體的平均時間複雜度還是O(1)。參考程式碼如下:

 void Delete(ListNode * pHead, ListNode * pToBeDeleted)
    {
    	if(pToBeDeleted == NULL)
    		return;
    	if(pToBeDeleted->m_pNext != NULL)
    	{
    		pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey; // 將下一個節點的資料複製到本節點,然後刪除下一個節點
    		ListNode * temp = pToBeDeleted->m_pNext;
    		pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;
    		delete temp;
    	}
    	else // 要刪除的是最後一個節點
    	{
    		if(pHead == pToBeDeleted) // 連結串列中只有一個節點的情況
    		{
    			pHead = NULL;
    			delete pToBeDeleted;
    		}
    		else
    		{
    			ListNode * pNode = pHead;
    			while(pNode->m_pNext != pToBeDeleted) // 找到倒數第二個節點
    				pNode = pNode->m_pNext;
    			pNode->m_pNext = NULL;
    			delete pToBeDeleted;
    		}	
    	}
    }