1. 程式人生 > >連結串列面試題

連結串列面試題

關於連結串列已經學了有一段時間了,今天抽空進行了整理,列出來常見的有關連結串列的面試題,以下想法如有瑕疵望批評指出,希望能給初學者帶來一點參考和價值

從尾到頭列印單鏈表

遞迴列印

// 1、從尾到頭列印單鏈表 遞迴
void ListReversePrint(ListNode *Node)
{
	if (Node == NULL)
	{
		return;
	}

	ListReversePrint(Node->next);
	printf("%d-->",Node->data);
}

非遞迴列印

void ListReveresePrint2(ListNode *Node)
{
	ListNode *end = NULL;
	while (end != Node)
	{
		ListNode *cur = Node;
		while (cur->next != end)
		{
			cur = cur->next;
		}
		printf("%d-->",cur->data);
		end = cur;
	}
}

刪除一個無頭單鏈表的非尾結點(不能遍歷連結串列)

核心程式碼

//2. 刪除一個無頭單鏈表的非尾結點(不能遍歷連結串列)
void ListDelNotFirst(ListNode **Node ,ListNode *pos)
{
	ListNode *del;
	if (Node == NULL)
	{
		return;
	}
	//	pos 位置為第一個結點時,修改指標
	if (pos == *Node)
	{
		*Node = pos->next;
		free(pos);
	}
	// pos 位置不是第一個結點時也不是最後一個結點時
	pos->data = pos->next->data;   //先修改data的值
	del = pos->next;     //將pos->next作為要刪除的結點
	pos->next = del->next;
	free(del);
}

測試

void TestListDelNotFirst()
{
	ListNode *list = NULL;

	ListPushBack(&list,1);
	ListPushBack(&list,2);
	ListPushBack(&list,3);	
	ListPushBack(&list,4);
	ListPushBack(&list,5);	
	ListPushBack(&list,6);

	ListPrint(list);

	ListDelNotFirst(list,list);

	ListPrint(list);
}

在無頭單鏈表的一個結點前插入一個結點(不能遍歷連結串列)

//3.在無頭單鏈表的一個結點前插入一個結點(不能遍歷連結串列)

void ListInsertBefore(ListNode **Node ,ListNode *pos ,DataType data)
{
	ListNode *newNode;
	if (*Node == NULL && pos == NULL)
	{
		return;
	}
	//插入的結點是在第一個結點上
	if (pos == *Node)
	{
		ListNode *newNode = CreateNode(data);
		newNode->next = *Node;
		*Node = newNode;
		return;
	}
	//插入的結點不是第一個結點時
	newNode = CreateNode(pos->data);
	newNode->next = pos->next;  //後插
	pos->next = newNode;
	pos->data = data;
}

逆置/反轉單鏈表

思路一:

從第二個結點開始,將其刪除,人挪活插在第一個結點處,直到連結串列結束

void listReverse(ListNode **Node)
{
	if (Node == NULL)
	{
		return ;
	}
	if (*Node == NULL)
	{
		return;
	}
	//只有一個結點
	if ((*Node)->next == NULL)
	{
		return ;
	}
	//至少兩個結點
	ListNode *cur = *Node;
	while (cur->next != NULL)
	{
		ListNode *rm = cur->next;
		cur->next = rm->next;
		rm->next = *Node;
		*Node = rm;
	}
}

思路二:

改變每個結點的指標指向從而逆置

void listReverseRePoint(ListNode **Node)
{
	if (Node == NULL)
	{
		return;
	}
	if (*Node == NULL)
	{
		return;
	}
	if ((*Node)->next == NULL)
	{
		return;
	}

	ListNode *pre = *Node;
	ListNode *cur = (*Node)->next;
	pre->next = NULL;
	
	while (cur != NULL)
	{
		ListNode *temp = cur->next;
		cur->next = pre;
		pre =cur;
		cur = temp;
	}

	*Node = pre;
}

找出兩個連結串列裡相同資料

// 5.找出兩個連結串列裡相同資料
void ListIntersection(ListNode *list1,ListNode *list2)
{
	ListNode *head1 = list1;
	ListNode *head2 = list2;
	
	DataType data;

	if (head1 ==NULL && head2 == NULL)
	{
		return ;
	}

	while (head1 != NULL && head2 != NULL)
	{
		if (head1->data < head2->data)
		{
			head1 = head1->next;
		}
		 if (head1->data > head2->data)
		{
			head2 = head2->next;
		}
		
		if (head1->data == head2->data)
		{
			printf("%d ",head1->data);
			data = head1->data;

			while (head2 != NULL && data == head2->data  )
			{
				head2 = head2->next;
			}
			while (head1 != NULL && data == head1->data )
			{
				head1 = head1->next;
			}
		}
	}
}

測試

void TestListInterstion()
{
	ListNode *list1 = NULL;
	ListNode *list2 = NULL;

	ListPushBack(&list1,1);
	ListPushBack(&list1,1);
	ListPushBack(&list1,3);	
	ListPushBack(&list1,4);

	ListPushBack(&list2,2);
	ListPushBack(&list2,2);
	ListPushBack(&list2,4);	
	ListPushBack(&list2,4);

	ListIntersection(list1,list2);
}

單鏈表實現約瑟夫環

核心程式碼

//6.單鏈表實現約瑟夫環
ListNode* ListJosephCircle(ListNode *list,DataType k)
{
	//第一步,變成環
	ListNode *tail = list;
	ListNode *cur = list;
	ListNode *pre = NULL;
	int i =0;
#if 0
	if (list == NULL)
	{
		return NULL;
	}
#endif
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = list;

	 
	//第二步,找到第k個數
	while ( cur->next != cur)
	{	
#if 0
        //1 2 3 4 5 6 7 8 
		while (--k)
		{
		
			pre = cur;  //記錄剔除結點的前一個結點
			cur = cur->next;
		}
#endif
		for (i=1;i<k;i++)
		{
			pre = cur;  //記錄剔除結點的前一個結點
			cur = cur->next;
		}
		
		pre->next =cur->next;
		printf("淘汰:%d\n",cur->data);
		free(cur);

		cur = pre->next;
	}	
	cur->next = NULL;
	return cur;
}

測試

void TestListJosephCircle()
{
	ListNode *list1 = NULL;
	ListNode *re;
	ListPushBack(&list1,1);
	ListPushBack(&list1,2);
	ListPushBack(&list1,3);	
	ListPushBack(&list1,4);
	ListPushBack(&list1,5);
	ListPushBack(&list1,6);
	
	ListPrint(list1);
	printf("\n");
	re = ListJosephCircle(list1,3);
	printf("%d\n",re->data);
}

合併兩個有序的連結串列,合併後依然有序

具體程式碼

//7.合併兩個有序的連結串列,合併後依然有序
ListNode *ListMerge(ListNode *list1,ListNode *list2)
{
	ListNode *cur1 = list1;
	ListNode *cur2 = list2;
	ListNode *result = NULL;	//結果連結串列
	ListNode *tail = NULL;		 //結果連結串列中的最後一個結點,方便尾插
	ListNode *next = NULL;		//儲存遍歷過程中的下一個結點
	ListNode *node;

	while (cur1 != NULL && cur2 != NULL)
	{
		if (cur1->data <= cur2->data)
		{
			node = cur1;
		}
		else
		{
			node =cur2;
		}

		next = node->next;
		if (result != NULL)
		{
			tail->next = node;
		}
		else
		{
			result = node;
		}
		node->next = NULL;

		tail = node;
	
		
		if (node == cur1)
		{
			cur1 = next;
		}
		else
		{
			cur2 = next;
		}
	}
	//一個連結串列空了
	if (cur1 == NULL)
	{
		tail->next =cur2;
	}
	if (cur2 == NULL)
	{
		tail->next =cur1;
	}

	return result;
}

測試

void TestMerge()
{
	ListNode *list1 = NULL;
	ListNode *list2 = NULL;

	ListNode *result = NULL;

	ListPushBack(&list1,1);
	ListPushBack(&list1,2);
	ListPushBack(&list1,3);	
	ListPushBack(&list1,4);

	ListPushBack(&list2,1);
	ListPushBack(&list2,2);
	ListPushBack(&list2,3);	
	ListPushBack(&list2,4);

	result = ListMerge(list1,list2);
	ListPrint(result);
}

查詢單鏈表的中間結點,要求只能遍歷一次連結串列

思路:定義快慢指標,當快指標走完時,慢指標剛好是在中間位置,好比是兩人賽跑,跑的快點人的速度是慢的人的二倍

具體程式碼

//8.查詢單鏈表的中間結點,要求只能遍歷一次連結串列
void ListFindMidNode(ListNode *list)
{	
	ListNode *fast = list;
	ListNode *slow = list;

	while (fast != NULL && fast->next != NULL)
	{
		fast = fast->next;
		if (fast == NULL)
		{
			break;
		}
		fast = fast->next;
		if (fast == NULL)
		{
			break;
		}
		slow = slow->next;
	}

	printf("%d\n",slow->data);
}

測試

void TestFindMid()
{
	ListNode *list = NULL;

	ListPushBack(&list,1);
	ListPushBack(&list,2);
	ListPushBack(&list,3);	
	ListPushBack(&list,4);
	ListPushBack(&list,5);	
	ListPushBack(&list,6);

	ListFindMidNode(list);
}

查詢單鏈表中的倒數第K個結點,要求只能遍歷一次連結串列

思路:定義快慢指標,快指標先走k步,滿指標再開始走,快指標走完的時候就是慢指標走到倒數第k個結點處

具體程式碼

//9.查詢單鏈表中的倒數第K個結點,要求只能遍歷一次連結串列

void ListFindTailK(ListNode *list,DataType k)
{
	ListNode *fast = list;
	ListNode *slow = list;

	while (k--)
	{
		fast = fast->next;
	}
	while (fast != NULL)
	{
		fast = fast->next;
		slow = slow->next;
	}

	printf("%d\n",slow->data);
}

測試

void TestFindTailK()
{
	ListNode *list = NULL;

	ListPushBack(&list,1);
	ListPushBack(&list,2);
	ListPushBack(&list,3);	
	ListPushBack(&list,4);
	ListPushBack(&list,5);	
	ListPushBack(&list,6);
	
	ListFindTailK(list,3);

}

刪除單鏈表中的倒數第K個結點

思路:同查詢倒數第k個結點一樣,我們定義快慢指標進行非同步,還需要定義一個pre來記錄要刪除結點的先驅,刪除後需要指向被刪除的下一個

具體程式碼

//10.刪除單鏈表中的倒數第K個結點

void ListDelTailK(ListNode *list, DataType k)
{
	ListNode *fast = list;
	ListNode *slow = list;
	ListNode *pre = NULL;
	
	while (k--)
	{
		fast = fast->next;
	}

	while (fast != NULL)
	{
		pre = slow;
		fast = fast->next;
		slow = slow->next;
	}
	pre->next = slow->next;

	free(slow);

}

測試

void TestDelTailK()
{
	ListNode *list = NULL;

	ListPushBack(&list,1);
	ListPushBack(&list,2);
	ListPushBack(&list,3);	
	ListPushBack(&list,4);
	ListPushBack(&list,5);	
	ListPushBack(&list,6);
	
	ListPrint(list);
	printf("\n刪除倒數第%d個結點\n",3);
	ListDelTailK(list,3);

	 ListDelNotFirst(list,list);


	ListPrint(list);
}