C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で接尾辞と一致する最長の接頭辞を見つけるプログラム

問題概要

文字列 s が与えられたとき、s 自身を除いた上で、同時に接尾辞(サフィックス)にもなる最長の接頭辞(プレフィックス)を見つけることを考えます。該当する接頭辞が存在しない場合は、空文字列を返します。

具体例

たとえば、入力が "madam" の場合、出力は "m" になります。自身を除く接頭辞は「m」「ma」「mad」「mada」の4つ、接尾辞は「m」「am」「dam」「adam」の4つ存在します。このうち接頭辞でもあり接尾辞でもある最大の文字列は「m」です。

解法のアプローチ

この問題は、KMP文字列検索アルゴリズムで使われる LPS配列を利用すると効率的に解けます。LPS配列の各要素には、「その位置までの部分文字列において、適切な接頭辞かつ接尾辞となる最長の長さ」が格納されます。したがって、文字列全体に対する答えは配列の最後の要素を参照するだけで求まり、計算量は O(n) と線形時間で済むのが大きな利点です。

LPS関数の構築手順

  • 関数 lps(s) を定義します。
  • n := 文字列 s の長さとします。
  • サイズ n の配列 ret を定義します。
  • j := 0、i := 1 で初期化します。
  • i < n の間、次を繰り返します。
    • s[i] == s[j] の場合:ret[i] := j + 1 とし、i と j をそれぞれ1増やします。
    • s[i] != s[j] の場合:j > 0 ならば j := ret[j - 1] とし、そうでなければ i を1増やします。
  • ret を返します。

メイン処理の手順

  • n := 文字列 s の長さとします。
  • n == 1 の場合は空文字列を返します。
  • v := lps(s) を計算します。
  • x := v[n - 1](配列の最後の要素=答えとなる長さ)とします。
  • 空文字列 ret を用意し、i を 0 から x 未満まで動かしながら ret := ret + s[i] を行います。
  • ret を返します。

それでは、実際の実装を見て理解を深めましょう。

C++実装例

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    vector<int> lps(string s){
        int n = s.size();
        vector<int> ret(n);
        int j = 0;
        int i = 1;
        while (i < n) {
            if (s[i] == s[j]) {
                ret[i] = j + 1;
                i++;
                j++;
            }
            else if (s[i] != s[j]) {
                if (j > 0)
                j = ret[j - 1];
                else {
                    i++;
                }
            }
        }
        return ret;
    }
    string longestPrefix(string s) {
        int n = s.size();
        if (n == 1)
        return "";
        vector<int> v = lps(s);
        int x = v[n - 1];
        string ret = "";
        for (int i = 0; i < x; i++) {
            ret += s[i];
        }
        return ret;
    }
};
main(){
    Solution ob;
    cout << (ob.longestPrefix("helloworldhello"));
}

入力

"helloworldhello"

出力

hello

この例では、「helloworldhello」の先頭と末尾に共通して現れる最長の部分文字列「hello」が出力されます。LPS配列を一度構築するだけで答えが得られるため、非常にシンプルかつ高速な解法といえます。

  1. C++で最長ビトニック部分列の長さを求めるプログラム

    数値のリストが与えられたとき、その中から「最長ビトニック部分列(バイトニックサブシーケンス)」の長さを求める問題を考えてみましょう。 ビトニック列とは、まず厳密に増加し、その後に厳密に減少するような数列のことです。なお、厳密に増加のみの数列や、厳密に減少のみの数列についても、ビトニック列として扱われます。 例えば、入力が nums = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15](要素数16)である場合、出力は 7 になります。 解法のアプローチ この問題は動的計画法(DP)を用いて効率的に解くことができます。基本的な手順は以下の

  2. C++でノード値の合計が最小となる二分木のレベルを求めるプログラム

    二分木(バイナリツリー)を考えます。根(ルート)のレベルを1とし、その子のレベルを2、さらにその下のレベルを3というように定義します。このとき、レベルXに存在するすべてのノードの値の合計が最小になるような、最も小さいレベルXを見つけるのが本記事の目的です。例として、次のような二分木を考えてみましょう。この場合、出力は 2 となります。なぜなら、レベル2のノードの値の合計は 4 + (-10) = -6 となり、これが全レベルの中で最小だからです。解法のアプローチこの問題は、幅優先探索(BFS)を使って各レベルごとにノードの値の合計を計算し、その中で最小となるレベルを記録していくことで解けます。