-
C++で配列を毎日変換し、最終的に安定する配列を求めるアルゴリズム
初期配列 arr があるとします。毎日、前日の配列をもとに新しい配列を作成していきます。i 日目には、i-1 日目の配列に対して次の操作を施し、i 日目の配列を生成します。操作の条件は以下のとおりです。ある要素が、左隣と右隣の両方の値より小さい場合(谷)、その要素を 1 増やします。ある要素が、左隣と右隣の両方の値より大きい場合(山)、その要素を 1 減らします。先頭と末尾の要素は常に変更されません。この操作を何日か繰り返すと、配列はそれ以上変化しない安定状態に到達します。その最終的な配列を求めるのがこの問題です。たとえば、初期配列が [6,2,3,4] の場合、出力は [6,3,3,4] に
-
C++でエンコードされた文字列をデコードする方法
エンコードされた文字列を受け取り、それを復号した結果を返すプログラムを考えてみましょう。ここで使われるエンコードのルールは「k[encoded_string]」という形式です。これは、角括弧の中の文字列がちょうど k 回繰り返されることを表しています。また、元のデータには数字が一切含まれていないと仮定できるため、数字は必ず繰り返し回数 k を意味します。 たとえば、入力が 1[ba]2[na] であれば、「ba」が1回、「na」が2回繰り返されるため、出力は banana になります。 解法の考え方:スタックを活用する このような入れ子構造を持つ問題は、スタック(stack)を使うのが定番のア
-
C++で各文字が少なくともK回出現する最長部分文字列を求めるアルゴリズム
問題概要小文字の英字のみで構成される文字列 s が与えられます。このとき、すべての文字が k 回以上出現する最長の部分文字列 T の長さを求めるのが本問題の目的です。例として、文字列が ababbc、k = 2 の場合を考えてみましょう。このとき答えは 5 となり、最長の部分文字列は ababb です。なぜなら、この部分文字列には a が 2 回、b が 3 回含まれており、すべての文字が 2 回以上出現しているからです。解法のアプローチこの問題は分割統治法(再帰)を用いて解くことができます。基本的な発想は、「出現回数が k 回未満の文字は答えとなる部分文字列に絶対に含まれない」ため、そのよう
-
C++で身長を基準にキューを再構築するアルゴリズム
人々がランダムな順序で列に並んでいる状況を考えてみましょう。各人は整数のペア (h, k) で表されます。ここで、h はその人の身長、k は自分より前に並んでいる「身長が h 以上の人」の人数を意味します。この情報をもとに、条件を満たすようにキューを再構築するメソッドを定義するのが本記事の目的です。例えば、入力が [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] の場合、正しい出力は [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] となります。解法のアプローチこの問題は、「背の高い人から順に決まった位置へ挿入していく」と
-
C++で配列を等しい和の2つの部分集合に分割できるか判定する方法
問題概要正の整数のみを含む空でない配列が与えられたとき、その配列を要素の合計が等しい2つの部分集合に分割できるかどうかを判定します。例えば、入力が [1, 5, 11, 5] の場合、出力は true になります。これは、この配列を [1, 5, 5] と [11] の2つの部分集合に分けると、どちらも合計が 11 で等しくなるためです。解法のアプローチ(動的計画法)この問題は「部分和問題」の一種であり、動的計画法(DP)を用いて効率的に解くことができます。基本的な考え方は以下の通りです。配列全体の合計が奇数の場合、2つの等しい整数に分割することは不可能なので、即座に false を返します。
-
C++で英語表記の文字列から元の数字を復元するアルゴリズム
0〜9の数字を表す英単語(zero、one、two、three、four、five、six、seven、eight、nine)が、順番を入れ替えた状態で1つの文字列に連結されて与えられます。この文字列から元の数字を読み取り、昇順に出力するのが本記事のテーマです。この問題には次のような特徴があります。 入力は必ず有効であり、必ず元の数字列へと復元できることが保証されています。つまり「abc」や「zerone」のような無効な入力は考慮不要です。 入力の長さは50,000未満です。 たとえば、入力が「fviefuro」(five と four の並び替え)であれば、出力は「45」となります。
-
C++で解く最長の繰り返し文字置換(スライディングウィンドウ法)
問題の概要 大文字のアルファベットのみで構成された文字列 s が与えられ、この文字列に対して最大 k 回までの操作を行うことができます。1 回の操作では、文字列内の任意の 1 文字を選び、それを別の大文字アルファベットに変更します。操作を終えた後に得られる「すべて同じ文字で構成される部分文字列」の最長の長さを求めるのが、この問題の目的です。 たとえば、入力が ABAB で k = 2 の場合、出力は 4 になります。これは、2 つの B を A に置き換える(またはその逆)ことで、文字列全体を同一の文字で構成できるためです。 解法のアプローチ:スライディングウィンドウ この問題は、スライディン
-
C++で配列内の重複要素をすべて検出する方法
整数の配列を考えてみましょう。各要素は 1 ≤ a[i] ≤ n(n は配列のサイズ)の範囲にあり、一部の要素は2回出現し、それ以外の要素は1回だけ出現するとします。このとき、配列内に2回出現するすべての要素を見つける必要があります。たとえば、配列が [4,3,2,7,8,2,3,1] である場合、出力は [2, 3] となります。アルゴリズムの考え方この問題は「符号反転マーキング」というテクニックを使うことで、余分なメモリをほとんど使わずに解くことができます。要素の値が必ず 1 以上 n 以下であるという制約を利用し、各値をインデックスとして対応する位置の要素の符号を負にすることで、「その値
-
C++で文字列を文字の出現頻度順に並べ替える方法
文字列が与えられたとき、各文字を出現頻度に基づいて並べ替える問題を考えてみましょう。例えば、入力文字列が abbbacbcc の場合、出力は bbbbcccaa となります。これは、頻度の高い文字(bが4回、cが3回、aが2回)から順に並べているためです。 アルゴリズムの考え方 この問題は、以下の手順で解決できます。 頻度のカウント: マップ(連想配列)を使って、文字列内の各文字の出現回数を数えます。 (頻度, 文字) ペアの作成: マップの内容を、(出現回数, 文字) のペアとしてベクター(配列)に格納します。 ソート: ベクターを出現回数の降順にソートします。 結果の構築: ソート済みの
-
C++で解く「Target Sum(ターゲットサム)」問題:動的計画法による実装方法
負でない整数のリスト a1, a2, ..., an と、目標値 S が与えられているとします。ここで使える記号は「+」と「-」の2種類であり、リスト内の各整数に対してどちらか一方を選んで割り当てる必要があります。 求めるのは、記号を割り当てた結果として整数全体の合計が目標値 S と一致するような、割り当て方の総数です。 例として、数列が [1, 1, 1, 1, 1]、S = 3 の場合を考えてみましょう。このとき出力は 5 になります。条件を満たす組み合わせは以下の通りです。 - 1 + 1 + 1 + 1 + 1 = 3 + 1 - 1 + 1 + 1 + 1 = 3 + 1 + 1
-
C++で解く「次に大きい要素 II」:循環配列のNext Greater Element問題
問題概要循環配列(最後の要素の次は配列の最初の要素に戻る配列)が与えられたとき、各要素に対して「次に大きい数(Next Greater Number)」を求めて表示することを考えます。ある数 x の次に大きい数とは、走査順において x より後で最初に現れる、x より大きな値のことです。このとき配列は循環しているため、末尾を超えたら先頭に戻って探索を続けることができます。もし次に大きい数が存在しない場合は -1 を返します。例えば、入力が [1, 2, 1, 3, 2, 1] の場合、出力は [2, 3, 3, -1, 3, 2] となります。最初の「1」の次に大きい数は「2」「2」の次に大きい
-
C++で解く「コインチェンジ2」— 硬貨の組み合わせ数を動的計画法で求める方法
問題概要異なる額面の硬貨と合計金額が与えられたとき、その金額をちょうど作れる組み合わせの数を求めるプログラムを作成します。各硬貨は無限に使用できるものとします。例えば、金額が 5 で硬貨が [1, 2, 5] の場合、組み合わせは次の 4 通りになります。(1 + 1 + 1 + 1 + 1)(1 + 1 + 1 + 2)(1 + 2 + 2)(5)解き方(アルゴリズム)この問題は動的計画法(DP)を使って効率的に解けます。手順は以下の通りです。サイズが amount + 1 の配列 dp を作成するdp[0] := 1 と初期化する(金額 0 を作る方法は 1 通りだけ)n := coins
-
C++でソート済み配列から一度だけ現れる要素を見つける方法
整数のみで構成されたソート済み配列を考えます。この配列では、ある1つの要素を除いて、すべての要素がちょうど2回ずつ出現します。この「一度だけ現れる要素」を見つけるのが本記事の目的です。例えば、配列が [1, 1, 2, 3, 3, 4, 4, 8, 8] の場合、一度だけ現れる要素は 2 となるため、出力は 2 になります。解法アプローチ:XOR(排他的論理和)を活用この問題は、XOR の性質を利用することでシンプルに解けます。XOR には「同じ値同士を演算すると 0 になり、0 との演算では元の値が残る」という性質があります。そのため、ペアで出現する要素はすべて打ち消し合い、最後に残るのは一
-
C++で総和がKに等しい部分配列の個数を求める方法
整数型の配列 nums と整数 k が与えられたとき、要素の総和がちょうど k に等しくなる「連続する部分配列(サブアレイ)」の総数を求めることを考えます。たとえば nums = [1, 1, 1]、k = 2 の場合、条件を満たすのは [1, 1](インデックス 0〜1)と [1, 1](インデックス 1〜2)の 2 つなので、答えは 2 となります。 解法の考え方:累積和とハッシュマップ すべての部分配列を総当たりで調べる方法もありますが、O(n²) の計算量がかかり非効率です。そこで役立つのが「累積和(プレフィックスサム)」のテクニックです。ある位置で終わる部分配列の和が k になるのは
-
C++で文字列の順列(並べ替え)を判定する方法
2つの文字列 s1 と s2 が与えられたとき、s2 の中に s1 の順列(アナグラム)が部分文字列として含まれていれば true を返す関数を作成する問題を考えてみましょう。つまり、「最初の文字列の順列のいずれかが、2番目の文字列の部分文字列になっているか」を判定します。例えば、s1 = abc、s2 = findcab の場合、結果は true になります。なぜなら abc の順列である cab が s2 の中に存在するからです。解決のためのアルゴリズムこの問題は「スライディングウィンドウ」と「文字の出現回数カウント」を組み合わせることで効率的に解けます。手順は以下の通りです。サイズ26の
-
C++で2つの文字列を一致させるための最小削除操作数を求めるアルゴリズム
2つの単語 w1 と w2 が与えられたとき、各ステップでどちらかの文字列から1文字を削除できるものとして、w1 と w2 を同一にするまでに必要な最小ステップ数を求める問題を考えてみましょう。 例えば、入力が「sea」と「eat」の場合、出力は 2 になります。これは、w1 から「s」を削除して「ea」にし、さらに w2 である「eat」から「t」を削除することで、両方とも「ea」になり一致するからです。 解決のアプローチ この問題は動的計画法(DP)を用いて効率的に解くことができます。以下の手順に従います。 n := s1 の長さ、m := s2 の長さとします s1 と s2 の先頭に
-
【C++】二分探索でxに最も近いK個の要素を効率的に求める方法
問題概要ソート済みの整数配列と、2つの整数 k および x が与えられたとします。このとき、配列の中から x に最も近い k 個の要素を求めます。結果は昇順で返す必要があり、距離が等しい要素が存在する場合は、より小さい値の要素を優先します。例えば、入力が [1,2,3,4,5]、k = 4、x = 3 の場合、出力は [1,2,3,4] となります。アルゴリズムの考え方(二分探索)この問題は二分探索を活用することで、O(log(n − k) + k) の計算量で効率的に解くことができます。ポイントは、「答えとなる k 個の連続した部分配列の開始位置」を二分探索で絞り込むことです。候補となるウィ
-
C++で最長増加部分列の個数を求める方法
問題概要ソートされていない整数の配列が与えられたとき、「最長増加部分列(LIS: Longest Increasing Subsequence)」の個数を求める問題を考えます。例えば、入力が [1, 3, 5, 4, 7] の場合を考えてみましょう。このとき最長増加部分列は [1, 3, 5, 7] と [1, 3, 4, 7] の2通りが存在するため、出力は 2 となります。解法のアプローチこの問題は動的計画法(DP)を用いて効率的に解くことができます。ポイントは、各インデックスについて「その要素を末尾とする最長増加部分列の長さ」と「その長さとなる部分列の個数」の2つを同時に管理することです
-
C++でTop Kの頻出単語を抽出するアルゴリズムの解説
問題の概要空でない単語のリストが与えられたとき、出現頻度の高い上位k個の単語を見つける問題を考えてみましょう。答えは頻度の高い順(降順)にソートされている必要があります。また、2つの単語の頻度が同じ場合は、アルファベット順で先に来る単語を先に配置します。例えば、配列が [the, sky, is, blue, the, weather, is, comfortable] の場合、最も頻出する単語は [is, the, blue] となります。解決手順この問題を解くには、以下の手順に従います。マップ m を1つ定義する優先度付きキュー v を1つ作成するi を 0 から n(単語配列のサイズ)ま
-
C++で配列をK個の等しい合計値の部分集合に分割できるか判定する方法
問題概要 整数型の配列 nums と正整数 k が与えられます。この配列を、各部分集合の合計値がすべて等しくなるような k 個の空でない部分集合に分割できるかどうかを判定してください。 例えば、配列が [4,3,2,3,5,2,1]、k = 4 の場合を見てみましょう。この配列は [[5], [1,4], [2,3], [2,3]] のように4つの部分集合に分割でき、それぞれの合計はすべて 5 で等しくなります。したがって、答えは True となります。 解法のポイント:ビットマスクDP この問題は、ビットマスク動的計画法(bitmask DP)によって効率的に解くことができます。n 個の要