[Likou] Résumé des questions de la liste chaînée

Annuaire d'articles

Bases des listes chaînées

Il existe trois types de listes chaînées : la liste chaînée simple, la liste chaînée double et la liste chaînée circulaire.

1. Liste à chaînage unique :
Insérer la description de l'image ici

2. Liste chaînée double :
Insérer la description de l'image ici
3. Liste chaînée circulaire :
Insérer la description de l'image ici
Structure de définition de liste chaînée :

  • Liste unique
struct ListNode {
    
    
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {
    
    }
    ListNode(int x) : val(x), next(nullptr) {
    
    }
    ListNode(int x, ListNode *next) : val(x), next(next) {
    
    }
};
  • liste chaînée circulaire
class Node {
    
    
public:
    int val;
    Node* next;

    Node() {
    
    }

    Node(int _val) {
    
    
        val = _val;
        next = NULL;
    }

    Node(int _val, Node* _next) {
    
    
        val = _val;
        next = _next;
    }
};

Type de question

1. Retourner, inverser et faire pivoter les listes à chaînage unique

1. Inversez la liste chaînée

Méthode récursive :

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
		if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

2. Liste chaînée inversée II - Liste chaînée partielle inversée

Méthode de tirage de l'aiguille et du fil :

class Solution {
    
    
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
    
    
        ListNode* dummyHead = new ListNode(-1);
        dummyHead->next = head;
        // pre指针指向旋转部分前的最后一个结点
        ListNode* pre = dummyHead;
        for (int i = 1; i < left; ++i) {
    
    
            pre = pre->next;
        }
        // curr指向已旋转部分完毕的最后一个结点
        ListNode* curr = pre->next;
        // next指向待插入的结点(也是curr的下一个结点)
        ListNode* next = curr->next;
        // 插入right-left次即可完成翻转
        for (int i = 0; i < right - left; ++i) {
    
    
            // cout << pre->val << " " << curr->val << " " << next->val << endl;
            curr->next = next->next;
            next->next = pre->next;
            pre->next = next;
            next = curr->next;
            
        }
        return dummyHead->next;
    }
};

Trois variables doivent être stockées :

  • pre pointe vers le nœud précédent 1 de la liste chaînée à inverser (ne change jamais)
  • curr pointe vers le premier nœud 2 de la liste chaînée à inverser, qui est également le dernier nœud de la partie inversée (ne change jamais)
    (par exemple, si ABCD doit être inversé, curr pointe vers le nœud A, et cela a été inversé après 2 opérations. CBAD, curr pointe toujours vers le nœud A, qui représente le dernier nœud de la partie inversée)
  • next pointe vers le nœud à insérer devant la partie inversée (pointe vers le prochain nœud de curr, car le prochain nœud de curr changera, donc chaque opération doit être mise à jour)
  1. La première fois pour tirer l'aiguille et le fil (insérer le nœud 3 après le pré-nœud) :
    Insérer la description de l'image ici

L'ordre des étapes de fonctionnement (1)(2)(3) ne peut pas être modifié :

  • (1) Pointez le nœud suivant de curr vers le nœud suivant de nextcurr->next = next->next;
  • (2) Pointez le nœud suivant de next vers le nœud suivant de pre (notez le nœud actuel)next->next = pre->next;
  • (3) Pointez le nœud suivant de pre vers le nœud suivantpre->next = next;

L'étape (1) dépend du nœud suivant de next, donc l'étape (2) doit être après l'étape (1) ; l'étape (2) dépend du nœud suivant de pre, donc l'étape (3) doit être après (2) ;

  1. La deuxième fois pour tirer l'aiguille (insérer le nœud 4 après le pré-nœud) : Une fois la
    Insérer la description de l'image ici
    deuxième insertion terminée, la liste chaînée est inversée : 1->4->3->2->5

Un total d' right-leftopérations de « traction sur l'aiguille et le fil » sont nécessaires pour achever l'inversion.

3. Liste chaînée rotative

class Solution {
    
    
public:
    // n为链表长度,当k大于n时,k=k%n+1
    // 向右移动k位后,结点排列为前k个数为n-k+1到n;后n-k个数为1到n-k
    ListNode* rotateRight(ListNode* head, int k) {
    
    
        if (k == 0 || head == nullptr) {
    
     // *注意特殊情况
            return head;
        }        // 先遍历一遍获得链表长度
        ListNode* p = head;
        int n = 1;
        while (p->next != nullptr) {
    
    
            ++n;
            p = p->next;
        }
        k = k % n;
        // 构造循环链表
        p->next = head;
        // 将链表从n-k到n-k+1中间断开
        p = head;
        for (int i = 1; i < n - k; ++i, p = p->next) {
    
    } // p走到n-k结点 *注意p++和p=p->next不能替换
        ListNode* tmp = p;
        p = p->next;
        tmp->next = nullptr;
        return p;
    }
};

4. Un groupe de listes chaînées K flip

  • En tirant toujours sur l'aiguille et le fil, retournez k nœuds et effectuez des opérations d'insertion k-1 pour chaque groupe. Avant de procéder au retournement, déterminez d’abord s’il reste au moins k nœuds.
class Solution {
    
    
public:
    // 判断还有没有k个结点待反翻转
    bool canReverse(ListNode* p, int k) {
    
    
        int num = 0;
        while (p) {
    
    
            p = p->next;
            ++num;
            if (num >= k) {
    
    
                return true;
            }
        }
        return false;
    }

    ListNode* reverseKGroup(ListNode* head, int k) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }

        ListNode* dummyHead = new ListNode(-1, head);
        ListNode* pre = dummyHead;
        ListNode* curr = head;
        ListNode* next = curr->next;
        ListNode* p = pre;
        
        while (canReverse(p->next, k)) {
    
    
            int num = k - 1;
            while (num--) {
    
     // 插入k-1次即完成一组翻转
                curr->next = next->next;
                next->next = pre->next;
                pre->next = next;
                next = curr->next;
            }
            pre = curr;
            p = pre;
            if (next != nullptr) {
    
    
                curr = curr->next;
                next = next->next;
            } else {
    
    
                break;
            }
        }

        return dummyHead->next;
    }
};

5. Inverser les nœuds en groupes de longueur paire

class Solution {
    
    
public:
    ListNode* reverseEvenLengthGroups(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* pre = head;
        ListNode* next;
        ListNode* p = head->next;
        int groupNum = 2;
        int curNum = 1;
        while (p && p->next) {
    
    
            while (p->next && curNum < groupNum) {
    
    
                p = p->next;
                ++curNum;
            }
            if (curNum % 2 == 0) {
    
    
                next = p->next;
                p->next = nullptr;
                ListNode* node = pre->next;
                pre->next = reverseList(pre->next);
                node->next = next;
                pre = node;
                p = pre->next;
            } else {
    
    
                pre = p;
                if (p) {
    
    
                    p = p->next;
                }
            }
            groupNum += 1;
            curNum = 1;
        }
        return head;
    }

    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

2. Supprimer les nœuds dans la liste à lien unique

1. Supprimez le nœud de la liste chaînée

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* deleteNode(ListNode* head, int val) {
    
    
        ListNode* dummyHead = new ListNode(-1);
        dummyHead->next = head;
        ListNode* p = dummyHead;
        while (p->next != nullptr) {
    
    
            if (p->next->val == val) {
    
    
                p->next = p->next->next;
                break;
            }
            p = p->next;
        }
        ListNode* res = dummyHead->next;
        delete dummyHead;
        return res;
    }
};

2. Supprimez les nœuds en double dans la liste chaînée non triée

  • Si la complexité de l'espace est O(n), vous pouvez utiliser une table de hachage pour stocker si chaque élément apparaît. La complexité temporelle peut atteindre O(n).
class Solution {
    
    
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        unordered_map<int, int> cnt;
        ListNode* p = head;
        cnt[head->val] = 1;
        while (p->next != nullptr) {
    
    
            if (cnt[p->next->val] > 0) {
    
     // 已经出现过的结点删除
                p->next = p->next->next;
            } else {
    
    
                cnt[p->next->val] = 1;
                p = p->next;
            }
        }
        return head;
    }
};
  • Si la complexité spatiale doit être O(1), le temps ne peut être échangé que contre de l'espace, ce qui peut être réalisé par un parcours en double boucle, avec une complexité temporelle de O(n^2).
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
    
    
        ListNode* p = head;
        while (p != nullptr) {
    
    
            ListNode* q = p;
            while (q->next != nullptr) {
    
    
                if (q->next->val == p->val) {
    
     // *注意写法:需要通过q->next的值与p的值比较,如果是比较q与p,则无法删除q节点(不知道前驱结点
                    q->next = q->next->next; // 删除q->next这个重复结点
                } else {
    
    
                    q = q->next;
                }
            }
            p = p->next;
        }
        return head;
    }
};

3. Supprimez les éléments en double I dans la liste chaînée triée - il ne reste qu'un seul élément en double

class Solution {
    
    
public:
    ListNode* deleteDuplicates(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* p = head->next; // 快指针
        ListNode* q = head; // 慢指针
        ListNode* res = q;
        while (p != nullptr) {
    
    
            if (p->val != q->val) {
    
    
                q->next = p;
                q = q->next;
            }
            p = p->next;
        }
        q->next = nullptr;
        return res;
    }
};

4. Supprimez les éléments en double dans la liste chaînée triée II - supprimez tous les éléments en double

class Solution {
    
    
public:
    ListNode* deleteDuplicates(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* dummyHead = new ListNode(-101, head);
        ListNode* p = dummyHead;
        ListNode* q;
        while (p != nullptr && p->next != nullptr) {
    
    
            q = p->next->next;
            if (q && q->val == p->next->val) {
    
    
                // 找到下一个值不等于p->next值的节点q
                while (q && q->val == p->next->val) {
    
    
                    q = q->next;
                }
                p->next = q;
            } else {
    
    
                p = p->next;
            }
        }
        return dummyHead->next;
    }
};

5. Supprimez le nième nœud du bas de la liste chaînée

  • Méthode 1 : parcourez d'abord une fois pour calculer la longueur de la liste chaînée, puis parcourez une deuxième fois pour supprimer le nième nœud du dernier
class Solution {
    
    
public:
    // 获取链表长度
    int getLength(ListNode* head) {
    
    
        int len = 0;
        while (head != nullptr) {
    
    
            ++len;
            head = head->next;
        }
        return len;
    }
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode* dummyHead = new ListNode(-1);
        dummyHead->next = head;
        int len = getLength(head);
        ListNode* p = dummyHead;
        // p走到倒数第n个结点的前驱结点
        for (int i = 1; i < len - n + 1; ++i) {
    
    
            p = p->next;
        }
        // 删除倒数第n个结点
        p->next = p->next->next;
        // 删除dummy节点,防止内存泄露
        ListNode* ans = dummyHead->next;
        delete dummyHead;
        return ans;
    }
}
  • Méthode 2 : utilisez des pointeurs doubles rapides et lents. Une fois que le pointeur rapide a parcouru n+1 pas en premier, le pointeur lent redémarre. Lorsque le pointeur rapide atteint la fin de la liste chaînée, le pointeur lent se trouve désormais au niveau du nœud prédécesseur de le nième nœud du dernier.
class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        // 使用dummy头结点,防止头结点被删
        ListNode* dummyHead = new ListNode(-1, head);
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        // 快指针先走n + 1个结点
        for (int i = 0; i <= n; ++i) {
    
    
            fast = fast->next;
        }
        // 快指针领先n + 1个结点后,快慢指针一起走,快指针走到nullptr时,慢指针正好指向倒数第n个结点的前驱结点
        while (fast != nullptr) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        // 删除倒数第n个结点
        slow->next = slow->next->next;
        // 删除dummy节点,防止内存泄露
        ListNode* ans = dummyHead->next;
        delete dummyHead;
        return ans;
    }
};

6. Supprimer les nœuds de la liste chaînée

Pour chaque nœud de la liste, si un nœud avec une valeur strictement supérieure existe à sa droite, supprimez le nœud.
Astuce : Après suppression, il doit s'agir d'une séquence monotone non croissante et la valeur de chaque nœud est supérieure ou égale à la valeur du nœud suivant.

  • Méthode 1 : opération récursive
class Solution {
    
    
public:
    ListNode* removeNodes(ListNode* head) {
    
    
        if (head->next == nullptr) {
    
    
            return head;
        }
        ListNode* tmp = removeNodes(head->next); // tmp链表已是单调非递增序列
        // 比较head和tmp的值,head需要大于等于tmp才符合要求,否则将head删除
        if (head->val < tmp->val) {
    
     
            return tmp; // 删除head
        }
        head->next = tmp; // head符合要求,加入到链表
        return head;
    }
};
  • Méthode 2 : inversez votre façon de penser.
    Il est facile d'en déduire que le dernier nœud doit être conservé, car il n'y a pas de nœud plus grand que lui après lui, alors vous pouvez envisager d'inverser la liste chaînée, en commençant par le premier nœud après l'inversion, la conservation peut former un non-linéaire monotone Les nœuds de la séquence descendante (ce dernier nœud doit être supérieur ou égal au nœud précédent). Enfin, inversez une fois la liste chaînée résultante et obtenez le résultat.
    Pourquoi devons-nous fonctionner dans l’ordre inverse ? Parce qu'il est certain que le dernier nœud sera conservé, l'ordre inverse ne nécessite qu'une complexité temporelle O(n) et une complexité spatiale O(1) ; l'ordre direct nécessite deux parcours de boucle pour être complétés sans l'aide d'espace supplémentaire.
class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }

    ListNode* removeNodes(ListNode* head) {
    
    
        head = reverseList(head);
        ListNode* p = head;
        int preVal = head->val;
        while (p->next != nullptr) {
    
    
            // cout << preVal << " " << p->next->val << endl;
            if (p->next->val < preVal) {
    
    
                p->next = p->next->next; // 删除p->next结点
            } else {
    
    
                preVal = p->next->val;
                p = p->next;
            }
        }
        return reverseList(head);
    }
};

7. Supprimez le nœud intermédiaire de la liste chaînée

Supprimez le ⌊n / 2⌋ème nœud (la longueur de la liste chaînée est n, l'indice commence à 0)
⌊x⌋ représente le plus grand entier inférieur ou égal à x. Par exemple, n=7, supprimez le nœud avec l'indice ⌊7 / 2⌋ = 3, qui est le quatrième nœud.
Lorsque la liste chaînée comporte un nombre impair de nœuds, supprimez celui du milieu :
Insérer la description de l'image ici
lorsque la liste chaînée comporte un nombre pair de nœuds, supprimez celui de droite au milieu :
Insérer la description de l'image ici

  • Pensez à utiliser des pointeurs rapides et lents. En partant du nœud muet en même temps, le pointeur rapide déplace deux nœuds à la fois et le pointeur lent déplace un nœud à la fois. Lorsque le nœud suivant du pointeur rapide ou le nœud suivant est nullptr, le pointeur lent vient juste de va au milieu.La position du nœud prédécesseur du nœud.
class Solution {
    
    
public:
    ListNode* deleteMiddle(ListNode* head) {
    
    
        ListNode* dummyHead = new ListNode(-1, head);
        if (head->next == nullptr) {
    
    
            return nullptr;
        }
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        ListNode* pre = dummyHead;
        while (fast->next != nullptr && fast->next->next != nullptr) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        ListNode* res = dummyHead->next;
        delete dummyHead;
        return res;
    }
};

8. Supprimez les nœuds consécutifs avec une somme nulle de la liste chaînée

  • Idée de somme de préfixe : mémorisez la somme de préfixe s[n]comme la somme des valeurs des n premiers nœuds. En supposant 0<i<j, alors s[j] = s[i] + 连续区间(i,j]中结点的总和. Si s[i]=s[j], cela signifie que la somme des nœuds dans l'intervalle continu (i, j] est 0.
    Parcourez d'abord la liste chaînée et enregistrez la position de chaque préfixe et la dernière occurrence ; parcourez la deuxième fois, si le préfixe de l'actuel Le nœud iet la position enregistrée sont trouvés Oui j, cela signifie que la même somme de préfixes apparaît, et la somme des valeurs des nœuds entre les deux sommes de position est 0, supprimez isimplement tous ces nœuds.j(i,j]
    Avis: En plus de la somme des préfixes égale de deux nœuds, il existe une autre situation qui peut montrer que la somme des nœuds consécutifs est 0, c'est-à-dire que la somme des préfixes d'un certain nœud est 0 , par exemple, s[i] = 0 , c'est-à-dire le premier i La somme des nœuds est 0. Vous pouvez envisager de définir un nœud principal stupide devant le nœud principal et de définir sa valeur sur 0, puis sa somme de préfixe est également 0, de sorte que les i premiers nœuds puissent être supprimés.
class Solution {
    
    
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
    
    
        ListNode* dummyHead = new ListNode(0, head);
        // 第一遍遍历获取前缀和最后出现的结点位置
        unordered_map<int, ListNode*> last;
        ListNode* p = dummyHead;
        int sum = 0;
        while (p != nullptr) {
    
    
            sum += p->val;
            last[sum] = p;
            p = p->next;
        }
        // 第二遍遍历删除相同前缀和之间的结点
        p = dummyHead;
        sum = 0;
        while (p != nullptr) {
    
    
            sum += p->val;
            if (last[sum] != p) {
    
    
                // 删除(i,j]中的结点
                p->next = last[sum]->next;
            }
            p = p->next;
        }
        return dummyHead->next;
    }
};

9. Supprimer les nœuds de la liste chaînée

Le donné est le nœud à supprimer

  • Puisqu'il s'agit d'une liste à chaînage unique, le nœud prédécesseur du nœud n'est pas accessible, donc le nœud actuel ne peut être défini qu'avec la valeur du nœud suivant, puis le nœud suivant peut être supprimé.
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    void deleteNode(ListNode* node) {
    
    
        node->val = node->next->val;
        node->next = node->next->next;
    }
};

3. Tri des listes chaînées

1. Tri de liste chaînée unique

  • Les méthodes de tri de listes chaînées couramment utilisées incluent le tri par tas et le tri par fusion.
    • Tri par tas : complexité temporelle o(nlogn), complexité spatiale o(n)
    • Tri par fusion:
      • Tri descendant : complexité temporelle o(nlogn), complexité spatiale o(logn)
      • Tri ascendant : complexité temporelle o(nlogn), complexité spatiale o(1)
      • Grâce à la fusion ascendante, les appels récursifs peuvent être modifiés en itérations sans utiliser d'espace supplémentaire.
  • Méthode 1 : tri par tas
class Solution {
    
    
public:
    static bool cmp(ListNode* a, ListNode* b) {
    
    
        return a->val > b->val;
    }

    ListNode* sortList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        priority_queue<ListNode*, vector<ListNode*>, decltype(&cmp)> heap(cmp); // 构造最小堆
        while (head != nullptr) {
    
    
            heap.push(head);
            head = head->next;
        }
        ListNode* dummyHead = new ListNode(-1);
        ListNode* p = dummyHead;
        while (!heap.empty()) {
    
    
            p->next = heap.top();
            heap.pop();
            p = p->next;
        }
        p->next = nullptr;
        return dummyHead->next;
    }
};
  • Méthode 2 : tri par fusion descendant (implémentation récursive)
    • Étape 1 : divisez la liste chaînée en deux listes chaînées à partir du milieu (utilisez les pointeurs rapide et lent pour trouver le nœud intermédiaire)
    • Étape 2 : Trier les deux listes chaînées séparément (appelez sortList récursif)
    • Étape 3 : Fusionner deux listes chaînées ordonnées (fusionner)
class Solution {
    
    
public:
    ListNode* sortList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
     // 空节点或单个节点不需要排序
            return head;
        }
        // 从中间将链表拆分成两部分分别进行排序,然后将两链表进行合并
        ListNode* mid = getMidNode(head);
        return merge(sortList(head), sortList(mid));
    }

    // 获取中间结点
    ListNode* getMidNode(ListNode* head) {
    
    
        ListNode* fast = head->next; // 相当于都从哑巴节点出发,快指针先走两步到head->next,慢指针走一步到head
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr; // 需要将前一段链表末尾置空
        return mid;
    }

    // 合并两个有序链表head1和head2为一个有序链表
    ListNode* merge(ListNode* head1, ListNode* head2) {
    
    
        // cout << head1->val << " " << head2->val << endl;
        ListNode* dummyHead = new ListNode(-1);
        ListNode* p = dummyHead;
        ListNode* p1 = head1;
        ListNode* p2 = head2;
        while (p1 && p2) {
    
    
            if (p1->val < p2->val) {
    
    
                p->next = p1;
                p1 = p1->next;
            } else {
    
    
                p->next = p2;
                p2 = p2->next;
            }
            p = p->next;
        }
        p->next = p1 ? p1 : p2;
        return dummyHead->next;
    }
};
  • Méthode 3 : tri par fusion ascendant (implémentation itérative)

2. Fusionner k listes ascendantes à lien unique

Étant donné un tableau de listes chaînées, chaque liste chaînée a été triée par ordre croissant. Fusionnez toutes les listes chaînées dans une liste chaînée triée.

  • Méthode 1 : méthode simple : fusionnez d’abord la liste chaînée 1 et la liste chaînée 2, puis fusionnez la liste chaînée fusionnée avec la liste chaînée 3, et ainsi de suite.
class Solution {
    
    
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
    
    
        if (lists.empty()) {
    
    
            return nullptr;
        }
        ListNode* res = lists[0];
        for (int i = 1; i < lists.size(); ++i) {
    
    
            res = merge2Lists(lists[i], res);
        }
        return res;
    }

    ListNode* merge2Lists(ListNode* head1, ListNode* head2) {
    
    
        ListNode* dummyHead = new ListNode(-1);
        ListNode* p = dummyHead;
        while (head1 && head2) {
    
    
            if (head1->val < head2->val) {
    
    
                p->next = head1;
                head1 = head1->next;
            } else {
    
    
                p->next = head2;
                head2 = head2->next;
            }
            p = p->next;
        }
        p->next = head1 ? head1 : head2;
        return dummyHead->next;
    }
};
  • Méthode 2 : tas minimum. Placez la liste chaînée dans le tas minimum (file d'attente prioritaire) et triez-la automatiquement.
class Solution {
    
    
public:
    static bool cmp (ListNode* a, ListNode* b) {
    
    
        return a->val > b->val;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
    
    
        priority_queue<ListNode*, vector<ListNode*>, decltype(&cmp)> heap(cmp);
        for (ListNode* p : lists) {
    
    
            while (p) {
    
    
                heap.push(p);
                p = p->next;
            }
        }
        ListNode* dummyHead = new ListNode(0);
        ListNode* p = dummyHead;
        while (!heap.empty()) {
    
    
            p->next = heap.top();
            cout << heap.top()->val << endl;
            heap.pop();
            p = p->next;
        }
        p->next = nullptr; // *最后一定要置空
        return dummyHead->next;
    }
};
  • Méthode 3 : trier par fusion

3. Réorganiser la liste à chaînage unique

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
```
L0 → L1 → … → Ln - 1 → Ln
```
请将其重新排列后变为:
```
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
```
  • Divisez la liste chaînée en deux listes chaînées à partir du milieu, puis inversez la seconde moitié des listes chaînées, puis fusionnez les deux listes chaînées.
class Solution {
    
    
public:
    void reorderList(ListNode* head) {
    
    
        if (!head || !head->next) {
    
    
            return;
        }
        ListNode* second = reverseList(splitFromMid(head));
        merge(head, second);
    }

    ListNode* splitFromMid(ListNode* head) {
    
    
        ListNode* fast = head->next;
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr;
        return mid;
    }

    ListNode* reverseList(ListNode* head) {
    
    
        if (!head || !head->next) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }

    void merge(ListNode* head1, ListNode* head2) {
    
    
        ListNode* tmp;
        while (head1 && head2) {
    
    
            tmp = head2;
            head2 = head2->next;
            tmp->next = head1->next;
            head1->next = tmp;
            head1 = tmp->next;
        }
    }
};

4. Liste chaînée circulaire triée

Étant donné un point dans une liste cyclique monotone non décroissante, écrivez une fonction pour insérer un nouvel élément insertVal dans la liste afin que la liste soit toujours dans l'ordre croissant cyclique.

class Solution {
    
    
public:
    Node* insert(Node* head, int insertVal) {
    
    
        if (head == nullptr) {
    
    
            Node* node = new Node(insertVal);
            node->next = node;
            return node;
        }
        Node* p = head;
        bool begin = true;
        bool insert = false;
        while (p != head || begin) {
    
    
            if ((p->next->val < p->val && p->next->val > insertVal) || // 插入一个最小值
                (p->next->val < p->val && p->val < insertVal) ||       // 插入一个最大值
                (p->val <= insertVal && p->next->val >= insertVal)) {
    
      // 插入一个中间值
                Node* node = new Node(insertVal);
                node->next = p->next;
                p->next = node;
                insert = true;
                break;
            }
            p = p->next;
            begin = false;
        }
        // 整个循环链表的值都相同
        if (!insert) {
    
    
            Node* node = new Node(insertVal);
            node->next = p->next;
            p->next = node;
        }
        return head;
    }
};

5. Tri par insertion sur une liste à chaînage unique

Étapes de l'algorithme de tri par insertion :
1. Le tri par insertion est itératif, déplaçant un seul élément à la fois jusqu'à ce que tous les éléments puissent former une liste de sortie ordonnée.
2. À chaque itération, le tri par insertion supprime uniquement un élément à trier des données d'entrée, trouve sa position appropriée dans la séquence et l'insère.
3. Répétez jusqu'à ce que toutes les données d'entrée aient été insérées.

class Solution {
    
    
public:
    ListNode* insertionSortList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        
        ListNode* pre = new ListNode(5001, head); // 头结点前新增一个最大结点,保证该节点每次都在已排序链表的末尾
        ListNode* dummyHead = new ListNode(0, pre);
        ListNode* p = dummyHead;
        ListNode* curr = head;  // 待排序节点
        ListNode* next = curr->next;
        while (curr != nullptr) {
    
    
            next = curr->next;
            // p指向已排序链表中第一个值大于curr结点的前驱结点
            while (p != curr && p->next->val <= curr->val) {
    
    
                p = p->next;
            }
            // 将curr插入到p节点后
            curr->next = p->next;
            pre->next = next;
            p->next = curr;
            // 节点更新
            curr = next;           
            p = dummyHead;
        }
        deleteNode(dummyHead->next, pre);
        return dummyHead->next;
    }

    void deleteNode(ListNode* head, ListNode* node) {
    
    
        ListNode* p = head;
        while (p->next != node) {
    
    
            p = p->next;
        }
        p->next = p->next->next;
        delete node;
    }
};

4. Liste chaînée Palindrome

Si une liste chaînée est un palindrome, alors la séquence de nœuds de la liste chaînée est la même, vue d'avant en arrière et d'arrière en avant.
Insérer la description de l'image ici

1. Déterminer la liste chaînée des palindromes

  • La liste chaînée palindrome doit être symétrique, donc divisez la liste chaînée du nœud central, puis inversez la liste chaînée suivante, puis comparez les deux listes chaînées pour voir si elles sont cohérentes.
class Solution {
    
    
public:
    bool isPalindrome(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return true;
        }
        if (head->next->next == nullptr) {
    
    
            return head->val == head->next->val;
        }
        ListNode* second = reverseList(splitFromMid(head));
        while (head && second) {
    
    
            if (head->val != second->val) {
    
    
                return false;
            }
            head = head->next;
            second = second->next;
        }
        return true;
    }

    ListNode* splitFromMid(ListNode* head) {
    
    
        ListNode* fast = head->next; // fast和slow别写放反
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr;
        return mid;
    }

    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

5. Liste chaînée circulaire

1. Déterminer la liste chaînée circulaire : Déterminez s'il y a un anneau dans la liste chaînée, comme le montre la figure : le nœud de queue de la liste chaînée est connecté au nœud central de la liste chaînée.

Insérer la description de l'image ici

  • utiliserpointeur de vitesseDéterminez s’il existe un cycle dans la liste chaînée. Le pointeur rapide fait deux pas à la fois et le pointeur lent fait un pas à la fois. Lorsque les pointeurs rapide et lent se déplacent dans l'anneau, parce que le pointeur rapide est rapide, le pointeur rapide doit rattraper le pointeur lent, c'est-à-dire que lorsque les pointeurs rapide et lent se rencontrent, cela signifie qu'il y a un anneau.
    Lorsque les deux pointeurs entrent dans l'anneau, chaque cycle de mouvement fait augmenter de un la distance entre le pointeur lent et le pointeur rapide, et en même temps la distance entre le pointeur rapide et le pointeur lent diminue également de un. il continue de bouger, le pointeur rapide rattrapera toujours le pointeur lent.
    S'il y a un cycle dans la liste chaînée, il n'y a pas de nullptr à la fin de la liste chaînée.
class Solution {
    
    
public:
    bool hasCycle(ListNode *head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return false;
        }
        ListNode* fast = head->next;
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
    
    
                return true;
            }
        }
        return false;
    }
};

2. Liste chaînée circulaire II : Renvoie le premier nœud où la liste chaînée commence à entrer dans l'anneau. Si la liste chaînée est sans boucle, null est renvoyé.

  • Toujours en utilisant des pointeurs rapides et lents, le pointeur rapide fait deux pas à la fois et le pointeur lent fait un pas à la fois.
  • Comme le montre la figure : supposons que la distance entre le nœud principal et le point d'entrée de la boucle est un pas. Le pointeur lent parcourt b pas dans la boucle et les deux pointeurs se rencontrent (en supposant que le pointeur rapide parcourt n fois dans la boucle) Le point de rendez-vous continue jusqu'au point d'entrée de la boucle. La distance entre les points de boucle est de c pas.
    Insérer la description de l'image ici
  • On peut voir que lorsque les pointeurs rapide et lent se rencontrent, le pointeur lent fait a+bun pas et le pointeur rapide fait a+b+n(b+c)un pas. Puisque la vitesse du pointeur rapide est le double de celle du pointeur lent, le nombre de pas effectués par le pointeur rapide Le pointeur doit être le double de celui du pointeur lent. La résolution de l'équation peut être a+b=a+n(b+c)+bobtenue a=n(b+c)-b=(n-1)(b+c)+c.
  • Cette équation montre que lorsqu'un pointeur part du nœud principal et que le pointeur lent part du point de rencontre , leur première rencontre aura certainement lieu au point d'entrée de la boucle.
  • Notez que le démarrage rapide et lent à partir du nœud principal, et les statistiques ici sont le nombre d'étapes. S'ils commencent à partir de dummyHead (c'est-à-dire que rapide va à head-> next en premier, et lent va à head), alors la conclusion est incorrect.
class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return nullptr;
        }
        ListNode* fast = head; // *注意不是head->next
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
    
    
                cout << fast->val << endl;
                ListNode* p = head;
                while (p != slow) {
    
    
                    p = p->next;
                    slow = slow->next;
                }
                return p;
            }
        }
        return nullptr;
    }
};

6. Obtenir des nœuds de liste chaînée

1. Renvoyez le k-ième nœud du dernier

  • pointeur de vitesse
class Solution {
    
    
public:
    int kthToLast(ListNode* head, int k) {
    
            
        ListNode* fast = head;
        int num = k;
        while (num--) {
    
    
            fast = fast->next;
        }
        ListNode* slow = head;
        while (fast) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        return slow->val;
    }
};
  1. Renvoie le k-ème nœud du dernier de la liste chaînée
  • Comme ci-dessus, renvoyez le nœud ici.
class Solution {
    
    
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
    
            
        ListNode* fast = head;
        int num = k;
        while (num--) {
    
    
            fast = fast->next;
        }
        ListNode* slow = head;
        while (fast) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};
  1. Le nœud central de la liste chaînée : je vous donne le nœud principal de la liste chaînée, veuillez trouver et renvoyer le nœud central de la liste chaînée. S'il y a deux nœuds intermédiaires, le deuxième nœud intermédiaire est renvoyé.
class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};
  1. Nœud aléatoire de liste chaînée
class Solution {
    
    
public:
    Solution(ListNode* head) {
    
    
        while (head) {
    
    
            arr.push_back(head->val);
            head = head->next;
        }
    }
    
    int getRandom() {
    
    
        return arr[rand() % arr.size()];
    }

private:
    vector<int> arr;
};

5. Échanger des nœuds de liste chaînée

Après avoir échangé les valeurs du k-ème nœud positif et de l'avant-dernier k-ème nœud de la liste chaînée, renvoyez le nœud principal de la liste chaînée (la liste chaînée commence l'indexation à partir de 1).

  • Échangez simplement les valeurs des nœuds, il n'est pas nécessaire d'échanger les nœuds.
class Solution {
    
    
public:
    ListNode* swapNodes(ListNode* head, int k) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* kNode = head;
        for (int i = 1; i < k; ++i) {
    
    
            kNode = kNode->next;
        }
        ListNode* lastKNode = findLastKNode(head, k);
        int tmp = kNode->val;
        kNode->val = lastKNode->val;
        lastKNode->val = tmp;
        return head;
    }

    ListNode* findLastKNode(ListNode* head, int k) {
    
    
        ListNode* fast = head;
        for (int i = 1; i <= k; ++i) {
    
    
            fast = fast->next;
        }
        ListNode* slow = head;
        while (fast) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

7. Intersection de listes chaînées

  1. Le premier nœud superposé des deux listes chaînées : comme le montre la figure c1, c'est le premier nœud superposé. S'il n'y a pas de nœud superposé, nullptr est renvoyé.

Insérer la description de l'image ici

  • Méthode 1 : table de hachage. Pour déterminer si deux listes chaînées se croisent, vous pouvez utiliser un jeu de hachage pour stocker les nœuds de liste chaînée. Parcourez d’abord la liste chaînée 1 et ajoutez chaque nœud de la liste chaînée 1 au jeu de hachage. Parcourez ensuite la liste chaînée 2. Pour chaque nœud parcouru, déterminez si le nœud est dans le jeu de hachage.
class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        unordered_set<ListNode*> nodeSet;
        while (headA) {
    
    
            nodeSet.insert(headA);
            headA = headA->next;
        }
        while (headB) {
    
    
            if (nodeSet.find(headB) != nodeSet.end()) {
    
    
                return headB;
            }
            headB = headB->next;
        }
        return nullptr;
    }
};
  • Méthode 2 : doubles pointeurs. Les deux pointeurs partent respectivement des nœuds principaux des deux listes chaînées. Lorsque les pointeurs atteignent la fin de la liste chaînée, l'étape suivante consiste à accéder au nœud principal de l'autre liste chaînée et à commencer la traversée. Supposons que la liste chaînée 1 est longue avant l'intersection a, la liste chaînée 2 est longue avant l'intersection bet la longueur de la partie d'intersection est c. Après que les deux pointeurs ont parcouru leurs propres listes chaînées, ils ont parcouru les parties non sécantes de l'autre liste chaînée, c'est-à-dire que les deux pointeurs viennent de se rencontrer a+b+cau point d'intersection alors qu'ils ont parcouru la même distance. Si les deux listes chaînées ne se croisent pas, elles pointeront toutes deux vers nullptr.
    Insérer la description de l'image ici
class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        ListNode* pA = headA;
        ListNode* pB = headB;
        while (pA != pB) {
    
    
            pA = pA ? pA->next : headB;
            pB = pB ? pB->next : headA;
        }
        return pA;
    }
};
  1. .

8. Fusion de listes chaînées

  1. Fusionner les nœuds entre les zéros
class Solution {
    
    
public:
    ListNode* mergeNodes(ListNode* head) {
    
    
        ListNode* dummyHead = new ListNode(-1);
        ListNode* p = dummyHead;
        int tmp = 0;
        head = head->next;
        while (head != nullptr) {
    
    
            if (head->val != 0) {
    
    
                tmp += head->val;
            } else {
    
    
                ListNode* node = new ListNode(tmp);
                p->next = node;
                p = p->next;
                tmp = 0;
            }
            head = head->next;
        }
        return dummyHead->next;
    }
};
  1. Fusionner deux listes chaînées
class Solution {
    
    
public:
    ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
    
    
        ListNode* dummyHead = new ListNode(-1, list1);
        // 找到a前驱节点
        ListNode* pre = dummyHead;
        for (int i = 0; i < a; ++i) {
    
    
            pre = pre->next;
        }
        //找到b后继结点
        ListNode* next = pre->next;
        for (int i = a; i <= b; ++i) {
    
    
            next = next->next;
        }
        // 合并
        pre->next = list2;
        while (list2->next) {
    
    
            list2 = list2->next;
        }
        list2->next = next;
        return dummyHead->next;
    }
};

9. Calcul des listes chaînées

1. Additionner deux nombres II

Vous recevez deux listes chaînées non vides pour représenter deux entiers non négatifs. Le chiffre le plus élevé du numéro se trouve au début de la liste chaînée. Chacun de leurs nœuds ne stocke qu'un seul chiffre. L'ajout de ces deux nombres renvoie une nouvelle liste chaînée.

class Solution {
    
    
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    
    
        l1 = reverseList(l1);
        l2 = reverseList(l2);
        ListNode* res = new ListNode(0);
        ListNode* p = res;
        int c = 0;
        while (l1 && l2) {
    
    
            ListNode* node = new ListNode((c + l1->val + l2->val) % 10);
            c = (c + l1->val + l2->val) / 10;
            p->next = node;
            p = p->next;
            l1 = l1->next;
            l2 = l2->next;               
        }
        p->next = l1 ? l1 : l2;
        while (l1) {
    
    
            int v = l1->val;
            l1->val = (v + c) % 10;
            c = (v + c) / 10;
            l1 = l1->next;
            p = p->next;
        }
        while (l2) {
    
    
            int v = l2->val;
            l2->val = (v + c) % 10;
            c = (v + c) / 10;
            l2 = l2->next;
            p = p->next;
        }
        if (c != 0) {
    
    
            ListNode* node = new ListNode(c);
            p->next = node;
            node->next = nullptr;
        }
        return reverseList(res->next);
    }

    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

3. Somme double maximale de la liste chaînée

  • Fractionnement de liste chaînée + inversion de liste chaînée
class Solution {
    
    
public:
    int pairSum(ListNode* head) {
    
    
        ListNode* mid = reverseList(split(head));
        int maxSum = 0;
        while (head && mid) {
    
    
            maxSum = max(maxSum, head->val + mid->val);
            head = head->next;
            mid = mid->next;
        }
        return maxSum;
    }

    ListNode* split(ListNode* head) {
    
    
        ListNode* fast = head->next;
        ListNode* slow = head;
        while (fast && fast->next) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr;
        return mid;
    }

    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
             return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

4. Convertir la liste chaînée binaire en entier

  • Méthode de conversion binaire :
    Insérer la description de l'image ici
  • Bien que la puissance de chaque chiffre multipliée par 2 soit liée au nombre de chiffres, il n'est pas nécessaire de connaître le nombre total de chiffres (la longueur de la liste chaînée). Il vous suffit de traiter le nœud actuel comme le chiffre le plus bas de chaque temps (c'est-à-dire la valeur actuelle du nœud multipliée par 0 sur 2).puissance), puis lors de la lecture de la valeur du nœud suivante, multipliez simplement le résultat déjà lu par 2 (le même résultat).
class Solution {
    
    
public:
    int getDecimalValue(ListNode* head) {
    
    
        int res = 0;
        int num = 0;
        while (head) {
    
    
            res = res * 2 + head->val;
            head = head->next;
        }
        return res;
    }
};

10. Diviser la liste chaînée

1. Divisez la liste chaînée

Étant donné le nœud principal d'une liste chaînée et une valeur spécifique x, veuillez séparer la liste chaînée afin que tous les nœuds inférieurs à x apparaissent avant les nœuds supérieurs ou égaux à x.

class Solution {
    
    
public:
    ListNode* partition(ListNode* head, int x) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return head;
        }
        ListNode* dummyHead = new ListNode(-1, head);
        ListNode* pre = dummyHead;
        ListNode* curr = dummyHead;
        while (curr && curr->next) {
    
    
            if (curr->next->val < x && pre != curr) {
    
    
                // 将curr->next插入到pre后面
                ListNode* next = curr->next->next;
                curr->next->next = pre->next;
                pre->next = curr->next;
                curr->next = next;
                // 更新节点
                pre = pre->next;
            } else {
    
    
                curr = curr->next;
            }
        }
        return dummyHead->next;
    }
};

2. Listes chaînées séparées

Étant donné une liste chaînée unique avec le nœud principal comme tête et un entier k, veuillez concevoir un algorithme pour diviser la liste chaînée en k parties consécutives. La longueur de chaque partie doit être aussi égale que possible : la longueur de deux parties ne doit pas différer de plus de 1. Cela peut entraîner la nullité de certaines parties. Les k parties doivent être disposées dans l'ordre dans lequel elles apparaissent dans la liste chaînée, et la longueur de la première partie doit être supérieure ou égale à la longueur de la partie suivante.
Renvoie un tableau composé des k parties ci-dessus.

  • Enfiler l'aiguille
class Solution {
    
    
public:
    ListNode* oddEvenList(ListNode* head) {
    
    
        ListNode* pre = head;
        ListNode* curr = head;
        ListNode* next;
        int num = 2;
        while (curr && curr->next) {
    
    
            if (num % 2 == 1) {
    
    
                // curr->next结点放到pre节点后
                next = curr->next->next;
                curr->next->next = pre->next;
                pre->next = curr->next;
                curr->next = next;
                // 更新节点
                curr = next;
                pre = pre->next;
                // curr移动到next,移动了两个结点
                num += 2; 
            } else {
    
    
                curr = curr->next;
                num += 1;
            }
        }
        return head;
    }
};

Liste chaînée et arbre binaire

1.BiNode

La structure de données d'arbre binaire TreeNode peut être utilisée pour représenter une liste chaînée unidirectionnelle (la gauche est laissée vide et la droite est le nœud de liste chaînée suivant). Pour implémenter une méthode de conversion d'un arbre de recherche binaire en liste chaînée unidirectionnelle, elle doit toujours respecter les propriétés de l'arbre de recherche binaire. L'opération de conversion doit être in-situ, c'est-à-dire directement modifiée sur la recherche binaire d'origine. arbre.
Renvoie le nœud principal de la liste chaînée unidirectionnelle convertie.

  • Les nœuds de l'arbre de recherche binaire, du plus petit au plus grand, peuvent être obtenus grâce à un parcours dans l'ordre.
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
public:
    TreeNode* convertBiNode(TreeNode* root) {
    
    
        inOrder(root);
        return head->right;
    }

    void inOrder(TreeNode* root) {
    
    
        if (root == nullptr) {
    
    
            return;
        }
        inOrder(root->left);
        pre->right = root;
        root->left = nullptr;
        pre = root;
        inOrder(root->right);
    }

private:
    TreeNode* head = new TreeNode(-1);
    TreeNode* pre = head;
};

2. Liste liée aux nœuds de profondeur spécifiques

Étant donné un arbre binaire, concevez un algorithme pour créer une liste chaînée contenant tous les nœuds à une certaine profondeur (par exemple, si la profondeur d'un arbre est D, D listes chaînées seront créées). Renvoie un tableau contenant des listes chaînées de toutes les profondeurs.

  • Utilisez des files d'attente pour le parcours par ordre de niveau.
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    vector<ListNode*> listOfDepth(TreeNode* tree) {
    
    
        if (tree == nullptr) {
    
    
            return {
    
    };
        }
        vector<ListNode*> res;
        res.push_back(new ListNode(tree->val));
        queue<TreeNode*> q;
        q.push(tree);
        while (!q.empty()) {
    
    
            ListNode* dummyHead = new ListNode(-1);
            ListNode* p = dummyHead;
            int len = q.size();
            while (len--) {
    
    
                auto top = q.front();
                q.pop();
                if (top->left) {
    
    
                    p->next = new ListNode(top->left->val);
                    p = p->next;
                    q.push(top->left);
                }
                if (top->right) {
    
    
                    p->next = new ListNode(top->right->val);
                    p = p->next;
                    q.push(top->right);
                }
            }
            p->next = nullptr;
            if (dummyHead->next) {
    
    
                res.push_back(dummyHead->next);
            }
        }
        return res;
    }
};

3. Liste chaînée dans l'arborescence binaire

  • Deux récursions. Un pour énumérer le point de départ et un pour déterminer si la liste chaînée peut correspondre à partir de ce point de départ.
Solution {
    
    
public:
    bool isSubPath(ListNode* head, TreeNode* root) {
    
    
        if (root == nullptr) {
    
    
            return false;
        }
        return dfs(head, root) || isSubPath(head, root->left) || isSubPath(head, root->right); // 枚举起点
    }

    bool dfs(ListNode* head, TreeNode* root) {
    
    
        if (head == nullptr) {
    
    
            return true;
        }
        if (root == nullptr) {
    
    
            return false;
        }
        if (head->val != root->val) {
    
    
            return false;
        }
        return dfs(head->next, root->left) || dfs(head->next, root->right);
    }
};

4. Remplissez le prochain pointeur de nœud droit de chaque nœud

Insérer la description de l'image ici

  • parcours dans l'ordre
class Solution {
    
    
public:
    Node* connect(Node* root) {
    
    
        if (root == nullptr) {
    
    
            return root;
        }
        queue<Node*> q;
        q.push(root);
        Node* dummyHead = new Node(-1);
        Node* p = dummyHead;
        while (!q.empty()) {
    
    
            p = dummyHead;
            int len = q.size();
            while (len--) {
    
    
                Node* top = q.front();
                q.pop();
                if (top->left) {
    
    
                    p->next = top->left;
                    p = p->next;
                    q.push(top->left);
                }
                if (top->right) {
    
    
                    p->next = top->right;
                    p = p->next;
                    q.push(top->right);
                }
            }
        }
        delete dummyHead;
        return root;
    }
};

5. L'arborescence binaire est développée en une liste chaînée

  • Utilisez le parcours de précommande. Étant donné que l'expansion de l'arbre binaire dans une liste chaînée détruira la structure de l'arbre binaire, l'ordre est enregistré pendant le parcours de pré-commande et les informations des pointeurs de nœuds gauche et droit sont mises à jour une fois le parcours terminé.
class Solution {
    
    
public:
    void flatten(TreeNode* root) {
    
    
        if (root == nullptr) {
    
    
            return;
        }
        vector<TreeNode*> orderList;
        preOrder(root, orderList);
        for (int i = 1; i < orderList.size(); ++i) {
    
    
            root->right = orderList[i];
            root->left = nullptr;
            root = root->right;
        }
    }

    // 先序遍历:根->左->右    
    void preOrder(TreeNode* root, vector<TreeNode*>& orderList) {
    
    
        if (root == nullptr) {
    
    
            return;
        }
        orderList.push_back(root);
        preOrder(root->left, orderList);
        preOrder(root->right, orderList);
    }
};

6. Convertir la liste chaînée ordonnée en arbre de recherche binaire équilibré

Résoudre les problèmes grâce aux listes chaînées

1. Disposez le tableau dans un ordre non décroissant - liste chaînée + pile monotone

Copie de la liste chaînée

1. Copie de listes chaînées complexes

  • Étant donné que la liste chaînée complexe contient un pointeur aléatoire, le nœud pointé par le pointeur aléatoire peut ne pas avoir été créé lors de la création du nœud actuel. Par conséquent, le mappage entre les anciens et les nouveaux nœuds de liste chaînée peut être construit mp, mp[newNode]=oldNode, mp[oldNode]=newNode, et la première fois que l'ancienne liste chaînée est parcourue pour construire le nouveau nœud (y compris val et le pointeur suivant) et le mappage ; la deuxième fois, le nouveau nœud lié liste est parcourue, le mp[mp[newNode]->random]pointeur aléatoire du nœud doit être obtenu par Pointer vers un autre nouveau nœud.
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
    
    
public:
    Node* copyRandomList(Node* head) {
    
    
        // 构建新旧链表节点之间的哈希映射
        unordered_map<Node*, Node*> mp;
        Node* newList = new Node(-1);
        Node* p = head;
        Node* q = newList;
        while (p) {
    
    
            // 建立新链表
            Node* node = new Node(p->val);
            q->next = node;
            q = q->next;
            // 构建映射
            mp[p] = node;
            mp[node] = p;
            p = p->next;
        }
        q->next = nullptr;
        q = newList->next;
        while (q) {
    
    
            q->random = mp[mp[q]->random];
            q = q->next;
        }
        return newList->next;
    }
};

Liste doublement chaînée

1. Aplatir la liste doublement chaînée à plusieurs niveaux

  • Traitement récursif
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;
};
*/

class Solution {
    
    
public:
    Node* flatten(Node* head) {
    
    
        if (head == nullptr) {
    
    
            return head;
        }
        dfs(head);
        return head;
    }

    Node* dfs(Node* head) {
    
    
        if (head->child == nullptr && head->next == nullptr) {
    
    
            return head;
        }
        if (head->child) {
    
    
            Node* next = head->next;
            head->next = head->child;
            head->child->prev = head;
            Node* tail = dfs(head->child);
            head->child = nullptr;
            tail->next = next;
            if (next) {
    
    
                next->prev = tail;
            }
        }
        return dfs(head->next);
    }
};

2. Arbre de recherche binaire et liste chaînée circulaire bidirectionnelle

  • parcours dans l'ordre
class Solution {
    
    
public:
    Node* treeToDoublyList(Node* root) {
    
    
        if (root == nullptr) {
    
    
            return root;
        }
        inOrder(root);
        pre->right = dummyHead->right;
        dummyHead->right->left = pre;
        return dummyHead->right;
    }

    // 中序遍历:左->根->右
    void inOrder(Node* root) {
    
    
        if (root == nullptr) {
    
    
            return;
        }
        inOrder(root->left);
        pre->right = root;
        root->left = pre;
        pre = root;
        inOrder(root->right);
    }

private:
    Node* dummyHead = new Node(-1);
    Node* pre = dummyHead;
};

Questions de conception de liste chaînée

1. Concevoir une collection de hachage

2.Cache LRU

Signification de la question : implémentez LRU : Least recent used, c'est-à-dire l'algorithme le moins récemment utilisé.

  • fonction get : si la clé du mot-clé existe dans le cache, renvoie la valeur du mot-clé, sinon renvoie -1
  • Fonction push : si le mot-clé clé existe déjà, modifiez sa valeur de données ; s'il n'existe pas, insérez l'ensemble clé-valeur dans le cache. Si une opération d'insertion entraîne un dépassement de la capacité du nombre de mots-clés, le mot-clé inutilisé le plus ancien doit être expulsé.
  • Les fonctions get et put doivent s'exécuter avec une complexité temporelle moyenne de O(1).

Idées de résolution de problèmes :

Puisque les opérations get et put sont conçues pour rechercher et insérer, les deux doivent répondre à la complexité temporelle de O(1), afin de pouvoir être transmises.Table de hachage + liste doublement chaînéeaccomplir.

  • Table de hachage - stocke le mappage entre les clés et les nœuds de liste chaînée. Réalisez une recherche rapide de la clé à la valeur, complexité O(1).
  • Liste doublement chaînée - stocke la séquence d'accès récente de chaque nœud. Le nœud enregistre les informations de clé et de valeur. L'ordre des nœuds est que les nœuds les plus récemment consultés sont devant, et le dernier nœud est le nœud qui n'a pas été accédé récemment et le plus longtemps. Implémentez l’insertion et la suppression rapides de nœuds.
  1. Pensée algorithmique
  • Chaque fois que vous obtenez, déterminez si la valeur clé existe dans la table de hachage
    • S'il n'existe pas, renvoie -1 ;
    • S'il existe, déplacez d'abord le nœud au début de la liste chaînée, puis accédez à la valeur du nœud par hachage.
  • Chaque fois que vous la placez, déterminez si la valeur clé existe dans la table de hachage.
    • S'il existe, déplacez le nœud au début de la liste chaînée et accédez au nœud via le hachage pour mettre à jour la valeur de value.
    • S'il n'existe pas, créez un nouveau nœud de clé, insérez-le au début de la file d'attente et établissez une relation de mappage de hachage. Si le nombre de nœuds dépasse la capacité après l'insertion du nœud, le dernier nœud de la liste chaînée doit être supprimé et sa relation de mappage de hachage est supprimée.
  1. Mise en œuvre
  • Définissez une liste doublement chaînée : les nœuds doivent contenir une clé, une valeur, un pointeur précédent et un pointeur suivant.
    (Il ne peut pas s'agir simplement d'une valeur, il doit également inclure une clé, car la clé est nécessaire lors de la suppression de la relation de mappage de hachage du dernier nœud)
  • Définissez le pointeur de tête de la liste doublement chaînée - head - pour faciliter les opérations d'insertion ; le pointeur de queue - tail - pour faciliter les opérations de suppression.
// 双向链表
struct DListNode {
    
    
    int key;
    int val;
    DListNode* prev;
    DListNode* next;
    DListNode() : key(0), val(0), prev(nullptr), next(nullptr) {
    
    }
    DListNode(int key_, int val_) : key(key_), val(val_), prev(nullptr), next(nullptr) {
    
    }
};

class LRUCache {
    
    
public:
    LRUCache(int capacity) {
    
    
        this->capacity = capacity;
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
    
    
        if (mp.count(key) == 0) {
    
    
            return -1;
        }
        // 将访问的该节点移动到头部
        removeToHead(mp[key]);
        return mp[key]->val;
    }
    
    void put(int key, int value) {
    
    
        if (!mp.count(key)) {
    
    
            // 新建节点插入到链表头部
            DListNode* node = insertToHead(key, value);
            mp[key] = node;          
            ++size;
            if (size > capacity) {
    
    
                // 移除最近最久未使用的结点
                node = removeLastNode();
                mp.erase(node->key);
                --size;
            }
        } else {
    
    
            mp[key]->val = value;
            removeToHead(mp[key]); // 因为访问了该结点,所以移动到链表头部
        }
    }

protected:
    // 插入节点到链表头部
    DListNode* insertToHead(int key, int val) {
    
    
        DListNode* node = new DListNode(key, val);
        node->next = head->next;
        head->next = node;
        node->prev = head;
        node->next->prev = node;
        return node;
    }

    // 逐出最近最久未使用的节点(即链表的最后一个节点)
    DListNode* removeLastNode() {
    
    
        DListNode* node = tail->prev;
        node->prev->next = tail;
        tail->prev = node->prev;
        return node;
    }

    // 将某结点移动到链表头部
    void removeToHead(DListNode* node) {
    
    
        // 将node前后结点进行连接
        node->prev->next = node->next;
        node->next->prev = node->prev;
        // 将node插入head之后
        node->next = head->next;
        head->next = node;
        node->prev = head;
        node->next->prev = node;
    }

private:
    int capacity {
    
    0};
    int size {
    
    0};
    unordered_map<int, DListNode*> mp;
    DListNode* head = new DListNode(); // 双向链表头结点
    DListNode* tail = new DListNode(); // 双向链表尾节点
};

De plus, l'impression prendra également du temps et il semble que vous ne puissiez pas jouer avec QaQ.
Insérer la description de l'image ici

3.Cache LFU

L'algorithme le moins fréquemment utilisé (LFU) et l'algorithme le plus récemment inutilisé (LRU) sont tous deux des algorithmes de remplacement de page de gestion de la mémoire. Leurs différences sont :

  • LRU, c'est-à-dire : algorithme d'élimination du moins récemment utilisé (Least Récemment utilisé). LRU élimine les pages qui n’ont pas été utilisées depuis le plus longtemps.
    La clé du LRU est d’examiner la durée écoulée entre la dernière utilisation des données et leur remplacement : plus la période est longue, plus les données seront remplacées ;
  • LFU, c'est-à-dire : algorithme d'élimination le moins fréquemment utilisé (Least Frequency Used). LFU consiste à éliminer la page la moins utilisée sur une période donnée.
    La clé de LFU est de regarder la fréquence (nombre de fois) à laquelle la page est utilisée au cours d'une certaine période de temps . Plus la fréquence d'utilisation est faible, les données seront remplacées.

Idées de résolution de problèmes :

  • Pour déterminer les clés les moins fréquemment utilisées, vous pouvez gérer uneUtiliser le compteurcnt. La clé avec le plus petit nombre d’utilisations est celle qui n’a pas été utilisée depuis le plus longtemps. Lorsqu'une clé est insérée pour la première fois dans le cache, son compteur d'utilisation est mis à 1 (en raison de l'opération put). Lorsqu'une opération get ou put est effectuée sur une clé dans le cache, le compteur d'utilisation sera incrémenté.

4. Concevoir le cache du navigateur

  • Mise en place de liste doublement chaînée. Le pointeur actuel pointe vers la page actuellement visitée. Chaque visite ajoute un point de base derrière la liste chaînée. En arrière, la liste chaînée est parcourue par le pointeur précédent. En avant, la liste chaînée est parcourue par le pointeur suivant.
struct DListNode {
    
    
    string url;
    DListNode* prev;
    DListNode* next;
    DListNode() : url(""), prev(nullptr), next(nullptr) {
    
    }
    DListNode(string url_) : url(url_), prev(nullptr), next(nullptr) {
    
    }
};

class BrowserHistory {
    
    
public:
    BrowserHistory(string homepage) {
    
    
        head->url = homepage;
    }
    
    void visit(string url) {
    
    
        DListNode* node = new DListNode(url);
        curr->next = node;
        node->prev = curr;
        curr = node;
    }
    
    string back(int steps) {
    
    
        while (curr->prev && steps--) {
    
    
            curr = curr->prev;
        }
        return curr->url;
    }
    
    string forward(int steps) {
    
    
        while (curr->next && steps--) {
    
    
            curr = curr->next;
        }
        return curr->url;
    }

private:
    DListNode* head = new DListNode();
    DListNode* curr = head;
};

5. Concevoir une file d'attente circulaire

  • Implémentation de listes chaînées. Définissez la tête du pointeur de tête et la queue du pointeur de queue, où ils sont les véritables nœuds de tête et de queue de la liste chaînée. Initialement, la tête et la queue sont laissées vides.
  • La file d'attente doit satisfaire à la condition du premier entré, premier sorti, de sorte que les nœuds sont insérés depuis la queue et supprimés depuis la tête.
  • Utilisez la taille et la capacité pour déterminer si la file d'attente est vide et pleine
class MyCircularQueue {
    
    
public:
    MyCircularQueue(int k) {
    
    
        capacity = k;
        head = tail = nullptr;
    }
    
    bool enQueue(int value) {
    
    
        if (isFull()) {
    
    
            return false;
        }
        ListNode* node = new ListNode(value);
        if (head) {
    
    
            // 从尾部插入
            tail->next = node;
            tail = node;
        } else {
    
    
            head = tail = node;
        }
        ++size;
        return true;
    }
    
    bool deQueue() {
    
    
        if (isEmpty()) {
    
    
            return false;
        }
        // 从头部删除
        ListNode* node = head;
        head = head->next;
        delete node;
        --size;
        return true;
    }
    
    int Front() {
    
    
        return isEmpty() ? -1 : head->val;
    }
    
    int Rear() {
    
    
        return isEmpty() ? -1 : tail->val;
    }
    
    bool isEmpty() {
    
    
        return size == 0;
    }
    
    bool isFull() {
    
    
        return size == capacity;
    }

private:
    int size {
    
    0};
    int capacity {
    
    0};
    ListNode* head;
    ListNode* tail;
};

6. Concevoir une file d'attente circulaire à double extrémité

  • Implémentation d'une liste doublement chaînée
struct DListNode {
    
    
    int val;
    DListNode* prev;
    DListNode* next;
    DListNode() : val(0), prev(nullptr), next(nullptr) {
    
    }
    DListNode(int val_) : val(val_), prev(nullptr), next(nullptr) {
    
    }
};

class MyCircularDeque {
    
    
public:
    MyCircularDeque(int k) {
    
    
        capacity = k;
        head->next = tail;
        tail->prev = head;
    }
    
    bool insertFront(int value) {
    
    
        if (size >= capacity) {
    
    
            return false;
        }
        // head前面添加node节点,head的值置为value,head前移一位
        DListNode* node = new DListNode();
        node->next = head;
        head->prev = node;
        head->val = value;
        head = head->prev;
        ++size;
        return true;
    }
    
    bool insertLast(int value) {
    
    
        if (size >= capacity) {
    
    
            return false;
        }
        // tail后添加node节点,tail的值置为value,tail后移一位
        DListNode* node = new DListNode();
        tail->next = node;
        node->prev = tail;
        tail->val = value;
        tail = tail->next;
        ++size;
        return true;
    }
    
    bool deleteFront() {
    
    
        if (size <= 0) {
    
    
            return false;
        }
        DListNode* node = head->next;
        head->next = node->next;
        node->next->prev = head;
        delete node;
        --size;
        return true;
    }
    
    bool deleteLast() {
    
    
        if (size <= 0) {
    
    
            return false;
        }
        DListNode* node = tail->prev;
        node->prev->next = tail;
        tail->prev = node->prev;
        delete node;
        --size;
        return true;
    }
    
    int getFront() {
    
    
        return size <= 0 ? -1 : head->next->val;
    }
    
    int getRear() {
    
    
        return size <= 0 ? -1 : tail->prev->val;
    }
    
    bool isEmpty() {
    
    
        return size == 0;
    }
    
    bool isFull() {
    
    
        return size == capacity;
    }

private:
    DListNode* head = new DListNode();
    DListNode* tail = new DListNode();
    int capacity {
    
    0};
    int size {
    
    0};
};

7. Concevoir des files d’attente avant, centrale et arrière

  • Implémentation d'une liste doublement chaînée
struct DListNode {
    
    
    int val;
    DListNode* prev;
    DListNode* next;
    DListNode() : val(0), prev(nullptr), next(nullptr) {
    
    }
    DListNode(int val_) : val(val_), prev(nullptr), next(nullptr) {
    
    }
};

class FrontMiddleBackQueue {
    
    
public:
    FrontMiddleBackQueue() {
    
    
        head->next = tail;
        tail->prev = head;
    }
    
    void pushFront(int val) {
    
    
        DListNode* node = new DListNode();
        node->next = head;
        head->prev = node;
        head->val = val;
        head = head->prev;
        ++size;
    }

    void pushMiddle(int val) {
    
    
        DListNode* mid = getMidNode();
        DListNode* node = new DListNode(val);
        if (size % 2 == 1) {
    
     // 期数个结点在mid前插入
            mid->prev->next = node;
            node->prev = mid->prev;
            node->next = mid;
            mid->prev = node;
        } else {
    
     // 偶数个结点在mid后插入
            node->next = mid->next;
            mid->next->prev = node;
            mid->next = node;
            node->prev = mid;
        }
        ++size;
    }
    
    void pushBack(int val) {
    
    
        DListNode* node = new DListNode();
        tail->next = node;
        node->prev = tail;
        tail->val = val;
        tail = tail->next;
        ++size;
    }
    
    int popFront() {
    
    
        if (size <= 0) {
    
    
            return -1;
        }
        int res = head->next->val;
        DListNode* node = head->next;
        head->next = node->next;
        node->next->prev = head;
        delete node;
        --size;
        return res;
    }

    int popMiddle() {
    
    
        if (size <= 0) {
    
    
            return -1;
        }
        DListNode* mid = getMidNode();
        mid->prev->next = mid->next;
        mid->next->prev = mid->prev;
        int res = mid->val;
        delete mid;
        --size;
        return res;
    }
    
    int popBack() {
    
    
        if (size <= 0) {
    
    
            return -1;
        }
        int res = tail->prev->val;
        DListNode* node = tail->prev;
        node->prev->next = tail;
        tail->prev = node->prev;
        delete node;
        --size;
        return res;
    }

protected:
    // 1 2 (3) 4 5
    // 1 2 (3) 4 5 6
    DListNode* getMidNode() {
    
    
        DListNode* p = head;
        for (int i = 1; i <= size / 2; ++i) {
    
    
            p = p->next;
        }
        if (size % 2 == 1) {
    
    
            p = p->next;
        }
        return p;
    }

private:
    int size {
    
    0};
    DListNode* head = new DListNode();
    DListNode* tail = new DListNode();
};

8. Concevoir une liste chaînée

  • Utilisez les pointeurs de tête et de queue comme nœuds de tête et de queue virtuels pour faciliter les opérations d'insertion aux deux extrémités.
class MyLinkedList {
    
    
public:
    MyLinkedList() {
    
    
        size = 0;
        head = new ListNode();
        tail = new ListNode();
        head->next = tail;
    }
    
    int get(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return -1;
        }
        ListNode* p = head;
        for (int i = 0; i <= index; ++i) {
    
    
            p = p->next;
        }
        return p->val;
    }
    
    void addAtHead(int val) {
    
    
        ListNode* node = new ListNode(val);
        node->next = head->next;
        head->next = node;
        ++size;
    }
    
    void addAtTail(int val) {
    
    
        tail->next = new ListNode();
        tail->val = val;
        tail = tail->next;
        ++size;
    }
    
    void addAtIndex(int index, int val) {
    
    
        if (index > size) {
    
    
            return;
        }
        // 找到index前一个节点
        ListNode* pre = head;
        for (int i = 0; i < index; ++i) {
    
    
            pre = pre->next;
        }
        // 插入新节点
        ListNode* node = new ListNode(val);
        node->next = pre->next;
        pre->next = node;
        ++size;
    }
    
    void deleteAtIndex(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return;
        }
        ListNode* pre = head;
        for (int i = 0; i < index; ++i) {
    
    
            pre = pre->next;
        }
        ListNode* node = pre->next;
        pre->next = node->next;
        delete node;
        --size;
    }

private:
    int size;
    ListNode* head;
    ListNode* tail;
};

9. Concevoir Twitter

  • Table de hachage + liste chaînée + file d'attente prioritaire
class Twitter {
    
    
public:
    Twitter() {
    
    
        curTime = 0;
        maxTweet = 10;
    }
    
    // 根据给定的 tweetId 和 userId 创建一条新推文
    void postTweet(int userId, int tweetId) {
    
    
        checkUser(userId);
        TweetNode* t = new TweetNode(tweetId, curTime++);
        t->next = userMap[userId]->tweets->next;
        userMap[userId]->tweets->next = t;
    }
    
    // 检索当前用户新闻推送中最近10条推文的ID,按时间由近到远 (必须是自己和关注人的)
    vector<int> getNewsFeed(int userId) {
    
    
        checkUser(userId);
        // 将userId自己以及所有关注的人的推特合并——使用优先队列(最小堆)
        priority_queue<TweetNode*, vector<TweetNode*>, decltype(&cmp)> heap(cmp);
        // 自己的博文放入堆中
        TweetNode* t = userMap[userId]->tweets->next;
        while (t) {
    
    
            heap.push(t);
            t = t->next;
        }
        // 关注的人的博文放入堆中
        for (auto id : userMap[userId]->followIds) {
    
    
            t = userMap[id]->tweets->next;
            while (t) {
    
    
                heap.push(t);
                t = t->next;
            }
        }
        // 取堆中前maxTweet条博文
        vector<int> res;
        int len = min(static_cast<int>(heap.size()), maxTweet);
        for (unsigned int i = 0; i < len; ++i) {
    
    
            res.push_back(heap.top()->tweetId);
            heap.pop();
        }
        return res;
    }
    
    // userId1 的用户开始关注ID为 userId2 的用户
    void follow(int userId1, int userId2) {
    
    
        checkUser(userId1);
        checkUser(userId2);
        userMap[userId1]->followIds.insert(userId2);
    }
    
    // userId1 的用户取消关注ID为 userId2 的用户
    void unfollow(int userId1, int userId2) {
    
    
        checkUser(userId1);
        checkUser(userId2);
        userMap[userId1]->followIds.erase(userId2);
    }

protected:
    struct TweetNode {
    
    
        int tweetId;
        int time;    // 发推时间
        TweetNode* next;
        TweetNode() : tweetId(-1), time(-1), next(nullptr) {
    
    }
        TweetNode(int t1, int t2) : tweetId(t1), time(t2), next(nullptr) {
    
    }
    };

    struct User {
    
    
        int userId;
        unordered_set<int> followIds; // 该用户关注的人
        TweetNode* tweets;            // 该用户发的推特
        User() : userId(-1) {
    
    
            tweets = new TweetNode();
        }

        User(int userId_) : userId(userId_) {
    
    
            tweets = new TweetNode();
        }
    };

    // 新推文排在前面
    static bool cmp(TweetNode* a, TweetNode*b) {
    
    
        return a->time < b->time;
    }

    // 当用户不存在时创建用户
    void checkUser(int userId) {
    
    
        if (userMap[userId] == nullptr) {
    
    
            userMap[userId] = new User(userId);
        }
    }

private:
    int curTime;  // 记录当前时间,值越大,推文越新
    int maxTweet; // 最多推送的推特个数
    unordered_map<int, User*> userMap; // 存储用户Id->用户信息
};

autre

1. Composant de liste chaînée

  • Les tables de hachage stockent des tableaux pour améliorer l'efficacité de la recherche.
class Solution {
    
    
public:
    int numComponents(ListNode* head, vector<int>& nums) {
    
    
        unordered_set<int> st(nums.begin(), nums.end());
        int exits = 0;
        int res = 0;
        while (head) {
    
    
            if (st.count(head->val)) {
    
    
                exits = 1;
            } else {
    
    
                res += exits;
                exits = 0;
            }
            head = head->next;
        }
        return exits ? res + 1 : res;
    }
};

[2. Trouver la distance minimale et maximale entre les points critiques]

class Solution {
    
    
public:
    vector<int> nodesBetweenCriticalPoints(ListNode* head) {
    
    
        if (!head || !head->next || !head->next->next) {
    
    
            return {
    
    -1, -1};
        }

        int minn = INT_MAX;
        int maxn = 0;
        int lastPos = -1;
        int pos = 2;
        int cnt = 0;
        while (head->next->next) {
    
    
            ListNode* curr = head->next;
            ListNode* next = curr->next;
            if ((curr->val > head->val && curr->val > next->val) || (curr->val < head->val && curr->val < next->val)) {
    
    
                minn = min(minn, lastPos == -1 ? INT_MAX : pos - lastPos);
                maxn += lastPos == -1 ? 0 : (pos - lastPos);
                lastPos = pos;
                ++cnt;
            } 
            head = head->next;
            ++pos;
        }
        if (cnt < 2) {
    
    
            return {
    
    -1, -1};
        }
        return {
    
    minn, maxn};
    }
};

Guess you like

Origin blog.csdn.net/weixin_36313227/article/details/131976612