Aunque la lista de enlaces es buena, es fácil de romper y enredar como una cuerda...
La matriz también es buena, pero la eficiencia del movimiento es baja...
141. Lista enlazada circular
Determinar si hay un ciclo en una lista enlazada
Puntero rápido y lento: el tamaño de paso del puntero rápido es 1 más que el puntero lento (Vs-Vf=1)
S+Vs-(F+Vf) = N-1
Vs-Vf = 1
Sea S el tamaño de paso del puntero lento F el tamaño de paso del puntero rápido
Diferencia: SF=N
S+1-(F+2)=N-1
…
Después de N veces: SF=0
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
876. Nodo intermedio de lista enlazada
1. Travesía violenta O(N) O(N)
Recorriendo una lista enlazada con una matriz
final obtener longitud len
Luego obtenga directamente el punto medio de la lista enlazada l [len//2]
// vector::back() 返回vector容器的最后一个元素...
// vector::push_back(Q) 向vector容器里加入一个元素Q
// vector::pop_back() 删除vector最后一个元素
class Solution {
public:
ListNode* middleNode(ListNode* head) {
vector<ListNode*> A = {
head};
while(A.back()->next!=NULL)
{
A.push_back(A.back()->next);
}
return A[A.size()/2];
}
};
2. Puntero único O(N) O(1)
El primer recorrido obtiene la longitud de la lista enlazada N
El segundo recorrido a la posición de N//2 es el punto medio de la lista enlazada
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int len = 0;
ListNode* t = head,*tt = head;
while(t!=NULL)
{
t = t->next;
len+=1;
}
for(int i=0;i<len/2;i++)
tt = tt->next;
return tt;
}
};
3. Punteros rápidos y lentos (otra vez eres tú...) O(N) O(1)
Defina los punteros rápido y lento en la posición del nodo principal
El puntero rápido es un paso más rápido que el puntero lento, el puntero rápido tiene 2 pasos a la vez y el puntero lento tiene 1 paso a la vez
La condición de terminación es que el puntero rápido esté vacío o que el siguiente nodo del puntero rápido esté vacío (porque devuelve el segundo nodo en el medio cuando es par)
Practique aquí: después del experimento, descubrí que es realmente inteligente y que otros tamaños de paso no pueden satisfacer...
R. Cuando la longitud de la lista enlazada es par, se supone que es 4. El puntero rápido debe apuntar a NULL y el puntero lento está justo en la segunda posición en el medio.
slow------slow-----------slow
fast---------------------fast----------------------fast
1 -> 2 -> 3 -> 4 ->NULL
B. Cuando la longitud de la lista enlazada es impar, se supone que es 5. El puntero rápido debe apuntar al nodo final y el puntero lento está justo en el nodo medio.
slow----------slow-----------slow
fast-------------------------fast--------------------------fast
1 -> 2 -> 3 -> 4 -> 5 -> NULL
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *fast=head,*slow=head;
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
Espada se refiere a la Oferta 22. El k-ésimo último nodo en la lista enlazada
Configure los punteros rápido y lento para que apunten al nodo principal.
Deje que el puntero rápido avance k pasos primero, y luego configure los punteros rápido y lento para que avancen un paso. Cuando el puntero rápido atraviesa el nodo de cola, el puntero lento apunta al k-ésimo nodo desde abajo.
¿cómo?
1->2->3->4->5->NULO, k=2 ====> [4->5]
más rápido: ir k pasos primero, apuntando al 3
lento: apuntar a la cabeza 1
Luego, los siguientes dos punteros se mueven 1 paso al mismo tiempo, hasta que el puntero rápido llega al final -> NULL
más rápido: NULL orientado
lento: orientado 4
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *faster = head, *slow = head;
int cnt = 0;
while(1){
faster = faster->next;
cnt += 1;
if(cnt == k) break;
}
while(faster!=NULL)
{
slow = slow->next;
faster = faster->next;
}
return slow;
}
};
206. Lista de enlaces inversos
Es dificil ser una gallina, que clase de monstruo es el 0-0!!!
El método de inserción de encabezado inserta el último nodo al principio. Tenga en cuenta que los significados de encabezado y encabezado->siguiente no son los mismos...
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *newHead = nullptr;
while (head != nullptr) {
//使用了一个 newHead 指针来记录新链表的头部,以及一个 head 指针来遍历原始链表
ListNode *next = head->next;
head->next = newHead;
newHead = head;
head = next;
}
return newHead;
}
};
//newHead -> nullptr
//head -> A -> B -> C -> nullptr
//newHead -> A -> nullptr
//head -> B -> C -> nullptr
//newHead -> B -> A -> nullptr
//head -> C -> nullptr
//newHead -> C -> B -> A -> nullptr
//head -> nullptr
La inserción de cola no funciona para listas invertidas porque solo hace una copia
puntero doble
// 注意啊 这里的什么*都是指针,并不是节点啊!没有节点需要被操作,我们操作的是他们之间的指针指向
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 双指针辅助移动 pre->指向头节点 cur指向空
ListNode* pre = head, *cur = NULL;
while(pre!=NULL)
{
//需要存储一下pre指向下一节点的指针 不然没法移动
ListNode* temp = pre->next;
pre->next = cur; //让pre指向cur
cur = pre; //让cur变成pre实现向右/左移动
pre = temp;//这里不能写pre->next 因为这时候pre->next=cur...这里就要用temp
}
return cur;
}
};
recursión malvada
Recorra hasta el final, y luego regrese para modificar el apuntamiento del siguiente nodo del nodo actual al nodo actual cada vez, y restablezca el apuntamiento del siguiente nodo apuntador para que esté vacío
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 使用递归 出口是到尽头NULL
// 遍历到最后一个节点 然后它将会变成头节点(因为是反转)
// 让当前节点的下一个节点指向当前节点???4->5->NULL 5->4->NULL
// 让当前节点next指向NULL
if(head == NULL || head->next==NULL)
{
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return ret;
}
};
puntero doble demonizado
///
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL) return NULL;
ListNode* cur = head;
while(head->next!=NULL)
{
// head->next->next => head 先把head->next->next存起来 是新节点t
// cur = head->next 移动下一个位置
// 此时head->next要指向下一个新节点 t
ListNode* t = head->next->next;//存好之后不需要担心之后找不到新节点的问题
head->next->next = cur;
cur = head->next;
head->next = t;
}
return cur;
}
};
92. Lista de enlaces inversos II
Invierta el orden de los nodos del lr fijo... como una versión avanzada de la lista de enlaces inversos
La razón principal es que si necesita incrustar la lista vinculada original después de invertir el segmento de la lista vinculada, debe ajustar el orden de los punteros en la cabeza y la cola.
Método de inserción de la cabeza:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 定义一个dummyHead, 方便处理
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
// 初始化指针
ListNode g = dummyHead;
ListNode p = dummyHead.next;
// 将指针移到相应的位置
for(int step = 0; step < left - 1; step++) {
g = g.next; p = p.next;
}
// 头插法插入节点
for (int i = 0; i < right - left; i++) {
ListNode removed = p.next;
p.next = p.next.next;
removed.next = g.next;
g.next = removed;
}
return dummyHead.next;
}
directamente al revés...
Puntero doble:
También necesita insertar un nodo centinela porque cuando se deja == 1, la inversión directa saldrá mal...
pre apunta al nodo transversal cur apunta al nodo de inicialización NULL
p0 es la posición del nodo anterior del nodo invertido... del dummy...
Lista enlazada: d->1->2->3->4->5->NULL
Invertir [3,4] p0 = d; el número de recorridos es (3-1) dos veces, p0 ->1->2 es exactamente al nodo anterior
Si escribe 3, es un nodo que está exactamente invertido, de esta manera, la posición del nodo antes de la inversión no se puede guardar, y luego la lista enlazada del nodo anterior no se puede conectar...
La lista enlazada anterior que no necesita invertirse es d->1->2 p0 guarda el subíndice de la posición final de esta ruta
//自己实现的
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummy = new ListNode(0);
dummy->next = head;
ListNode *p0 = dummy;
for(int i=0;i<left-1;i++)
//遍历到left-1的位置 因为我需要把p0控制在反转节点的前一个节点的位置
p0 = p0->next;
//反转次数 right - left + 1
// cur 指向当前的被反转的元素
// pre 指向上一个元素
ListNode *cur = p0->next, *pre = NULL;
for(int i=0;i<right-left+1;i++)
{
ListNode *nxt = cur->next;//指向cur的下一个节点
cur->next = pre;//修改cur的指向
pre = cur; // 移动pre到已经反转的节点
cur = nxt;// 移动cur到下一个节点
}
// 还没完 我们只是单纯反转了其中的[l,r]的方向
// 拼接链表
// 此时pre = 已经反转的最后一个节点 是头节点
// 此时cur = 已经反转节点的下一个节点 未反转节点的第一个
// 以下的顺序不能改 不然无法修改p0->next->next的指向
p0->next->next = cur;
// p0->next 反转节点的第一个节点 再指向未反转链表的头节点cur
p0->next = pre;
// p0指向下一个节点是已经反转的最后一个节点
return dummy->next;//head
}
};
143. Reorganizar lista enlazada
Investiga tres preguntas: (¡Mujer venenosa! Pura vergüenza)
- 1. Punteros rápidos y lentos para encontrar nodos intermedios
- 2. Invertir la segunda mitad de la lista enlazada
- 3. Combinar las listas enlazadas delantera y trasera
1->2->3->4->5->6
->Truncar 1->2->3 || 4->5->6
->Invertir la segunda mitad 1->2->3 || 6->5->4
->Fusión intercalada6->1->5->2->4->3
pseudocódigo:
A
// 快慢指针找到中间节点...但是这里求的是中间节点的第一个
// 所以终止条件要模拟改变一下...
// 我们希望在链表长度是偶数的时候,slow落在中间部分的左侧
假设len = 4 以下类型是我们想要的 可以发现此时fast没有到末尾并且下一个节点也不是NULL,但是它下一个节点的下一个指向是NULL;
slow----slow
fast-----------fast
1 2 3 4 NULL
假设len = 5 以下类型是我们想要的:此时fast只到末尾节点
slow----slow---slow
fast-----------fast----------fast
1 2 3 4 5 NULL
总结得到只要满足: fast->next!=NULL and fast->next->next!=NULL 就继续走
fast,slow = head,head
while fast->next!=NULL and faste->next->next!=NULL:
fast = fast->next->next
slow = slow->next
return slow
temp = slow
B
反转链表的操作...在A之后已经获得切割出来的头节点temp
由于是单链表后半段,故末尾一定指向NULL
head = temp
pre = NULL
while(head!=NULL)
{
t = head->next;
head->next = pre;
pre = head;
head = t;
}
return pre //头节点
C
ListNode *i = head1 , *j = head2
while i!=NULL and j!=NULL:
t1 = i->next
t2 = j->next
i->next = j
j->next = t
i = t1
j = t2
return head1
mi código:
Primero implementar 3 funciones correspondientes a los objetivos de la tarea
1. Encuentra el punto medio de la lista enlazada
2. Lista de enlaces inversos
3. Fusionar lista enlazada
class Solution {
public:
void reorderList(ListNode* head) {
// 特判
if(head == NULL) return;
// 找中间节点
ListNode *mid = FindMidNode(head);
// 这里注意是中间节点的第一个 所以反转的后半段是第二个中间节点开头的
ListNode *reverse_right = reverseList(mid->next);
// 断链之后要把中点指向的改写NULL
mid->next = NULL;
merge_List(head,reverse_right);
}
//找截断中点
ListNode* FindMidNode(ListNode* head)
{
ListNode *fast = head, *slow = head;
//这里注意和找中点的那个题不一样,偶数的长度要截断的是中点左侧的那个点
while(fast->next != NULL && fast->next->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
// 反转链表 这里因为是直接给出头节点所以直接遍历就可以
// 如果没有找中点的话,就要根据left,right写一个反转链表2的函数...
ListNode* reverseList(ListNode* head)
{
ListNode* pre = NULL;
while(head!=NULL)
{
ListNode *t = head->next;
head->next = pre;
pre = head;
head = t;
}
return pre;
}
void merge_List(ListNode *i, ListNode *j)
{
// 依次接起来 i -> j -> i->next -> j->next -> ...
while(i!=NULL && j!= NULL)
{
ListNode *ti = i->next;
ListNode *tj = j->next;
i->next = j;
j->next = ti;
i = ti;
j = tj;
}
}
};
148. Ordenar Lista enlazada
Merge Sort C++ Merge Sort Board Preguntas ¿Recuerdas? ? ?
//先写一遍归并排序的板子
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int tmp[N];
int a[N];
void merge_sort(int q[],int l,int r)
{
//特判
if(l>=r) return;
//先划分中点
int mid = l+r>>1;
int i = l, j =mid+1, k = 0;;
//快乐递归...
merge_sort(q,i,mid);
merge_sort(q,j,r);
while(i<=mid && j<=r)
{
if(q[i]<=q[j]) tmp[k++] = q[i++];//i++ 先返回i 再++
else tmp[k++] = q[j++];
}
// 长的一方并到tmp里
while(i<=mid) tmp[k++] = q[i++];
while(j<=r) tmp[k++] = q[j++];
for(int i=l,j=0;i<=r;i++,j++)
q[i] = tmp[j];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
merge_sort(a,0,n-1);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
cout<<endl;
return 0;
}
código leetcode:
recursión
1. Encuentra el punto medio en secciones
2. Ordene las dos sublistas por separado
3. Combinar sublistas
//新建一个头节点用于存储合并结果
newnode = new ListNode(0)
*p = newnode
//传入 head1 head2 链表
//同时遍历 head1 head2 的节点 并比较当前节点值大小
while head1!=NULL and head2!=NULL:
if head1->val < head2->val:
p->next = head1
head1 = head1->next
else:
p->next = head2
head2 = head2->next
//合并长的链表
if(head1!=NULL) p->next = head1
if(head2!=NULL) p->next = head2
return newnode
Código oficial:
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* head1 = head;
ListNode* head2 = split_(head);
head1 = sortList(head1); //一条链表分成两段分别递归排序
head2 = sortList(head2);
return merge(head1, head2); //返回合并后结果
}
//双指针找单链表中点模板 中点左侧
ListNode* split(ListNode* head)
{
ListNode *slow = head, *fast = head;
while (fast->next->next != nullptr && fast->next != nullptr)
{
slow = slow->next;
fast = fast->next->next;
}
// 这里必须要处理把链表断开,不然指针乱指
ListNode* mid = slow->next;
slow->next = nullptr; //断尾
return mid;
}
//双指针找单链表中点模板
ListNode* split_(ListNode* head)
{
ListNode *slow = head, *fast = head;
//中点右侧
ListNode *p = NULL;
while(fast!= nullptr && fast->next != nullptr)
{
p = slow;
slow = slow->next;
fast= fast->next->next;
}
p->next = NULL;
return slow;
}
//合并两个排序链表模板
ListNode* merge(ListNode* head1, ListNode* head2)
{
ListNode *dummy = new ListNode(0), *p = dummy;
while (head1 != nullptr && head2 != nullptr)
{
if (head1->val < head2->val)
{
p = p->next = head1;
head1 = head1->next;
}
else
{
p = p->next = head2;
head2 = head2->next;
}
}
if (head1 != nullptr) p->next = head1;
if (head2 != nullptr) p->next = head2;
return dummy->next;
}
};