C++で1からnまでの数を辞書式順序で生成するアルゴリズム
整数 n が与えられたとき、1 から n までの数を辞書式順序(lexicographic order)で返す問題を考えます。例えば n = 13 が与えられた場合、出力は [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9] となります。
これは数値の大小順ではなく、数を文字列として比較したときの順序に従うためです。つまり「1」の次は「2」ではなく「10」が来ます。
アルゴリズムの考え方
この問題は、次の手順で O(n) の時間計算量で解くことができます。
- サイズ n の配列 ret を定義する
- 現在の数 curr を 1 で初期化する
- i を 0 から n - 1 まで繰り返す
- ret[i] に curr を代入する
- curr * 10 が n 以下であれば、curr を curr * 10 に更新する(桁を一つ深く進む)
- そうでない場合
- curr が n 以上であれば、curr を curr / 10 にする
- curr に 1 を加算する
- curr が 10 で割り切れる間、curr を curr / 10 に更新する(末尾の 0 を取り除く)
- 最後に ret を返す
ポイントは、辞書式順序では「1」の直後に「10」が来るように、現在の数に 10 を掛けた値が n 以下なら必ずそちらへ進むことです。それができない場合は次の数に移動し、余分な末尾の 0 を取り除くことで正しい順序を維持します。
C++の実装例
以下に実際の実装を示します。
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
class Solution {
public:
vector<int> lexicalOrder(int n) {
vector <int> ret(n);
int curr = 1;
for(int i = 0; i < n; i++){
ret[i] = curr;
if(curr * 10 <= n){
curr*= 10;
} else {
if(curr>= n)curr /= 10;
curr += 1;
while(curr % 10 == 0)curr/=10;
}
}
return ret;
}
};
int main(){
Solution ob;
print_vector(ob.lexicalOrder(20));
}入力
20
出力
[1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9]
計算量
各要素を一度だけ処理するため、時間計算量は O(n) です。出力用の配列を除けば、追加で必要なメモリは定数 O(1) となり、非常に効率的な実装です。
-
C++でデューデニー数(Dudeney Number)を判定する方法
デューデニー数とは? デューデニー数(Dudeney Number)とは、数論で定義される特殊な自然数の一つです。「ある自然数が、別の自然数の完全立方数に等しく、かつ元の数の各桁の数字和が、その立方根となる数の桁和と一致する」とき、その数をデューデニー数と呼びます(Wikipediaより)。 この数は、イギリスの著名なパズル作家であるヘンリー・デューデニー(Henry Dudeney)によって発見されました。数学的には次の式で表されます。 有名な例としては 512 = 8³ が挙げられます。512 の桁和は 5 + 1 + 2 = 8 となり、立方根である 8 と一致するため、512 はデ
-
C++で二分木内の最大BSTサブツリーを求める方法
二分木が与えられたとき、その中に含まれる「最大のBST(二分探索木)サブツリー」を見つけることを考えます。ここで「最大」とは、含まれるノードの数が最も多いサブツリーを指します。 例えば、次のような二分木が入力として与えられた場合を考えてみましょう。 この場合の出力は 3 となります。ハイライトされた部分が、ノード数最大のBSTサブツリーだからです。 解法のアプローチ この問題は、再帰的に各ノードの情報を収集することで効率的に解けます。具体的には、以下の手順に従います。 Data という構造体を定義します。この構造体には4つの値を持たせます。sz(サブツリーのノード数)、maxVal(最大