【Sword finger offer】 —— Summary of linked list related exercises 2

1. The penultimate k-th node in the linked list

1. The problem is to
input a linked list and output the penultimate k-th node in the linked list. In order to meet the habits of most people, this question starts counting from 1, that is, the tail node of the linked list is the penultimate node. For example: a linked list has 6 nodes, starting from the head node, their values ​​are 1, 2, 3, 4, 5, 6. The penultimate node of this list is the node with value 4. Linked list nodes are defined as follows:

struct ListNode
{
	int m_nKey;
	ListNode* m_pNext;
};

2. Problem analysis
First of all, once we get this question, we will naturally think of going to the end of the linked list first, and then going back to the k step from the end. But it seems incorrect to think about this approach carefully, because ours is a singly linked list, there is no pointer from back to front, so this method will not work.

Later, we discovered such a rule: the last k-th node is the n-k + 1 node from the beginning. We can first traverse sequentially to calculate the value of n, and then walk n-k + 1 nodes from the beginning node. In other words, it is necessary to traverse the linked list twice, but this is not efficient.

Then, we came up with a method that only needs to be traversed once. We can define two pointers. The first pointer p1 starts at k-1 (3-1) from the head node, and the second pointer remains stationary. Still using the example in the title to analyze the instantiation as follows:
Insert picture description here
from step k (3), p2 also starts traversing from the head pointer of the linked list. Since the distance between the two pointers is kept at k-1, when p1 goes to the tail node of the linked list, the p2 pointer just points to the penultimate k (3) node
Insert picture description here

With the above solution, but there are still many small details in this method, we think about it separately

  1. If pListHead is a null pointer, code trying to access the memory pointed to by the null pointer will cause a memory crash. In this case, the penultimate k-th node naturally returns nullptr
  2. If the total number of input linked lists with pListNode as the head node is less than k. Since the for loop will go forward k-1 steps on the linked list, it will still crash due to a null pointer. So add a condition to the for loop
  3. If the input parameter is 0, because k is an unsigned integer, then k-1 in the for loop will not get -1, but 0xffffffff, causing the program to crash. This input is meaningless and can return nullptr.

So the code is implemented as follows:

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
{
	if (pListHead == nullptr || k == 0)//细节1、3
		return nullptr;

	ListNode* p1 = pListHead;
	ListNode* p2 = nullptr;

	for (unsigned int i = 0; i < k-1; i++)
	{
		if (p1->m_pNext != nullptr)//细节2
			p1 = p1->m_pNext;
		else
			return nullptr;
	}
	p2 = pListHead;
	while (p1->m_pNext != nullptr)
	{
		p1 = p1->m_pNext;
		p2 = p2->m_pNext;
	}
	return p2;
}

Second, the replication of complex linked lists

1. The problem is to
enter a complex linked list (each node has a node value, and two pointers, one to the next node, and another special pointer to any node), and the returned result is the head of the complex linked list after the copy. (Note that in the output result, please do not return the node reference in the parameter, otherwise the judgment program will directly return empty). The definition of his node is as follows:

struct ComplexListNode
{
	int m_nValue;
	ComplexListNode* m_pNext;
	ComplexListNode* m_pSibling;
};

The following figure is a specific diagram of a complex linked list:
Insert picture description here
2. Problem analysis
When we see this question, we will find that it is slightly different from the general list structure we are familiar with, and you may feel that you ca n’t start. But we should all know the military thought of "each break", that is, when we encounter complex big problems, if we can first break down the big problems into several simple small problems, and then solve these small problems one by one, it is very It's easy.

Method 1:
Most of us may think of dividing the copying process into two steps after some thought. The first step is to copy the nodes on the original linked list and link them with m_pNext. The second step is to set the m_pSibling pointer for each node. When setting the m_pSibling pointer of each node, you need to find the m_pSibling of each node through O (n) steps from the head node. Therefore, the time complexity of this method requires O (n ^ 2). Inefficient, consider the next method.

Method 2:
Because the time of method 1 is mainly spent on positioning node m_pSibling. The first step of our second method is to copy the nodes on the original linked list like the method. The second step optimizes the positioning node m_pSibling to put the pairing information of <N, N1> into a hash table. Since the m_pSibling of the node N in the original linked list node points to the node S, then in the duplicate linked list, the corresponding N1 should also point to S1. With a hash table, we can use O (1) time to find S1 according to S. But this method is a typical space-for-time strategy.

Method 3:
This method is to achieve O (n) time efficiency without auxiliary space. The first step is to create a corresponding N1 based on each node N of the original linked list. This time, we link N1 to the back of N, as shown in the following figure.
Insert picture description here
The code to complete this step is as follows:

void CloneNodes(ComplexListNode* pHead)
{
	ComplexListNode* pNode = pHead;
	while (pNode != nullptr)
	{
		ComplexListNode* pCloned = new ComplexListNode();
		pCloned->m_nValue = pNode->m_nValue;
		pCloned->m_pNext = pNode->m_pNext;
		pCloned->m_pSibling = nullptr;

		pNode->m_pNext = pCloned;
		pNode = pCloned->m_pNext;
	}
}

Next is to set the m_pSibling of the copied node. Keep the points of the copied nodes consistent. As shown below: The
Insert picture description here
completion code is as follows:

void ConnectSibilingNodes(ComplexListNode* pHead)
{
	ComplexListNode* pNode = pHead;
	while (pNode != nullptr)
	{
		ComplexListNode* pCloned = pNode->m_pNext;
		if (pNode->m_pSibling != nullptr)
		{
			pCloned->m_pSibling = pNode->m_pSibling->m_pNext;
		}

		pNode = pCloned->m_pNext;
	}
}

The last step is to split the long list into two lists. The nodes in odd positions are the original linked lists, and the nodes in even positions are connected to make a linked list. The code is implemented as follows:

ComplexListNode* ReconnectNodes(ComplexListNode* pHead)
{
	ComplexListNode* pNode = pHead;
	ComplexListNode* pCloneHead = nullptr;
	ComplexListNode* pCloneNode = nullptr;

	if (pNode != nullptr)
	{
		pCloneHead = pCloneNode = pNode->m_pNext;
		pNode->m_pNext = pCloneNode->m_pNext;
		pNode = pNode->m_pNext;
	}

	while (pNode != nullptr)
	{
		pCloneNode->m_pNext = pNode->m_pNext;
		pCloneNode = pCloneNode->m_pNext;
		pNode->m_pNext = pCloneNode->m_pNext;
		pNode = pNode->m_pNext;
	}
	return pCloneHead;
}

Finally, combining the above three steps is the complete process of replication.

ComplexListNode* Clone(ComplexListNode* pHead)
{
	CloneNodes(pHead);
	ConnectSibilingNodes(pHead);
	return ReconnectNodes(pHead);
}

3. The first common node of the two linked lists

1. Topic requirements
Enter two linked lists to find their first common node. The definition of a linked list is as follows:

struct ListNode
{
	int m_key;
	ListNode* m_pnext;
};

2. Problem analysis
Method 1: Once
you get this question, if you don't think much, the first solution you might think of is to use the brute force method to solve it. First traverse the nodes of the first linked list, each traversal of a node in the second linked list sequentially traverse a node. If the same node as the first one appears in the second linked list, it means that a common node has been found. However, the efficiency of this method is not high, and the time complexity reaches O (n + m).

Method 2:
Because the linked list designed for this problem is a single linked list structure. That is to say, once the two linked lists intersect, they must be a linked list, which is a Y-shaped rather than X-shaped. The effect is as follows:
Insert picture description here
carefully observe the above structure, we will find that if we start from the end of the two linked lists and compare forward, then the last node that is the same is the node we are looking for. But because the structure of the singly linked list can only be traversed from front to back, but we require comparison from back to front, then we have introduced our commonly used stack. Put the nodes of the two linked lists on the stack, so that the tail nodes of the two nodes are the top elements of the stack. The next job is to compare the top elements of the stack one by one. Until the last identical node is found. But the space complexity of this idea is O (n + m), and the time complexity is O (n + m). Although the efficiency has improved, this is a strategy of swapping space for time.

Method three: The
reason why our last method uses the stack is because we want to reach the tail node of the linked list at the same time. When the length of the two linked lists is not the same, if we traverse from the beginning, then the time to reach the tail node is inconsistent. To make the time to reach the tail node consistent, we can only first traverse the two linked lists in sequence to calculate the length of each linked list, and the difference in their lengths is the number of nodes that the longer linked list goes first in the second traversal. Then we set off to traverse two nodes at the same time and compare them. Finding the first identical node is the result we want.

//得到链表长度
unsigned int GetListLength(ListNode* pHead)
{
	ListNode* pNode = pHead;
	unsigned int count = 0;
	while (pNode != nullptr)
	{
		count++;
		pNode = pNode->m_pnext;
	}
	return count;
}

ListNode* FindFirstCommonNode(ListNode* pHead1,ListNode* pHead2)
{
	unsigned int length1 = GetListLength(pHead1);
	unsigned int length2 = GetListLength(pHead2);
	int lengthDif = length1 - length2;

	ListNode* pListHeadlong = pHead1;
	ListNode* pListHeadshort = pHead2;
	if (length1 < length2)
	{
		ListNode* pListHeadlong = pHead2;
		ListNode* pListHeadshort = pHead1;
		lengthDif = length2 - length1;
	}

	for (int i = 0; i < lengthDif; i++)
	{
		pListHeadlong = pListHeadlong->m_pnext;
	}

	while ((pListHeadlong != nullptr) && (pListHeadshort != nullptr) && (pListHeadlong != pListHeadshort))
	{
		pListHeadlong = pListHeadlong->m_pnext;
		pListHeadshort = pListHeadshort->m_pnext;
	}

	ListNode* pFirstCommonNode = pListHeadlong;
	return pFirstCommonNode;
}

4. The last number in the circle

1. The question requires
that the n numbers 0, 1, ..., n-1 are arranged in a circle, starting with the number 0, and deleting the mth number from this circle each time. Find the last number left in this circle. For example, the 5 digits 0,1,2,3,4 form a circle as shown in the figure below. The third digit is deleted every time from 0, then the first 4 digits deleted are 2,0,4,1 . The last remaining number is 3.
Insert picture description here
2.
Problem analysis This is the famous Joseph Ring problem.
Method one:
use the circular list to simulate the classic solution of the circle. We can create a circular list with a total of n nodes, and then delete the m-th node in this list each time. We can use the std :: list in the template library to simulate a circular list, but because the list itself is not a circular list, so whenever the iterator scans to the end of the list, we must remember to move the iterator to the head of the list . In this way, the time complexity is O (nm), and the space complexity is O (n).

int LastRemaining(unsigned int n, unsigned int m)
{
	if (n < 1 || m < 1)
		return -1;

	unsigned int i = 0;
	list<int> numbers;
	for(i=0;i<n;i++)
	numbers.push_back(i);

	list<int>::iterator current = numbers.begin();

	while (numbers.size() > 1)
	{
		for (int i = 1; i < m; i++)
		{
			current++;
			if (current == numbers.end())
				current = numbers.begin();
		}

		list<int>::iterator next = ++current;
		if (next == numbers.end())
			next = numbers.begin();

		--current;
		numbers.erase(current);
		current = next;
	}

	return *(current);
}

Method 2:
After complex mathematical analysis, a recursive relational expression is obtained as follows. The time complexity of this algorithm is O (n), and the space complexity is O (1).
Insert picture description here
The code is written according to the formula as follows:

int LastRemaining(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;
}
Published 98 original articles · won praise 9 · views 3639

Guess you like

Origin blog.csdn.net/qq_43412060/article/details/105528455