Preguntas de algoritmo - C++ (4) Lista de enlaces malvados

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

inserte la descripción de la imagen aquí

//自己实现的
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;
    }
};

Supongo que te gusta

Origin blog.csdn.net/daxuanzi515/article/details/130140260
Recomendado
Clasificación