字符組成的等式:WWWDOT - GOOGLE = DOTCOM,
每個字符代表一個0-9之間的數字,WWWDOT、GOOGLE和DOTCOM都是合法的數字,不能以0開頭。
請找出一組字符和數字的對應關系,使它們互相替換,並且替換後的數字能夠滿足等式。這個字符等式是Google公司能力傾向測試實驗室的一道題目,這種題目主要考察人的邏輯推導能力和短期記憶能力,通常棋下的好的人解決這類問題會更得心應手一些(飛行棋例外)。
採用窮舉遞歸的算法實現:
1. 建立數學模型
要想讓計算機解決問題,就要讓計算機能夠理解題目,這就需要建立一個計算機能夠識別、處理的數學模型,首先要解決的問題就是建立字母和數字的映射關係的數學模型。本題的數學模型很簡單,就是一個字母二元組:{char, number}。考察等式:
WWWDOT - GOOGLE = DOTCOM
共出現了9個不同的字母:W、D、O、T、G、L、E、C和M,因此,最終的解應該是9個字母對應的字母二元組向量:[ {'W', 7}, {'D', 5}, {'O', 8}, {'T', 9}, {'G', 1}, {'L', 0}, {'E', 3}, {'C', 4}, {'M', 6} ]。窮舉算法就是對這個字母二元組向量中每個字母二元組的number元素進行窮舉,number的窮舉範圍就是0-9共10個數字,當然,根據題目要求,有一些字符不能爲0,比如W、G和D。排列組合問題的窮舉多使用多重循環,看樣子這個窮舉算法應該是9重循環了,在每層循環中對一個字母進行從0到9遍歷。問題是,必須這樣嗎,對於更通用的情況,不是9個字母的問題怎麼辦?首先思考一下是否每次都要遍歷0-9。題目要求每個字母代表一個數字,而且不重複,很顯然,對每個字母進行的並不是排列,而是某種形式的組合,舉個例子,就是如果W字母佔用了數字7,那麼其它字母就肯定不是7,所以對D字母遍歷是就可以跳過7。進一步,假設某次遍歷的字母二元組向量中除M字母外其它8個字母已經有對應的數字了,比如:
[ {'W', 7}, {'D', 5}, {'O', 8}, {'T', 9}, {'G', 1}, {'L', 0}, {'E', 3}, {'C', 4}, {'M', ?} ] (序列-1)
那麼M的可選範圍就只有2和6,顯然沒必要使用9重循環。
現在換一種想法,對9個二元組的向量進行遍歷,可以分解爲兩個步驟,首先確定第一個二元組的值,然後對剩下的8個二元組進行遍歷。顯然這是一種遞歸的思想(分治),算法很簡單,但是要對10個數字的使用情況進行標識,對剩下的二元組進行遍歷時只使用沒有佔用標識的數字。因此還需要一個標識數字佔用情況的數字二元組定義,這個二元組可以這樣定義:{number, using},0-9共有10個數字,因此需要維護一個長度爲10的數字二元組向量。數字二元組向量的初始值是:
[{0, false}, {1, false},{2, false},{3, false},{4, false},{5, false},{6, false},{7, false},{8, false},{9, false}]
每進行一重遞歸就有一個數字的using標誌被置爲true,當字母二元組向量得到(序列-1)的結果時,對應的數字二元組向量的值應該是:
[{0, true}, {1, true},{2, false},{3, true},{4, true},{5, true},{6, false},{7, true},{8, true},{9, true}]
此時遍歷這個數字二元組向量就可以知道M字母的可選值只能是2或6。
窮舉遍歷的結束條件是每層遞歸中遍歷完所有using標誌是false的數字,最外一層遍歷完所有using標誌是false的數字就結束了算法。
根據題目要求,開始位置的數字不能是0,也就是W、G和D這三個字母不能是0,這是一個“剪枝”條件,要利用起來,因此,對字母二元組進行擴充成字母三元組,添加一個leading標誌:{char, number, leading}。下面就是這個數學模型的C語言定義:
typedef struct _CharItem {
char c;
int value;
BOOL leading;
}CharItem;
typedef struct _CharValue {
int value;
BOOL used;
}CharValue;
CharItem arrayItem[MAX_CHAR_COUNT] = {{'W', -1, TRUE},
{'D', -1, TRUE}, { 'G', -1, TRUE}, { 'O', -1, FALSE}, { 'T', -1, FALSE},
{ 'L', -1, FALSE}, { 'E', -1, FALSE}, { 'C', -1, FALSE}, { 'M', -1, FALSE}};
CharValue arrayVal[MAX_VALUE_COUNT] = {{0, FALSE},
{ 1, FALSE}, { 2, FALSE}, { 3, FALSE}, { 4, FALSE},
{ 5, FALSE}, {6, FALSE}, { 7, FALSE}, { 8, FALSE}, { 9, FALSE}};
2. 窮舉算法
建立數學模型,其實就是爲了讓計算機理解題目並處理相關的數據,算法就是告訴計算機如何使用這些模型中的數據。本文介紹的是窮舉算法,算法的核心其實前面已經提到了,就是窮舉所有的字母和數字的組合,對每種組合進行合法性判斷,如果是合法的組合,就輸出結果。
整個算法的核心是calCharItem()函數,其實這個函數簡單:
/***************************************************函數名: isValueUsed
說明: 遞歸調用遍歷所有可能的情況
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
CharValue arrayVal 整數佔用列表
int index 已經完成配對的字符
輸出參數:無
返回值: 無
****************************************************/
void calCharItem(CharItem ci[MAX_CHAR_COUNT],
CharValue cv[MAX_VALUE_COUNT],
int index)
{
int i = 0;
if ( MAX_CHAR_COUNT == index )
{
//遞歸結束, 檢測當前組合是否滿足條件marchValue(ci);return;
}
for(i = 0;i < MAX_VALUE_COUNT; i++)
{
if ( !isValueUsed(ci[index], cv[i]) ){
cv[i].used = TRUE;//設置已用標誌ci[index].value = cv[i].value;calCharItem(ci, cv, index + 1);cv[i].used = FALSE;//回溯, 清楚標誌
}
}}
代碼
#include <stdio.h>
#include <string.h>
#define MAX_CHAR_COUNT 9
#define MAX_VALUE_COUNT 10
typedef unsigned char BOOL;
#define TRUE 1
#define FALSE 0
typedef struct _CharItem {
char c;
int value;
BOOL leading;
}CharItem;
typedef struct _CharValue {
int value;
BOOL used;
}CharValue;
/***************************************************
函數名: getCharValue
說明: 返回字符對應的整數值
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
char ch 需要查詢的字符
輸出參數:無
返回值: 字符對應的整數
****************************************************/
int getCharValue(CharItem arrayItem[], char ch)
{
int i = 0;
for ( i = 0; i < MAX_CHAR_COUNT; i++)
{
if ( arrayItem[i].c == ch)
{
return arrayItem[i].value;
}
}
return -1;
}
/***************************************************
函數名: calInt
說明: 計算字符串轉換後的整數值
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
char p[] 需要計算的字符串
輸出參數:無
返回值: 字符串轉換後的整數
****************************************************/
int calInt(CharItem arrayItem[], char p[])
{
int tmp = 0;
int strCnt = 0;
char *pTmp = NULL;
if ( (NULL == arrayItem) || (NULL == p))
{
return -1;
}
strCnt = strlen(p);
do
{
tmp = tmp * 10 + getCharValue(arrayItem, *p);
p++;
strCnt--;
} while ( 0 != strCnt);
return tmp;
}
/***************************************************
函數名: marchValue
說明: 判斷字符和整數的對應關係是否滿足條件
滿足條件輸出相應結果, 否則退出
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
輸出參數:無
返回值: 無
****************************************************/
void marchValue(CharItem arrayItem[])
{
//題目給定的求解字符串
char *pCh1 = "WWWDOT";
char *pCh2 = "GOOGLE";
char *pCh3 = "DOTCOM";
int num1 = calInt(arrayItem, pCh1);
int num2 = calInt(arrayItem, pCh2);
int num3 = calInt(arrayItem, pCh3);
if ( (num1 - num2) == num3 )
{
printf("%s - %s = %s\n", pCh1, pCh2, pCh3);
printf("%d - %d = %d\n", num1, num2, num3);
}
}
/***************************************************
函數名: isValueUsed
說明: 判斷當前數字是否被佔用
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
CharValue arrayVal 整數佔用列表
輸出參數:無
返回值: TRUE 被佔用不可以使用
FALSE 未被佔用可以使用
****************************************************/
BOOL isValueUsed(CharItem arrayItem, CharValue arrayVal)
{
if ( arrayVal.used )
{
return TRUE;
}
//首位字符不能爲0
if ( arrayItem.leading && (0 == arrayVal.value))
{
return TRUE;
}
return FALSE;
}
/***************************************************
函數名: isValueUsed
說明: 遞歸調用遍歷所有可能的情況
輸入參數:CharItem arrayItem[] 字符《--》整數對應表
CharValue arrayVal 整數佔用列表
int index 已經完成配對的字符
輸出參數:無
返回值: 無
****************************************************/
void calCharItem(CharItem ci[MAX_CHAR_COUNT],
CharValue cv[MAX_VALUE_COUNT],
int index)
{
int i = 0;
if ( MAX_CHAR_COUNT == index )
{
//遞歸結束, 檢測當前組合是否滿足條件
marchValue(ci);
return;
}
for(i = 0;i < MAX_VALUE_COUNT; i++)
{
if ( !isValueUsed(ci[index], cv[i]) )
{
cv[i].used = TRUE;//設置已用標誌
ci[index].value = cv[i].value;
calCharItem(ci, cv, index + 1);
cv[i].used = FALSE;//回溯, 清楚標誌
}
}
}
int main()
{
CharItem arrayItem[MAX_CHAR_COUNT] = {{'W', -1, TRUE},
{'D', -1, TRUE}, { 'G', -1, TRUE}, { 'O', -1, FALSE}, { 'T', -1, FALSE},
{ 'L', -1, FALSE}, { 'E', -1, FALSE}, { 'C', -1, FALSE}, { 'M', -1, FALSE}};
CharValue arrayVal[MAX_VALUE_COUNT] = {{0, FALSE},
{ 1, FALSE}, { 2, FALSE}, { 3, FALSE}, { 4, FALSE},
{ 5, FALSE}, {6, FALSE}, { 7, FALSE}, { 8, FALSE}, { 9, FALSE}};
calCharItem(arrayItem, arrayVal, 0);
return 0;
}