字符组成的等式: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 <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; }
http://blog.csdn.net/orbit/article/details/6529277