字符组成的等式: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()函数,其实这个函数简单:
/***************************************************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
#include
#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;
}
http://blog.csdn.net/orbit/article/details/6529277