アルゴリズムの質問の復習: ダブル ポインター リンク リスト シリーズのコレクション

1. 2 つの順序付きリンク リストを結合する

 一連の考え

  • マージされたリンク リストを保存するために空を指す新しいリンク リストを作成し、p ポインターはリンク リストを指します。
  • ダブル ポインターを作成し、それぞれ p1 と p2 で表される 2 つのリンク リストを指します。
  • while ループは 2 つのポインターが指すデータのサイズを順番に判断し、最小値を p ポインターの現在値に代入します。最小値のポインタは次のノードを指し、p ポインタは次のノードを指します。p1 または p2 が null ポインターを指している場合、while ループを終了します。
  • p1 ポインタが空でないかどうかを確認し、p->next を p1->next にポイントします。
  • p2 ポインタが空でないかどうかを確認し、p->nex を p2->next にポイントします。

コード

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1), *p = dummy;
    ListNode* p1 = l1, *p2 = l2;
    
    while (p1 != NULL && p2 != NULL) {
        // 比较 p1 和 p2 两个指针
        // 将值较小的的节点接到 p 指针
        if (p1->val > p2->val) {
            p->next = p2;
            p2 = p2->next;
        } else {
            p->next = p1;
            p1 = p1->next;
        }
        // p 指针不断前进
        p = p->next;
    }
    
    if (p1 != NULL) {
        p->next = p1;
    }
    
    if (p2 != NULL) {
        p->next = p2;
    }
    
    return dummy->next;
}

2. 単一連結リストの分解

リンク リストのヘッド ノード head と特定の値 x を指定して、x 未満のすべてのノードが x ノードの前に配置され、x 以上のノードが x ノードの後に​​配置されるようにリンク リストを分割します。2 つのパーティション内の各ノードの相対位置は保持されます。

一連の考え

  • 2 つのリンク リストを作成し、それぞれ x 未満の数値と x 以上の数値を持つノードを格納します。ポインターは p1、p2 です。
  • p1 と p2 をマージするノードが必要です。

コード内の各 while ループは、最初に p->next を tmp の新しいリンク リストに割り当て、次に p->next ノードを nullptr に設定することに注意してください。これは、p ポインタが毎回 p1->next に直接割り当てられるためです。 p2→次へ。p1 と p2 のリンクされたリストがカオス p ノードを指すのを防ぐために、p->next ノードを nullptr に設定する必要があります。

 コード


ListNode* partition(ListNode* head, int x) {
    // 存放小于 x 的链表的虚拟头结点
    ListNode* dummy1 = new ListNode(-1);
    // 存放大于等于 x 的链表的虚拟头结点
    ListNode* dummy2 = new ListNode(-1);
    // p1, p2 指针负责生成结果链表
    ListNode* p1 = dummy1, * p2 = dummy2;
    // p 负责遍历原链表,类似合并两个有序链表的逻辑
    // 这里是将一个链表分解成两个链表
    ListNode* p = head;
    while (p != nullptr) {
        if (p->val >= x) {
            p2->next = p;
            p2 = p2->next;
        } else {
            p1->next = p;
            p1 = p1->next;
        }
        // 断开原链表中的每个节点的 next 指针
        ListNode* temp = p->next;
        p->next = nullptr;
        p = temp;
    }
    // 连接两个链表
    p1->next = dummy2->next;

    return dummy1->next;
}

3. k個の順序付きリンクリストをマージします

リンク リストの配列が与えられると、各リンク リストは昇順に並べ替えられます。すべてのリンク リストを 1 つの昇順のリンク リストに結合して返してください。

 一連の考え

  • 最小限のヒープを作成し、各リンク リストのヘッド ノードをヒープに格納します
  • while ループは、ヒープから現在の最小値が取得されるたびに判断し、ノードを結果のリンク リストに配置します。そして、最小値を取り出す連結リストの次のノードが空でない場合は、その次のノードを指し、それを最小ヒープに入れます。最小ヒープにリンク リストが存在しない場合は、while ループを終了します。

時間計算量: O(Nlogk)、k はリンク リストの数、N はこれらのリンク リスト内のノードの総数です。

コード

ListNode* mergeKLists(vector<ListNode*>& lists) {
    if (lists.empty()) return nullptr;
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1);
    ListNode* p = dummy;
    // 优先级队列,最小堆
    priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(
        [] (ListNode* a, ListNode* b) { return a->val > b->val; });
    // 将 k 个链表的头结点加入最小堆
    for (auto head : lists) {
        if (head != nullptr) {
            pq.push(head);
        }
    }

    while (!pq.empty()) {
        // 获取最小节点,接到结果链表中
        ListNode* node = pq.top();
        pq.pop();
        p->next = node;
        if (node->next != nullptr) {
            pq.push(node->next);
        }
        // p 指针不断前进
        p = p->next;
    }
    return dummy->next;
}

4. 単一リンクリストの下から k 番目のノードを見つけます。

基本バージョン: リンク リストを 2 回走査します。最初のパスではリンク リストの長さ n がわかり、2 番目のパスでは最後から 2 番目の k ノードが正の数 n-k+1 ノードになり、n-k+1 ノードが出力されます。

ダブルポインターのバージョン:

  • 最初のポインター p1 を参照し、k 個のノードに移動します。
  • 次に、2 番目のポインター p2 を作成します。p2 は先頭ポインターを指します。
  • p1 ポインタと p2 ポインタは同期して次へ進み、p1 ポインタが元のリンク リスト p の末尾の nullptr を指している場合、p2 はそのまま n-k+1 番目の位置に進みます。

 コード

// 返回链表的倒数第 k 个节点
ListNode* findFromEnd(ListNode* head, int k) {
    ListNode* p1 = head;
    // p1 先走 k 步
    for (int i = 0; i < k; i++) {
        p1 = p1 -> next;
    }
    ListNode* p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != nullptr) {
        p2 = p2 -> next;
        p1 = p1 -> next;
    }
    // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
    return p2;
}

展開するには、リンク リストの最後の n 番目のノードを削除する必要がある場合、上記のコードを直接使用して最後の n+1 番目のノード x を検索し、x->next ポインタを x->next-> にポイントします。次。

5. 単一リンクリストの中点を見つける

アイデア
同様に、リンク リストのヘッド ノードの先頭をそれぞれ指すように、遅いポインターと速いポインターを 2 つ作成します。

Slow は 1 ステップごとに、Fast は 2 ステップごとに進み、Fast が先頭のリンク リストの最後に到達すると、Slow はリンク リストの中間点になります。

注: リンクされたリストに 2 つの中間点がある場合は、後のノードが返されます。

コード

ListNode* middleNode(ListNode* head) {
    // 快慢指针初始化指向 head
    ListNode* slow = head;
    ListNode* fast = head;
    // 快指针走到末尾时停止
    while (fast != nullptr && fast->next != nullptr) {
        // 慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
    }
    // 慢指针指向中点
    return slow;
}

6. リンクリストにリングが含まれているかどうか、およびリングの開始点を決定します。

一連の考え

または、高速ポインタと低速ポインタを介して。遅いポインタは 1 ステップ進み、速いポインタは 2 ステップ進みます。

高速ポインタが最終的にヌル ポインタに遭遇した場合、それはリンク リストにリングが存在しないことを意味し、高速ポインタが最終的に低速に遭遇した場合、高速がスローを 1 円分超えていることを意味し、リンク リストにリングがあることを示します。

bool hasCycle(ListNode* head) {
    // 初始化快慢指针,指向头结点
    ListNode* slow = head;
    ListNode* fast = head;
    // 快指针到尾部时停止
    while (fast && fast->next) {
        // 慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
        // 快慢指针相遇,说明含有环
        if (slow == fast) {
            return true;
        }
    }
    // 不包含环
    return false;
}

リングの開始点の判断: 高速ポインタと低速ポインタが出会ったときに、低速ノードをヘッド ノードに再ポイントするだけで済みます。その後、低速ポインタと高速ポインタが同期して前進を開始します。再び出会ったとき、それがリングの開始ノードになります。指輪。

以下の図に示すように、最初の遭遇では、遅い場合は k ステップかかり、速い場合は 2 k ステップかかり、リングの長さは k です。出会いノードとリングの開始点の間の距離を n とすると、次のようになります。先頭ノードからスロー位置までの距離をk、先頭ノードからリング開始点beginまでの距離をknとする。リングの長さからリングの開始点からスローまでの距離を引いた値も -n です。そこで、初めて出会った場所から、再びslowをヘッドノードに向けて、fastとslowを同期して進めていき、再び出会ったときにリングの始点ノードとなります。

コード

ListNode* detectCycle(ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast != nullptr && fast->next != nullptr) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    if (fast == nullptr || fast->next == nullptr) {
        // fast 遇到空指针说明没有环
        return nullptr;
    }

    // 重新指向头结点
    slow = head;
    // 快慢指针同步前进,相交点就是环起点
    while (slow != fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

7. 2 つのリンクされたリストが交差するかどうかを判断します

下図のように、連結リストAがa1->a2->c1->c2、連結リストBがb1->b2->b3->c1->c2である場合、AとBが交差するかどうかを判断する方法?

 一連の考え

問題は、2 つのリンク リストの長さが異なる可能性があるため、2 つのリンク リスト間のノードが対応できないことです。したがって、問題を解決する鍵は、p1 と p2 がそれぞれ A と B を特定の方法でポイントし、同時にノード c1 に到達できるようにすることです。

p1 リンク リストを走査した A 後にリンク リストの走査を開始すること Bも、 リンクp2 リストを走査し B た後にリンク リストの走査を開始することも できます A。これは、2 つのリンク リストを「論理的に」接続することと同等です。

この方法でスプライシングが実行され、 同時に共通部分p1 に 入ることができる場合p2 、つまり、同時に交差ノードに到達する場合 c1:

 コード

// 求链表的交点
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    // p1 指向 A 链表头结点,p2 指向 B 链表头结点
    ListNode *p1 = headA, *p2 = headB;
    while (p1 != p2) {
        // p1 走一步,如果走到 A 链表末尾,转到 B 链表
        if (p1 == nullptr) p1 = headB;
        else               p1 = p1->next;
        // p2 走一步,如果走到 B 链表末尾,转到 A 链表
        if (p2 == nullptr) p2 = headA;
        else               p2 = p2->next;
    }
    return p1; // 返回交点
}

別の考え方としては、まず A と B のリンク リストの長さを取得し、その後、長いほうが最初に n ステップを実行して、同時に最後のノードに到達するというものです。次に、A と B は同期して前進し、同時にプログラムを終了して true を返すか、最後に同時に終了して false を返します。

コード

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    int lenA = 0, lenB = 0;
    // 计算两条链表的长度
    for (ListNode *p1 = headA; p1 != nullptr; p1 = p1->next) {
        lenA++;
    }
    for (ListNode *p2 = headB; p2 != nullptr; p2 = p2->next) {
        lenB++;
    }
    // 让 p1 和 p2 到达尾部的距离相同
    ListNode *p1 = headA, *p2 = headB;
    if (lenA > lenB) {
        // p1 先走过 lenA-lenB 步
        for (int i = 0; i < lenA - lenB; i++) {
            p1 = p1->next;
        }
    } else {
        // p2 先走过 lenB-lenA 步
        for (int i = 0; i < lenB - lenA; i++) {
            p2 = p2->next;
        }
    }
    // 看两个指针是否会相同,p1 == p2 时有两种情况:
    // 1、要么是两条链表不相交,他俩同时走到尾部空指针
    // 2、要么是两条链表相交,他俩走到两条链表的相交点
    while (p1 != p2) {
        p1 = p1->next;
        p2 = p2->next;  
    }
    return p1;
}

参考:ツーポインター技術により 7 つのリンクされたリストが削除される トピック:: Labuladong のアルゴリズムのチートシート

おすすめ

転載: blog.csdn.net/u010420283/article/details/130000992