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

C++でエンコードされたメッセージをデコードする方法の数を求めるプログラム

問題の概要

エンコードされたメッセージとして、整数の数字からなる文字列が与えられたとします。これらの数字は、アルファベットの特定の文字にマッピングされます。具体的には、a は 1、b は 2、c は 3 というように対応しています。

さらに、メッセージにはワイルドカードとして文字「*」が含まれることがあり、これは 1 から 9 までの任意の数字にマッピングできるとします。メッセージ input が与えられたとき、そのメッセージをデコードできる方法が何通りあるかを求めるのがこの問題です。

具体例

例えば、入力が input = "18" の場合、出力は 2 になります。

このメッセージは「ah」とデコードできます。1 が「a」に、8 が「h」に対応するためです。また、18 という 2 桁の数は「r」に対応するため、「r」とデコードすることもできます。したがって、この入力をデコードする方法は合計 2 通りあります。

解法のアプローチ

この問題は動的計画法(DP)を使うことで効率的に解けます。各位置 i までの部分文字列をデコードする方法の数を配列に記録しながら、1 桁の数字として読む場合と 2 桁の数字として読む場合の組み合わせを順に累積していきます。ワイルドカード「*」が現れた場合は、取りうる値(1〜9)の数に応じて場合分けして計算します。

アルゴリズムの手順

  • n := 入力文字列の長さとする
  • サイズ n+1 の配列 dynArr を定義し、すべて 0 で初期化する
  • p := 1、k := '0'、dynArr[0] := 1 とする
  • i = 1 から n まで、i を 1 ずつ増やしながら以下を繰り返す
    • c := input[i - 1] とする
    • c が 0 であり、かつ k が '1'、'2'、'*' のいずれでもない場合は、デコード不可能なため p := 0 としてループを抜ける
    • input[i - 1] が '*' の場合
      • dynArr[i] := (dynArr[i - 1] * 9) mod m(単独で 1〜9 のいずれにもなり得るため)
      • k が '1' または '*' の場合、dynArr[i] := (dynArr[i] + dynArr[i - 2] * 9) mod m
      • k が '2' または '*' の場合、dynArr[i] := (dynArr[i] + (dynArr[i - 2] * 6) mod m) mod m
    • それ以外の場合
      • c が '0' でなければ、dynArr[i] := dynArr[i - 1]
      • k が '1' または '*' の場合、dynArr[i] := (dynArr[i] + dynArr[i - 2]) mod m
      • (k が '2' または '*')かつ input[i - 1] <= '6' の場合、dynArr[i] := (dynArr[i] + (dynArr[i - 2]) mod m) mod m
    • k := c と更新する
  • p が 0 でなければ dynArr[n] を返し、そうでなければ 0 を返す

なお、m = 10^9 + 7 は、答えが非常に大きくなるのを防ぐための剰余(モジュロ)です。

実装例(C++)

それでは、上記のアルゴリズムを C++ で実装した例を見てみましょう。

#include<bits/stdc++.h>

using namespace std;

const long m = 1e9 + 7;

int solve(string input) {
    int n = input.length();
    long long dynArr[n + 1] = {0};

    bool p = 1;
    char k = '0';

    dynArr[0] = 1;
    for (int i = 1; i <= n; i++) {
        char c = input[i - 1];
        if (c == 0 && !(k == '1' || k == '2' || k == '*')) {
            p = 0;
            break;
        }
        if (input[i - 1] == '*') {
            dynArr[i] = (dynArr[i - 1] * 9) % m;
            if (k == '1' || k == '*') dynArr[i] = (dynArr[i] + dynArr[i - 2] * 9) % m;
            if (k == '2' || k == '*') dynArr[i] = (dynArr[i] + (dynArr[i - 2] * 6) % m) % m;
        } else {
            if (c != '0') dynArr[i] = dynArr[i - 1];
            if (k == '1' || k == '*') dynArr[i] = (dynArr[i] + dynArr[i - 2]) % m;
            if ((k == '2' || k == '*') && input[i - 1] <= '6') dynArr[i] = (dynArr[i] + (dynArr[i - 2]) % m) % m;
        }
        k = c;
    }
    return p ? dynArr[n] : 0;
}

int main() {
   cout<< solve("18") <<endl;
   return 0;
}

入力

18

出力

2

まとめ

このプログラムでは、動的計画法を用いて、数字列とワイルドカード「*」を含むエンコード済みメッセージをデコードできるパターンの総数を O(n) の計算量で求めています。各桁を単独で読むか、直前の桁と合わせて 2 桁で読むかを場合分けすることで、「18」のような入力に対して正しく 2 通りという答えが得られます。デコード不可能な並び(例えば先行する '0' など)が検出された場合は 0 を返すようになっています。

  1. C++で配列からリンクリストを作成する方法を徹底解説

    このチュートリアルでは、C++を使って与えられた配列からリンクリスト(連結リスト)を作成する方法を学びます。配列の各要素を順番にノードとして連結していく、データ構造の学習において基本となる重要なテクニックです。解決の手順問題を解くための流れは以下の通りです。ダミーデータで配列を初期化するノード用の構造体(struct)を定義する配列を反復処理しながら次の操作を行う要素のデータを持つ新しいノードを作成する作成したノードをリンクリストに挿入する完成したリンクリストを出力するコード例それでは、実際のコードを見ていきましょう。#include <bits/stdc++.h> using n

  2. C++で二分木を剪定するプログラム:1を含まない部分木の削除方法

    問題の概要 ここでは、各ノードの値が「0」または「1」のいずれかである二分木を扱います。求めるのは、値「1」を一切含まないすべての部分木を削除した後の木です。たとえば、次のような木が与えられた場合を考えてみましょう。 剪定後の木では、子孫のどこにも「1」を持たない部分木が丸ごと取り除かれます。重要なのは、葉ノードだけでなく、内部ノードであっても、その下の部分木全体に1が存在しなければ削除対象になるという点です。 解決のためのアプローチ この問題は、再帰を用いることで簡潔に解けます。ノードを受け取る再帰メソッド solve()(実装例では pruneTree())を定義し、以下の手順で処理を進