【Oferta de dedo de espada】 —— Resumen de la lista vinculada ejercicios relacionados 2

1. El penúltimo nodo k-ésimo en la lista vinculada

1. El problema es
ingresar una lista vinculada y generar el penúltimo nodo k-ésimo en la lista vinculada. Para cumplir con los hábitos de la mayoría de las personas, esta pregunta comienza contando desde 1, es decir, el nodo de cola de la lista vinculada es el penúltimo nodo. Por ejemplo: una lista vinculada tiene 6 nodos, comenzando desde el nodo principal, sus valores son 1, 2, 3, 4, 5, 6. El penúltimo nodo de esta lista es el nodo con valor 4. Los nodos de la lista vinculada se definen de la siguiente manera:

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

2. Análisis del problema En
primer lugar, una vez que recibamos esta pregunta, naturalmente pensaremos en ir primero al final de la lista vinculada y luego volver al paso k desde el final. Pero parece incorrecto pensar en este enfoque con cuidado, porque somos una lista enlazada individualmente, no hay un puntero de atrás hacia adelante, por lo que este método no funcionará.

Más tarde, descubrimos dicha regla: el último nodo k-ésimo es el nodo n-k + 1 desde el principio. Primero podemos atravesar secuencialmente para calcular el valor de n, y luego caminar n-k + 1 nodos desde el nodo inicial. En otras palabras, es necesario recorrer la lista vinculada dos veces, pero esto no es eficiente.

Luego, se nos ocurrió un método que solo debe atravesarse una vez. Podemos definir dos punteros: el primer puntero p1 comienza en k-1 (3-1) desde el nodo principal y el segundo puntero permanece estacionario. Todavía usando el ejemplo en el título para analizar la creación de instancias de la siguiente manera:
Inserte la descripción de la imagen aquí
desde el paso k (3), p2 también comienza a moverse desde el puntero principal de la lista vinculada. Dado que la distancia entre los dos punteros se mantiene en k-1, cuando p1 va al nodo de cola de la lista vinculada, el puntero p2 solo apunta al penúltimo nodo k (3)
Inserte la descripción de la imagen aquí

Con la solución anterior, pero todavía hay muchos pequeños detalles en este método, lo pensamos por separado.

  1. Si pListHead es un puntero nulo, el código que intenta acceder a la memoria señalada por el puntero nulo provocará un bloqueo de memoria. En este caso, el penúltimo nodo k-ésimo naturalmente devuelve nullptr
  2. Si el número total de listas vinculadas de entrada con pListNode como nodo principal es menor que k. Como el bucle for avanzará k-1 pasos en la lista vinculada, seguirá bloqueándose debido a un puntero nulo. Entonces agregue una condición al bucle for
  3. Si el parámetro de entrada es 0, porque k es un entero sin signo, entonces k-1 en el bucle for no obtendrá -1, sino 0xffffffff, lo que provocará que el programa se bloquee. Esta entrada no tiene sentido y puede devolver nullptr.

Entonces el código se implementa de la siguiente manera:

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

Segundo, la replicación de listas enlazadas complejas.

1. El problema es
ingresar una lista enlazada compleja (cada nodo tiene un valor de nodo y dos punteros, uno al siguiente nodo y otro puntero especial a cualquier nodo), y el resultado devuelto es el encabezado de la lista enlazada compleja después de la copia. (Tenga en cuenta que en el resultado de salida, no devuelva la referencia de nodo en el parámetro; de lo contrario, el programa de evaluación volverá directamente vacío). La definición de su nodo es la siguiente:

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

La siguiente figura es un diagrama específico de una lista enlazada compleja:
Inserte la descripción de la imagen aquí
2. Análisis del problema
Cuando veamos esta pregunta, descubriremos que es ligeramente diferente de la estructura general de la lista con la que estamos familiarizados, y puede sentir que no puede comenzar. Pero todos deberíamos conocer el pensamiento militar de "cada descanso", es decir, cuando nos encontramos con grandes problemas complejos, si primero podemos dividir los grandes problemas en varios problemas pequeños y simples, y luego resolver estos pequeños problemas uno por uno, Es facil.

Método 1:
La mayoría de nosotros puede pensar en dividir el proceso de copia en dos pasos después de pensarlo un poco. El primer paso es copiar los nodos en la lista vinculada original y vincularlos con m_pNext. El segundo paso es establecer el puntero m_pSibling para cada nodo. Al configurar el puntero m_pSibling de cada nodo, debe encontrar el m_pSibling de cada nodo a través de O (n) pasos desde el nodo principal. Por lo tanto, la complejidad temporal de este método requiere O (n ^ 2). Ineficiente, considere el siguiente método.

Método 2:
porque el tiempo del método 1 se dedica principalmente al posicionamiento del nodo m_pSibling. El primer paso de nuestro segundo método es copiar los nodos en la lista vinculada original como el método. El segundo paso optimiza el nodo de posicionamiento m_pSibling para colocar la información de emparejamiento de <N, N1> en una tabla hash. Dado que m_pSibling del nodo N en el nodo de la lista vinculada original apunta al nodo S, en la lista vinculada duplicada, el N1 correspondiente también debe apuntar a S1. Con una tabla hash, podemos usar el tiempo O (1) para encontrar S1 de acuerdo con S. Pero este método es una estrategia típica de espacio por tiempo.

Método 3:
Este método es lograr una eficiencia de tiempo O (n) sin espacio auxiliar. El primer paso es crear un N1 correspondiente basado en cada nodo N de la lista vinculada original. Esta vez, vinculamos N1 al reverso de N, como se muestra en la siguiente figura.
Inserte la descripción de la imagen aquí
El código para completar este paso es el siguiente:

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

Lo siguiente es establecer el m_pSibling del nodo copiado. Mantenga los puntos de los nodos copiados consistentes. Como se muestra a continuación: El
Inserte la descripción de la imagen aquí
código de finalización es el siguiente:

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

El último paso es dividir la larga lista en dos listas. Los nodos en posiciones impares son las listas enlazadas originales, y los nodos en posiciones pares están conectados para hacer una lista enlazada. El código se implementa de la siguiente manera:

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

Finalmente, combinar los tres pasos anteriores es el proceso completo de replicación.

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

3. El primer nodo común de las dos listas vinculadas

1. Requisitos del tema
Ingrese dos listas vinculadas para encontrar su primer nodo común. La definición de una lista vinculada es la siguiente:

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

2. Análisis del problema
Método 1: una vez
que reciba esta pregunta, si no piensa mucho, la primera solución que puede pensar es utilizar el método de fuerza bruta para resolverlo. Primero atraviesa los nodos de la primera lista vinculada, cada recorrido de un nodo en la segunda lista vinculada atraviesa secuencialmente un nodo. Si el mismo nodo que el primero aparece en la segunda lista vinculada, significa que se ha encontrado un nodo común. Sin embargo, la eficiencia de este método no es alta, y la complejidad del tiempo alcanza O (n + m).

Método 2:
porque la lista vinculada diseñada para este problema es una estructura de lista vinculada única. Es decir, una vez que las dos listas enlazadas se cruzan, deben ser una lista enlazada, que tiene forma de Y en lugar de X. El efecto es el siguiente:
Inserte la descripción de la imagen aquí
observe cuidadosamente la estructura anterior, descubriremos que si comenzamos desde el final de las dos listas vinculadas y comparamos hacia adelante, entonces el último nodo que es el mismo es el nodo que estamos buscando. Pero debido a que la estructura de la lista enlazada solo se puede recorrer de adelante hacia atrás, pero requerimos una comparación de atrás hacia adelante, entonces hemos introducido nuestra pila de uso común. Coloque los nodos de las dos listas vinculadas en la pila, de modo que los nodos de cola de los dos nodos sean los elementos superiores de la pila. El siguiente trabajo es comparar los elementos superiores de la pila uno por uno. Hasta que se encuentre el último nodo idéntico. Pero la complejidad espacial de esta idea es O (n + m), y la complejidad temporal es O (n + m). Aunque la eficiencia ha mejorado, esta es una estrategia de intercambio de espacio por tiempo.

Método tres: La
razón por la cual nuestro último método usa la pila es porque queremos llegar al nodo final de la lista vinculada al mismo tiempo. Cuando la longitud de las dos listas enlazadas no es la misma, si atravesamos desde el principio, entonces el tiempo para llegar al nodo de cola es inconsistente. Para que el tiempo para llegar al nodo de cola sea consistente, solo podemos atravesar primero las dos listas enlazadas en secuencia para calcular la longitud de cada lista enlazada, y la diferencia en sus longitudes es el número de nodos que la lista enlazada más larga va primero en la segunda transversal. Luego partimos para atravesar dos nodos al mismo tiempo y compararlos. Encontrar el primer nodo idéntico es el resultado que queremos.

//得到链表长度
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. El último número en el círculo.

1. La pregunta requiere
que los n números 0, 1, ..., n-1 estén dispuestos en un círculo, comenzando con el número 0, y borrando el número enésimo de este círculo cada vez. Encuentra el último número que queda en este círculo. Por ejemplo, los 5 dígitos 0,1,2,3,4 forman un círculo como se muestra en la figura siguiente: el tercer dígito se elimina cada vez de 0, luego los primeros 4 dígitos eliminados son 2,0,4,1 . El último número restante es 3.
Inserte la descripción de la imagen aquí
2. Análisis del
problema Este es el famoso problema de Joseph Ring.
Método uno:
use la lista circular para simular la solución clásica del círculo. Podemos crear una lista circular con un total de n nodos, y luego eliminar el m-ésimo nodo en esta lista cada vez. Podemos usar std :: list en la biblioteca de plantillas para simular una lista circular, pero debido a que la lista en sí no es una lista circular, por lo tanto, cada vez que el iterador escanea hasta el final de la lista, debemos recordar mover el iterador al comienzo de la lista. . De esta manera, la complejidad del tiempo es O (nm), y la complejidad del espacio es 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);
}

Método 2:
Después de un análisis matemático complejo, se obtiene una expresión relacional recursiva de la siguiente manera: la complejidad temporal de este algoritmo es O (n) y la complejidad espacial es O (1).
Inserte la descripción de la imagen aquí
El código se escribe según la fórmula de la siguiente manera:

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;
}
Publicado 98 artículos originales · ganado elogios 9 · vistas 3639

Supongo que te gusta

Origin blog.csdn.net/qq_43412060/article/details/105528455
Recomendado
Clasificación