C++で連結リストをパーティション分割するアルゴリズム
問題の概要
連結リストと値 x が与えられたとき、リストを2つのグループに分割することを考えます。具体的には、「x 未満のノード」がすべて「x 以上のノード」よりも前に来るように並べ替えます。ただし、各グループ内ではノードの元の相対的な順序を保持しなければなりません。
例えば、リストが [1,4,3,2,5,2]、x = 3 の場合、出力は [1,2,2,4,3,5] となります。3未満のノード(1, 2, 2)が先頭に集まり、3以上のノード(4, 3, 5)がその後に続きます。
解法のアプローチ
この問題は、ダミーノードを2つ使うことでシンプルに解決できます。手順は以下の通りです。
- 初期値 -1 を持つダミーノード d1 と d2 を作成し、それぞれを指すポインタ dp1 と dp2 を用意します。
- 走査用のポインタ a が NULL でない間、以下を繰り返します。
- a の値が b(= x)未満の場合:dp1 の next に新しいノードを接続し、dp1 を前進させます。
- それ以外の場合:dp2 の next に新しいノードを接続し、dp2 を前進させます。
- a を次のノードへ進めます。
- ループ終了後、dp1 の next に d2 の next をつなぎ、2つのリストを連結します。
- d1 の next を結果として返します。
この方法なら、元の順序を保ちながら一度の走査で分割が完了します。計算量は時間・空間ともに O(n) です。
C++での実装例
以下の実装を見ると、処理の流れがより理解しやすくなります。
#include <bits/stdc++.h>
using namespace std;
class ListNode{
public:
int val;
ListNode *next;
ListNode(int data){
val = data;
next = NULL;
}
};
ListNode *make_list(vector<int> v){
ListNode *head = new ListNode(v[0]);
for(int i = 1; i<v.size(); i++){
ListNode *ptr = head;
while(ptr->next != NULL){
ptr = ptr->next;
}
ptr->next = new ListNode(v[i]);
}
return head;
}
void print_list(ListNode *head){
ListNode *ptr = head;
cout << "[";
while(ptr->next){
cout << ptr->val << ", ";
ptr = ptr->next;
}
cout << "]" << endl;
}
class Solution {
public:
ListNode* partition(ListNode* a, int b) {
ListNode* dummy1 = new ListNode(-1);
ListNode* dummy2 = new ListNode(-1);
ListNode* dummyPtr1 = dummy1;
ListNode* dummyPtr2 = dummy2;
while(a){
if(a->val < b){
dummyPtr1->next = new ListNode(a->val);
dummyPtr1 = dummyPtr1->next;
}
else{
dummyPtr2->next = new ListNode(a->val);
dummyPtr2 = dummyPtr2->next;
}
a = a->next;
}
dummyPtr1->next = dummy2->next;
return dummy1->next;
}
};
main(){
Solution ob;
vector<int> v = {1,4,6,3,2,5,2,8};
ListNode *head = make_list(v);
print_list(ob.partition(head, 3));
}入力
[1,4,6,3,2,5,2,8] 3
出力
[1, 2, 2, 4, 6, 3, 5, 8]
まとめ
ダミーノードを活用したこの手法は、条件分岐しながら2つの新しいリストを構築し、最後にそれらを連結するだけという非常に直感的なアプローチです。特別なソート処理を行うことなく、安定した順序でリストを分割できるため、面接や競技プログラミングでも頻出のテクニックとなっています。
-
C++で「次の大きい要素」を求める方法:スタックを使った効率的なアルゴリズム
「次の大きい要素(Next Greater Element)」とは、配列内のある要素に対して、その後ろに最初に現れるより大きい要素のことです。具体例を見てみましょう。 arr = [4, 5, 3, 2, 1] この場合、4 の次の大きい要素は 5 です。一方、3、2、1 については、後ろにより大きい要素が存在しないため、次の大きい要素は -1 となります。 アルゴリズム 配列をランダムな数値で初期化します。 スタックを初期化します。 配列の最初の要素をスタックにプッシュします。 配列の残りの要素を先頭から順に走査します。 スタックが空であれば、現在の要素をスタックにプッシュして次へ進みま
-
C++でランダムポインタを持つリンクリストをディープコピーする方法
ランダムポインタを持つリンクリストとはリンクリスト(連結リスト)は代表的な線形データ構造の一つで、各ノードは「ノードが保持する値(データ)」と「次のノードのアドレスを格納するポインタ(next)」という2つの部分で構成されます。本記事では、さらに各ノードがリスト内の別のノードを指す「ランダムポインタ(random)」を持つリンクリストを扱います。このようなリストに対して、元のリストと同じデータ・同じランダムポインタ構造を持つ新しいリストを作成することを、リンクリストの「ディープコピー(Deep Copy)」と呼びます。例入力:出力:5-> 2 -> 3 -> 7 ->4