代碼面試之鏈表

  最近經歷了各類面試,各類東北西跑,各類面試準備,以及各類各樣不同的問題和麪試官對本身本科大學的打擊,,我相信即便是普通大學也會讓考官眼前一亮的,,做爲一名非211的湘潭大學的學子,我不知道本身之後路在何方,但依然會繼續前行,相信本身會作到最好!html

  剛從武漢面試回來,打算總結一下面試經驗,身爲一名通訊工程專業的與技術行離不開的技術男,確定離不開各類電路設計,程序設計,總之在面試過程當中,考官也會問道關於數據結構這種最多見的問題,話很少說,先就總結一下吧!前端

  鏈表是最基本的數據結構,鏈表相關的操做相對而言比較簡單,在面試過程時間有限的過程中也適合考察寫代碼的能力。鏈表的操做離不開指針,而指針又很容易致使出錯。綜合多方面的緣由,鏈表題目在面試中佔據着很重要的地位,甚至能夠說必不可少,在我看來也是學習指針的最好辦法,是理解C語言最好的學習方式。node

  下面我先來列出本身敲的代碼,有關於靜態鏈表的建立,都有本身詳細的解釋,你們先看看了解一下:面試

typedef struct _tag_StaticListNode  //結點結構體定義 
{
    unsigned int data;      //
    int next;    			//保存鏈表裏的數據元素 
} TStaticListNode;

typedef struct _tag_StaticList	//靜態鏈表 結構體定義 
{
    int capacity;	//靜態鏈表最多容納的元素 
    TStaticListNode header;  //鏈表頭 
    TStaticListNode node[]; //柔性數組 
} TStaticList;

StaticList* StaticList_Create(int capacity) // O(n)-- 1.建立靜態鏈表 
{
    TStaticList* ret = NULL;    //定義返回值變量ret 
    int i = 0;
    
    if( capacity >= 0 )	//指定的容量大爲0,要合法 
    {
        ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
   			//malloc動態的申明空間來申明動態鏈表以及裏面的數組, (capacity + 1)由於有一個被設爲表頭 
    }
    
    if( ret != NULL )
    {
        ret->capacity = capacity;//賦初值 
        ret->header.data = 0;	//	
        ret->header.next = 0;   //開始一個元素都沒有故爲0 
        
        for(i=1; i<=capacity; i++)
        {
            ret->node[i].next = AVAILABLE;//全部 位置標誌爲可用 
        }
    }
    
    return ret;
}

void StaticList_Destroy(StaticList* list) // O(1)  直接釋放free 
{
    free(list);
}

void StaticList_Clear(StaticList* list) // O(n)   清除 
{
    TStaticList* sList = (TStaticList*)list;   //強制類型轉換 
    int i = 0;
    
    if( sList != NULL )   //判斷合法 
    {
        sList->header.data = 0;
        sList->header.next = 0;    //先清空 ,由於全部元素的下標都是大於0 的 
        
        for(i=1; i<=sList->capacity; i++)
        {
            sList->node[i].next = AVAILABLE;  //全部的數組都是可使用的 
        }
    }
}

int StaticList_Length(StaticList* list) // O(1)
{
    TStaticList* sList = (TStaticList*)list;
    int ret = -1;	//表示傳進來的數據不合法 
    
    if( sList != NULL )   //判斷是否合法,再賦值 
    {
        ret = sList->header.data;  //把獲得的值賦給ret 
    }
    
    return ret;
}

int StaticList_Capacity(StaticList* list) // O(1)返回最大容量 
{
    TStaticList* sList = (TStaticList*)list;
    int ret = -1;
    
    if( sList != NULL )
    {
        ret = sList->capacity;
    }
    
    return ret;
}

int StaticList_Insert(StaticList* list, StaticListNode* node, int pos)  // O(n)
//進行插入一個元素的操做 
{
    TStaticList* sList = (TStaticList*)list; //強制轉換作一個數據封裝 
    int ret = (sList != NULL); //1表明插入成功,0表明插入失敗 
    int current = 0;
    int index = 0;
    int i = 0;
    
    ret = ret && (sList->header.data + 1 <= sList->capacity);//插入位置是否合法的 
    ret = ret && (pos >=0) && (node != NULL); //插入位置補位空且大於0 
    
    if( ret )  // ret = 1時 
    {
        for(i=1; i<=sList->capacity; i++)   //開始尋找哪一個位置可用 
        {
            if( sList->node[i].next == AVAILABLE )  //判斷是否可用 
            {
                index = i;   //記下插入位置 
                break;
            }
        }
        
        sList->node[index].data = (unsigned int)node;//新元素放到這個位置來 
        
        sList->node[0] = sList->header; //表頭 
        
        for(i=0; (i<pos) && (sList->node[current].next != 0); i++)//移動pos次,(此位置下標不爲0) 
        {
            current = sList->node[current].next; //移到下一個元素的下標 
        }
        
        sList->node[index].next = sList->node[current].next;//1. 新元素與下一個元素鏈接 
        sList->node[current].next = index;//2.新元素與插入位置前一個位置鏈接 
        
        sList->node[0].data++; //元素長度加1了 
        
        sList->header = sList->node[0];  //更新表頭信息 
    }
    
    return ret;
}

StaticListNode* StaticList_Get(StaticList* list, int pos)  // O(n)
{
    TStaticList* sList = (TStaticList*)list;
    StaticListNode* ret = NULL;  //定義返回值變量 
    int current = 0; //表頭的下標爲0 
    int object = 0; //要獲取的元素下標 
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
    //sLIST指針不爲空 , 獲取元素位置pos>0  而且小於它的長度值 
	{
        sList->node[0] = sList->header; //第0個元素就是頭結點 
        
        for(i=0; i<pos; i++)
        {
            current = sList->node[current].next; 
        }
        
        object = sList->node[current].next;//當前元素的next域即爲要獲取元素在數組中的下標 
        
        ret = (StaticListNode*)(sList->node[object].data);//獲取的值 
    }
    
    return ret;
}

StaticListNode* StaticList_Delete(StaticList* list, int pos) // O(n)刪除第pos元素 
{
    TStaticList* sList = (TStaticList*)list;
    StaticListNode* ret = NULL;
    int current = 0;
    int object = 0;
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
    {
        sList->node[0] = sList->header;
        
        for(i=0; i<pos; i++)
        {
            current = sList->node[current].next;
        }
        
        object = sList->node[current].next;
        
        sList->node[current].next = sList->node[object].next; // 與增長一個元素同樣 
        
        sList->node[0].data--;  //元素刪除,減1 
        
        sList->header = sList->node[0];  //更新表頭信息 
        
        sList->node[object].next = AVAILABLE;  //可用 
        
        ret = (StaticListNode*)(sList->node[object].data);//數據返回 
    }
    
    return ret;
}

  而對於循環鏈表來講,其實和靜態鏈表是一個道理,只是在基於單向鏈表的基礎上有一些改動,這裏我只是把改動的列出來,固然其他的可能仍是有一些小的差別,可是這個並不明顯,下面是改動的部分代碼,筆者也作了相關解釋:數組

int CircleList_Insert(CircleList* list, CircleListNode* node, int pos) // O(n)
//循環鏈表中要是插入第一個元素的話就得另加說明了 
{ 
    TCircleList* sList = (TCircleList*)list;
    int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
    int i = 0;
    
    if( ret )
    {
        CircleListNode* current = (CircleListNode*)sList;
        
        for(i=0; (i<pos) && (current->next != NULL); i++)
        {
            current = current->next;
        }
        
        node->next = current->next;
        current->next = node;
        
        if( sList->length == 0 )   //插入的是第一個元素 
        {
            sList->slider = node;
            node->next = node;   //插入元素得指向本身才能構成循環 
        }
        
        sList->length++;
    }
    
    return ret;
}

CircleListNode* CircleList_Get(CircleList* list, int pos) // O(n)獲取元素 
{
    TCircleList* sList = (TCircleList*)list;
    CircleListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (pos >= 0) )//不須要(pos<sList->length)這個約束 
    {
        CircleListNode* current = (CircleListNode*)sList;
        
        for(i=0; i<pos; i++)
        {
            current = current->next;
        }
        
        ret = current->next;
    }
    
    return ret;
}

CircleListNode* CircleList_Delete(CircleList* list, int pos) // O(n)
{
    TCircleList* sList = (TCircleList*)list;
    CircleListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (pos >= 0) )
    {
        CircleListNode* current = (CircleListNode*)sList;
        CircleListNode* first = sList->header.next; //first指向鏈表的 第一個元素 
        CircleListNode* last = (CircleListNode*)CircleList_Get(sList, sList->length - 1);
        
        for(i=0; i<pos; i++)  //開始移動 
        {
            current = current->next;
        }
        
        ret = current->next;    
        current->next = ret->next;
        
        sList->length--;
        
        if( first == ret )  //若是刪除的是第一個元素 
        {
            sList->header.next = ret->next;  //將表頭指向ret 
            last->next = ret->next;			//最後一個指針移動到第二個位置上 
        }
        
        if( sList->slider == ret )//遊標 
        {
            sList->slider = ret->next;
        }
        
        if( sList->length == 0 )  //鏈表是否爲空 
        {
            sList->header.next = NULL;
            sList->slider = NULL;
        }
    }
    
    return ret;
}

  至於雙向鏈表,就複雜一些了,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點,這樣代碼難度就加大了,其實也是一個意思,可是在短短的面試時間中,我以爲仍是不多讓你寫出來的,通常理解了單鏈表就能夠應付了,下面我詳細的介紹在面試過程中會問道的有關鏈表的知識。數據結構

1 求單鏈表的節點數,注意檢查鏈表是否爲空。時間複雜度爲O(n)ide

// 求單鏈表中結點的個數
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;
}

2 將單鏈表翻轉,從頭至尾遍歷原鏈表,每遍歷一個結點,將其摘下放在新鏈表的最前端。注意鏈表爲空和只有一個結點的狀況。時間複雜度爲O(n)函數

// 反轉單鏈表
ListNode * ReverseList(ListNode * pHead)
{
        // 若是鏈表爲空或只有一個結點,無需反轉,直接返回原鏈表頭指針
	if(pHead == NULL || pHead->m_pNext == NULL)  
		return pHead;

	ListNode * pReversedHead = NULL; // 反轉後的新鏈表頭指針,初始爲NULL
	ListNode * pCurrent = pHead;
	while(pCurrent != NULL)
	{
		ListNode * pTemp = pCurrent;
		pCurrent = pCurrent->m_pNext;
		pTemp->m_pNext = pReversedHead; // 將當前結點摘下,插入新鏈表的最前端
		pReversedHead = pTemp;
	}
	return pReversedHead;
}

3 查找單鏈表中的第n個點(n>0),使用兩個指針,先讓前面的指針走到正向第k個結點,這樣先後兩個指針的距離差是k-1,以後先後兩個指針一塊兒向前走,前面的指針走到最後一個結點時,後面指針所指結點就是倒數第k個結點。學習

// 查找單鏈表中倒數第K個結點
ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函數名前面的R表明反向
{
	if(k == 0 || pHead == NULL) // 這裏k的計數是從1開始的,若k爲0或鏈表爲空返回NULL
		return NULL;

	ListNode * pAhead = pHead;
	ListNode * pBehind = pHead;
	while(k > 1 && pAhead != NULL) // 前面的指針先走到正向第k個結點
	{
		pAhead = pAhead->m_pNext;
		k--;
	}
	if(k > 1 || pAhead == NULL)     // 結點個數小於k,返回NULL
		return NULL;
	while(pAhead->m_pNext != NULL)  // 先後兩個指針一塊兒向前走,直到前面的指針指向最後一個結點
	{
		pBehind = pBehind->m_pNext;
		pAhead = pAhead->m_pNext;
	}
	return pBehind;  // 後面的指針所指結點就是倒數第k個結點
}

4 查找單鏈表中的單節點,設置兩個指針,兩個指針同時向前走,前面的指針每次走兩步,後面的指針每次走一步,前面的指針走到最後一個結點時,後面的指針所指結點就是中間結點,即第(n/2+1)個結點。注意鏈表爲空,鏈表結點個數爲1和2的狀況。時間複雜度O(n)ui

// 獲取單鏈表中間結點,若鏈表長度爲n(n>0),則返回第n/2+1個結點
ListNode * GetMiddleNode(ListNode * pHead)
{
	if(pHead == NULL || pHead->m_pNext == NULL) // 鏈表爲空或只有一個結點,返回頭指針
		return pHead;

	ListNode * pAhead = pHead;
	ListNode * pBehind = pHead;
	while(pAhead->m_pNext != NULL) // 前面指針每次走兩步,直到指向最後一個結點,後面指針每次走一步
	{
		pAhead = pAhead->m_pNext;
		pBehind = pBehind->m_pNext;
		if(pAhead->m_pNext != NULL)
			pAhead = pAhead->m_pNext;
	}
	return pBehind; // 後面的指針所指結點即爲中間結點
}

5 從尾部到頭部打印單鏈表,就是倒序打印單鏈表!注意鏈表爲空的狀況,下面使用的是棧。時間複雜度爲O(n)

// 從尾到頭打印鏈表,使用棧
void RPrintList(ListNode * pHead)
{
	std::stack<ListNode *> s;
	ListNode * pNode = pHead;
	while(pNode != NULL)
	{
		s.push(pNode);
		pNode = pNode->m_pNext;
	}
	while(!s.empty())
	{
		pNode = s.top();
		printf("%d\t", pNode->m_nKey);
		s.pop();
	}
}
// 從尾到頭打印鏈表,使用遞歸
void RPrintList(ListNode * pHead)
{
	if(pHead == NULL)
	{
		return;
	}
	else
	{
		RPrintList(pHead->m_pNext);
		printf("%d\t", pHead->m_nKey);
	}
}

6 已知兩個單鏈表pHead1 和pHead2 各自有序,把它們合併成一個鏈表依然有序,須要O(1)的空間。時間複雜度爲O(max(len1, len2))

// 合併兩個有序鏈表
ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
{
	if(pHead1 == NULL)
		return pHead2;
	if(pHead2 == NULL)
		return pHead1;
	ListNode * pHeadMerged = NULL;
	if(pHead1->m_nKey < pHead2->m_nKey)
	{
		pHeadMerged = pHead1;
		pHeadMerged->m_pNext = NULL;
		pHead1 = pHead1->m_pNext;
	}
	else
	{
		pHeadMerged = pHead2;
		pHeadMerged->m_pNext = NULL;
		pHead2 = pHead2->m_pNext;
	}
	ListNode * pTemp = pHeadMerged;
	while(pHead1 != NULL && pHead2 != NULL)
	{
		if(pHead1->m_nKey < pHead2->m_nKey)
		{
			pTemp->m_pNext = pHead1;
			pHead1 = pHead1->m_pNext;
			pTemp = pTemp->m_pNext;
			pTemp->m_pNext = NULL;
		}
		else
		{
			pTemp->m_pNext = pHead2;
			pHead2 = pHead2->m_pNext;
			pTemp = pTemp->m_pNext;
			pTemp->m_pNext = NULL;
		}
	}
	if(pHead1 != NULL)
		pTemp->m_pNext = pHead1;
	else if(pHead2 != NULL)
		pTemp->m_pNext = pHead2;
	return pHeadMerged;
}

7 判斷一個單鏈表是否有環!若是一個鏈表中有環,也就是說用一個指針去遍歷,是永遠走不到頭的。所以,咱們能夠用兩個指針去遍歷,一個指針一次走兩步,一個指針一次走一步,若是有環,兩個指針確定會在環中相遇。時間複雜度爲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;
}

8 判斷兩個單鏈表是否相交:若是兩個鏈表相交於某一節點,那麼在這個相交節點以後的全部節點都是兩個鏈表所共有的。也就是說,若是兩個鏈表相交,那麼最後一個節點確定是共有的。先遍歷第一個鏈表,記住最後一個節點,而後遍歷第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點作比較,若是相同,則相交,不然不相交。時間複雜度爲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;
}

9 求兩個單鏈表相交的第一個節點:兩個鏈表均從頭節點開始,假設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;
}

10 已知一個單鏈表中存在環,求進入環中的第一個節點:首先判斷是否存在環,若不存在結束。在環中的一個節點處斷開(固然函數結束時不能破壞原鏈表),這樣就造成了兩個相交的單鏈表,求進入環中的第一個節點也就轉換成了求兩個單鏈表相交的第一個節點。

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

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

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

  

  版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4809784.html

相關文章
相關標籤/搜索