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

C/C++で電話番号のダイヤルに使える文字列の全組み合わせを出力する方法

問題の概要

ある番号が与えられたとき、以下の仕様に基づいて、その番号を電話でダイヤルするために使用できる文字列のすべての組み合わせを表示するプログラムを考えます。

  • 2 →「A」「B」「C」のいずれか
  • 3 →「D」「E」「F」のいずれか
  • ……(以下同様)
  • 7 →「P」「Q」「R」「S」のいずれか
  • 8 →「T」「U」「V」のいずれか
  • 9 →「W」「X」「Y」「Z」のいずれか
  • 1 →「1」のみ
  • 0 →「0」のみ

例として、電話番号として「89」が与えられた場合、プログラムは次の12通りの文字列を出力します。

TW TX TY TZ UW UX UY UZ VW VX VY VZ

アルゴリズムの考え方

この問題は再帰を使って解くのが一般的です。各桁について対応する文字候補を順に選びながら、次の桁へと処理を進めていきます。最後の桁まで文字を選び終えた時点で1つの文字列が完成したことになるため、それを出力します。

  1. 各数字に対応する文字をハッシュテーブル(TableHash)にあらかじめ格納しておく。
  2. 現在の桁に対応する文字を1つ選び、出力用バッファに設定する。
  3. 次の桁に対して同じ処理を再帰的に繰り返す。
  4. すべての桁の処理が完了したら、完成した文字列を出力する。

C言語での実装例

#include <stdio.h>
#include <string.h>

/* TableHash[i] には、電話の数字 i に対応する文字が格納される */
const char TableHash[10][5] = {
    "0", "1", "ABC", "DEF", "GHI",
    "JKL", "MNO", "PQRS", "TUV", "WXYZ"
};

/* 再帰関数:入力された数字列 number1[](サイズ n1)から
   作成できるすべての単語を出力する。
   完成した単語は output1[] に1つずつ格納される */
void UtilWordsPrint(int number1[], int curr_digit1, char output1[], int n1)
{
    int i;

    /* ベースケース:出力すべき単語が完成した場合 */
    if (curr_digit1 == n1) {
        printf("%s ", output1);
        return;
    }

    /* 現在の桁に対して考えられるすべての文字を試し、
       残りの桁について再帰的に処理する */
    for (i = 0; i < strlen(TableHash[number1[curr_digit1]]); i++) {
        output1[curr_digit1] = TableHash[number1[curr_digit1]][i];
        UtilWordsPrint(number1, curr_digit1 + 1, output1, n1);
    }
}

/* UtilWordsPrint() のラッパー関数。
   出力用配列 result1 を作成し、UtilWordsPrint() を呼び出す */
void printWords(int number1[], int n1)
{
    char result1[n1 + 1];
    result1[n1] = '\0';
    UtilWordsPrint(number1, 0, result1, n1);
}

/* ドライバプログラム */
int main(void)
{
    int number1[] = {8, 9};
    int n1 = sizeof(number1) / sizeof(number1[0]);
    printWords(number1, n1);
    return 0;
}

出力結果

TW TX TY TZ UW UX UY UZ VW VX VY VZ

コードのポイント

  • TableHash は、数字ごとに割り当てられた文字を参照するためのテーブルです。配列のインデックスがそのままダイヤルの数字に対応します。
  • UtilWordsPrint() は再帰的に動作し、現在の桁を示す curr_digit1 が桁数 n1 に達した時点で1つの文字列が完成したと判断して出力します。
  • printWords() はヌル終端文字を含む出力用バッファを確保し、再帰関数への入り口となるラッパー関数です。
  • 「1」と「0」はそれぞれ自分自身にのみマッピングされるため、テーブル上では1文字だけを持つ扱いとなり、特別な分岐を設けなくても自然に処理されます。

計算量

このコードの時間計算量は O(4n) です。ここで n は入力番号の桁数を表します。これは、1つの数字に最大4文字(「7」と「9」の場合)が割り当てられるため、最悪の場合に各桁で4通りの選択肢が生じることに起因します。一方、空間計算量は再帰の深さと出力バッファのサイズに比例し、O(n) となります。

  1. C++で指定した合計値となるすべての組み合わせを求める方法

    正の整数 n が与えられたとき、その数の合計となるすべての正の数の組み合わせを求めることを考えます。ここで必要なのは「組み合わせ」であり、「順列」ではない点に注意してください。例えば n = 4 の場合、答えは [1, 1, 1, 1]、[1, 1, 2]、[1, 3]、[2, 2]、[4] の5通りになります。アプローチ:再帰を利用した解法この問題は再帰(リカージョン)を使うことで効率的に解くことができます。組み合わせを一時的に格納するための配列を用意し、再帰呼び出しを通じてその配列を順に埋めていきます。重複する順列を避けるため、各組み合わせの要素は必ず昇順に格納されるようにします。具体的に

  2. C++でオーバーロードできない関数のケースを徹底解説

    はじめにC++では、同じ名前でも引数の型や個数が異なる複数の関数を定義できる「関数オーバーロード」という強力な機能が用意されています。しかし、すべての場合でオーバーロードが成立するわけではなく、条件によってはコンパイルエラーになります。本記事では、C++において関数をオーバーロードできない代表的なケースを、具体的なコード例とともにわかりやすく解説します。1. 戻り値の型だけが異なる場合関数のシグネチャ(引数の型と個数)が完全に同一で、戻り値の型のみが異なる場合、オーバーロードすることはできません。戻り値の型はオーバーロード解決の判断材料にならないためです。int my_func() {&nbs