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 のアルゴリズムのチートシート