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

C++で整数文字列に含まれる6の倍数となる部分文字列の個数を効率的に求める方法

本記事では、数字のみで構成された文字列が与えられたとき、その中に6で割り切れる部分文字列がいくつ含まれるかを求める問題を解説します。入力は数字の文字列として与えられますが、6で割り切れるかどうかの判定は、文字コード(ASCII値)ではなく、整数として扱って行う点に注意してください。

問題の例

入力:

str = "648"

出力:

3

説明:

部分文字列「6」「48」「648」が6で割り切れます。

入力:

str = "38342"

出力:

4

説明:

部分文字列「3834」「342」「834」「42」が6で割り切れます。

全探索(ブルートフォース)によるアプローチ

最もシンプルな方法は、取り得るすべての部分文字列を生成し、それぞれが6で割り切れるかどうかを順番に確認することです。割り切れる場合はカウントを1ずつ増やしていきます。この方法は実装が簡単ですが、すべての部分文字列を調べる必要があるため、時間計算量はO(n²)となり、文字列が長くなると処理に時間がかかります。

サンプルコード(C++)

#include <bits/stdc++.h>
using namespace std;
int
str_to_int (string str, int i, int j) {
   int temp = 0;
   for (; i <= j; i++) {
      temp = temp * 10 + (str[i] - '0');
   }
   return temp;
}
int main () {
   char str[] = "24661";
   int n = strlen (str);
   int count = 0;
   for (int i = 0; i < n; i++) {
      for (int j = i; j < n; j++) {
         int temp = str_to_int (str, i, j);
         if (temp % 6 == 0) count++;
      }
   }
   cout << count << endl;
   return 0;
}

出力

6

動的計画法を用いた効率的なアプローチ

ある数が6で割り切れるための条件は次の2つです。

  • その数の最後の桁(下一桁)が偶数であること(2で割り切れること)
  • その数の各桁の合計が3の倍数であること(3で割り切れること)

この性質を利用すると、動的計画法(DP)によって効率的に答えを求められます。

ここで、f(i, s) を「i番目のインデックスから始まる部分文字列のうち、桁の合計を3で割った余りがsとなるものの個数」と定義します。求めたい答えは、すべての開始位置iに対する Σf(i, 0) です。

文字列のi番目の桁を a とすると、f(i, s) からは「i + 1番目から始まる偶数の部分文字列」の情報が得られます。さらに、(a + s) が3で割り切れる場合、新たに1つの部分文字列が条件を満たします。したがって、次の漸化式が成り立ちます。

f(i, s) = f(i + 1, (s + a) % 3) + (a % 2 == 0 かつ (a + s) % 3 == 0)

サンプルコード(C++)

#include <bits/stdc++.h>
using namespace std;
int find(int i, int s, char str[], int dp[][3]){
   // 文字列の末尾に到達した場合
   if (i == strlen(str))
      return 0;
   // すでに計算済みの状態であれば結果を返す(メモ化)
   if (dp[i][s] != -1)
      return dp[i][s];
   int a = str[i] - '0';
   int ans = ((a+s)%3 == 0 && a%2 == 0) + find(i+1, (s+a)%3, str, dp);
   return dp[i][s] = ans;
}
int main(){
   char str[] = "24661";
   int n = strlen(str);
   // すべての状態を保存するためのDP配列
   int dp[n+1][3];
   memset(dp, -1, sizeof dp);
   int count = 0;
   for (int i = 0; i < n; i++){
      // 桁が0の場合は、それ自体が6の倍数となるためカウント
      if (str[i] == '0')
         count++;
      // 直前の合計の剰余(3で割った余り0)を再帰関数に渡す
      else
         count += find(i, 0, str, dp);
   }
   cout << "6で割り切れる部分文字列の個数: " << count << endl;
   return 0;
}

出力

6で割り切れる部分文字列の個数: 6

時間計算量: O(N)

全探索ではO(n²)かかっていた処理が、動的計画法とメモ化を組み合わせることで線形時間O(N)まで高速化できる点が、このアプローチの大きな利点です。

まとめ

本記事では、動的計画法を活用して、整数文字列に含まれる6の倍数となる部分文字列の個数を効率的に求める方法を学びました。紹介したロジックは、C言語、Java、Pythonなど他のプログラミング言語でも同様に実装できます。アルゴリズムの理解と実装力向上の参考にしていただければ幸いです。

  1. C++で8の倍数かつ3の倍数ではない部分文字列の個数を数える方法

    0〜9の数字だけで構成された文字列が与えられたとき、「8で割り切れるが、3では割り切れない」部分文字列がいくつあるかを求めるのが本記事のテーマです。一見複雑に思えますが、問題を「8の倍数の判定」と「3の倍数の除外」という2つのステップに分けて、一段階ずつコードを組み立てれば解決できます。 入出力例 入力: str = 80 出力: 2 この場合、8 と 80 の2つが条件を満たします。 入力: str = 7675636788 出力: 4 こちらは 8(2か所)、56、88 の合計4つが該当します。 解法のアプローチ まず押さえておくべき重要な性質は次の2つです。 整数が8で割り切れるかどう

  2. C++で文字列の部分文字列の総数を求める方法を解説

    この記事では、与えられた文字列から作成できる空でない部分文字列の個数を求める方法について解説します。入力 : string = "moon" 出力 : 10 説明 : 部分文字列は m、o、o、n、mo、oo、on、moo、oon、moon の 10 個です。 入力 : string = "yellow" 出力 : 21解法のアプローチ文字列の長さを n とします。上の例からも分かるように、考えられるすべての部分文字列の個数を求めるには、長さ n、(n-1)、(n-2)、(n-3)、……2、1 の部分文字列の個数を順に加算していく必要があります。部分文