算法系列(1):Google方程式
有一个字符组成的等式:WWWDOT – GOOGLE = DOTCOM,每个字符代表一个0-9之间的数字,WWWDOT、GOOGLE和DOTCOM都是合法的数字,不能以0开头。请找出一组字符和数字的对应关系,使它们互相替换,并且替换后的数字能够满足等式。这个字符等式是Google公司能力倾向测试实验室的一道题目,这种题目主要考察人的逻辑推导能力和短期记忆能力,通常棋下的好的人解决这类问题会更得心应手一些(飞行棋例外)。此类型的题目有很多变种,各种编程比赛中常常能见到它们的身影。比如2005年的GOOGLE中国编程挑战赛第二轮淘汰赛有一道名为“SecretSum”的500分的竞赛题,与本题如出一辙,只不过字母都是三个,而且用的是加法计算。现在言归正传,先看看如何分析这个问题。
以人的思维方式分析问题
将横式改成竖式可能更直观一些:
根据以上竖式减法,从左向右依次可以得到6个算式,分别是:
W – G = D (算式 1)
W – O = O (算式 2)
W – O = T (算式 3)
D – G = C (算式 4)
O – L = O (算式 5)
T – E = M (算式 6)
根据以上6个算式可以分析出两个关键信息:一个是W要足够大,因为考虑到它可能被借位的情况还要等于G和D的和;另一个则是本问题的突破口,就是算式2和算式3两次出现的W – O计算。现在分析算式2和算式3,根据是否需要借位,算式2和算式3一共有四种借位组合结果,下面分别对这四种借位组合结果进行分析。
1. W – O = T不需要借位,W – O = O也不需要借位
由于W – O = T和W – O = O都不需要借位,则可由算式2变形得到算式1.1:
W = 2O (算式1.1)
将算式1.1带入算式3,又可以得到算式1.2:
O = T (算式 1.2)
根据算式1.2,O和T代表的数字是同一个数字,这与题目要求不符,因此,这种借位组合不能得到正确的结果。
2. W – O = T需要借位,W – O = O不需要借位
根据借位情况,对算式2和算式3进行借位修正,得到两个修正算式:
W – 1 – O = O (算式2.1)
W + 10 – O = T (算式2.2)
由算式2.1变形得到算式2.3:
W = 2O + 1 (算式2.3)
将算式2.3带入算式2.2可以得到算式2.4:
T = O + 11 (算式2.4)
对算式2.3分析,由于W是个位数,最大值是9,所以O的取值只能是1-4,但是无论如何,由算式2.4计算出的T都超过9,这与题目要求不符,因此,这种情况也是无解的情况。
3. W – O = T不需要借位,W – O = O需要借位
根据借位情况,对算式2和算式3进行借位修正,得到两个修正算式:
W + 10 – O = O (算式3.1)
W – O = T (算式3.2)
由算式3.1变形得到算式3.3:
W = 2O – 10 (算式3.3)
将算式3.3带入算式3.2由可得到算式3.4:
O – 10 = T (算式3.4)
O显然是不能比10大的个位数,因此,这种情况也是无解的情况
4. W – O = T需要借位,W – O = O也需要借位
根据借位情况,对算式2和算式3进行借位修正,得到两个修正算式:
W – 1 + 10 – O = O (算式4.1)
W + 10 – O = T (算式4.2)
由算式4.1变形得到算式4.3:
W = 2O – 9 (算式4.3)
将算式4.3代入算式4.2得到算式4.4:
O + 1 = T (算式4.4)
由于W不能小于0,因此,根据算式4.3,O的取值最小为5。根据算式4.4继续分析,因为T不能大于9,因此O的最大值只能取值为8。根据O的取值区间[5,8],可依次计算出W和T的值,如下表所示:
已知O、W、T的取值,可以进一步推算其他字符代表的数字,上表中得到了四组目前合法的取值,但是并不是四组取值都能最终推算出正确的结果,本题的答案只有一个,也就是说只有一组O、W、T的取值是正确的,下面就分别进行分析。
O = 5, W = 1, T = 7
在这种情况下,考察算式1:W – G = D,W = 1显然无法满足此种情况,更何况算式2: W – O = O还要从它这里借位,因此,这种情况无解。
O = 6, W = 3, T = 7
在这种情况下,算式2: W – O = O还要从它这里借位,因此算式1:W – G = D对应的实际情况是2 – G = D,G和D不能同时为1,而且G和D都是第一位数字,不能是0,因此无法满足算式1,这种情况也是无解。
O = 7, W = 5, T = 8
在这种情况下,需要考察另另外两个关键算式,分别是算式5和算式6。根据这两个算式是否需要借位进行不同的假设,根据组合,仍然有四种假设,下面分别分析这四种假设:
假设一:算式5需要借位,算式6不需要借位。则此时算式5可修正为O + 10 – L = O,推算出L = 10,显然不符合题意,假设一不成立;
假设二:算式5需要借位,算式6需要借位,则算式5和算式6应该修正为算式4.3.1和算式4.3.2:
O -1 + 10 – L = O (算式4.3.1)
T + 10 – E = M (算式4.3.2)
因为已知T=8,带入4.3.2可得E+M=18,显然对于两个不相同的个位数无法满足这个等式,因此假设二也不成立;
假设三:算式5不需要借位,算式6不需要借位,此时根据算式6可知E和M的和是8(T=8),排除E=M=4的情况后,E和M的组合可以是(1,7)、(2,6)和(3,5),又因为数字5和7分别被W和O使用,因此E和M只能是2或6。再回头来看算式1,因为算式2需要借位,算式1实际相当于G + D = 4,G和D只能取值1和3,若G=1,D=3,则根据算式4计算出C=2,这与E或M矛盾。若G=3,D=1,则算式4需要借位,这又与算式3的假设矛盾。由此看来,假设三也不能得到正确的结果;
假设四:算式5不需要借位,算式6需要借位,此时根据算式5被修正为O – 1 – L = O,这种情况下也是无解的。
O = 8, W = 7, T = 9
在这种情况下,根据算式5和算式6是否借位的假设,仍然有四种假设,下面分别分析这四种假设:
假设一:算式5需要借位,算式6不需要借位。则此时算式5可修正为O + 10 – L = O,推算出L = 10,显然不符合题意,假设一不成立;
假设二:算式5需要借位,算式6需要借位,则算式5和算式6应该修正为算式4.4.1和算式4.4.2:
O -1 + 10 – L = O (算式4.4.1)
T + 10 – E = M (算式4.4.2)
因为已知T=9,带入4.4.2可得E+M=19,两个不同的个位数的和不可能大于18,因此假设二也不成立;
假设三:算式5不需要借位,算式6不需要借位,此时根据算式6可知E和M的和是9(T=9),E和M的组合可以是(1,8)、(2,7) 、(4,5)和(3,6),又因为数字8和7分别被O和W使用,因此E和M只能是(4,5)和(3,6)。进一步假设E=4,M=5(反过来E=5,M=4是一样的,不影响分析)。再看算式1,因为算式2需要借位,算式1实际相当于G + D = 6,由于M或E是5,所以G和D只能取值2和4。若G=2,D=4,则根据算式4计算出C=2,这与G=2矛盾。若G=4,D=2,则算式4需要借位,这又与算式3的假设矛盾,因此E=4,M=5的情况无解。再次进一步假设E=3,M=6(反过来E=6,M=3是一样的,不影响分析)。同样再看算式1,G和D的值可取是(2,4)和(1,5),G和D取值1和2的情况刚刚分析过无解,因此G和D的取值只能是1和5,前面分析过,算式4没有借位,也就是说要保证D > G,因此,D=5,G=1,根据算式4计算出C=4,这样就得到了一组解:O = 8,W = 7,T = 9,D = 5,L = 0, G = 1,C = 4,E = 3/6,M = 6/3。最终的等式是:
777589 – 188103 = 589486
或
777589 – 188106 = 589483
假设四:算式5不需要借位,算式6需要借位,此时根据算式5被修正为O – 1 – L = O,这种情况下也是无解的。
完整的分析过程结束,得到了一组答案,事实上通过计算机穷举算法也只能得到这一组结果,下面就看看如何用计算机算法求解本题的答案
用计算机穷举所有的解
以上是用人的思维方式的解题过程,如果方法正确,加上运气好(三次假设都是正确的,避免在错误分支上浪费时间),两分钟内就可得到结果。但是考虑到更通用的情况,字母数字没有规律,也没有可供分析的入手点和线索,比如:
AAB – BBC = CCD
这样的问题,该什么方法解决呢?只能“猜想”,用穷举的方法试探每一种猜想,对每个字母和数字穷举所有可能的组合,直到得到正确的结果。当然,这样的力气活交给计算机做是最合适不过了。
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}] (序列-2)
每进行一重递归就有一个数字的using标志被置为true,当字母二元组向量得到(序列-1)的结果时,对应的数字二元组向量的值应该是:
[{0, true}, {1, true},{2, false},{3, true},{4, true},{5, true},{6, false},{7, true},{8, true},{9, true}] (序列-3)
此时遍历这个数字二元组向量就可以知道M字母的可选值只能是2或6。
穷举遍历的结束条件是每层递归中遍历完所有using标志是false的数字,最外一层遍历完所有using标志是false的数字就结束了算法。
根据题目要求,开始位置的数字不能是0,也就是W、G和D这三个字母不能是0,这是一个“剪枝”条件,要利用起来,因此,对字母二元组进行扩充成字母三元组,添加一个leading标志:{char, number, leading}。下面就是这个数学模型的C语言定义:
typedef struct
{
char c;
int value;
bool leading;
}CharItem;
typedef struct
{
bool used;
int value;
}CharValue;
根据此数学模型初始化字母三元组和数字二元组向量:
CharItem char_item[max_char_count] =
{
{ 'W', -1, true }, { 'D', -1, true }, { 'O', -1, false },
{ 'T', -1, false }, { 'G', -1, true }, { 'L', -1, false },
{ 'E', -1, false }, { 'C', -1, false }, { 'M', -1, false }
};
CharValue char_val[max_number_count] =
{
{false, 0}, {false, 1}, {false, 2}, {false, 3},
{false, 4}, {false, 5}, {false, 6}, {false, 7},
{false, 8}, {false, 9}
};
2. 穷举算法
建立数学模型,其实就是为了让计算机理解题目并处理相关的数据,算法就是告诉计算机如何使用这些模型中的数据。本文介绍的是穷举算法,算法的核心其实前面已经提到了,就是穷举所有的字母和数字的组合,对每种组合进行合法性判断,如果是合法的组合,就输出结果。
整个算法的核心是SearchingResult()函数,其实这个函数非常简单:
void SearchingResult(CharItem ci[max_char_count],
CharValue cv[max_number_count],
int index, CharListReadyFuncPtr callback)
{
if(index == max_char_count)
{
callback(ci);
return;
}
for(int i = 0; i < max_number_count; ++i)
{
if(IsValueValid(ci[index], cv[i]))
{
cv[i].used = true;/*set used sign*/
ci[index].value = cv[i].value;
SearchingResult(ci, cv, index + 1, callback);
cv[i].used = false;/*clear used sign*/
}
}
}
SearchingResult()函数有四个参数,ci就是存储遍历结果的字母三元组向量,cv是存储遍历过程中数字占用情况的数字二元组向量,index是当前处理的字母三元组在字母三元组向量中的位置索引,0表示第一个字母三元组。callback是一个回调函数,当ci中所有三元组都分配了数字,就调用callback对这组解进行判断,如果满足算式就输出结果。SearchingResult()函数的代码分两部分,前一部分是结束条件判断和结果输出,后一部分是算法的关键。算法就是遍历cv中的所有数字二元组,对于每一个可用的数字(当前没有被占用,并且满足第一个数字不是0的要求),首先设置占用标志,然后将当前字母三元组的值与这个数字的值绑定,最后递归处理下一个字母三元组。
SearchingResult()函数是一个通用过程,负责字母和数字的组合,回调函数(callback)负责根据题目要求对SearchingResult()函数得到的字母和数字的组合进行筛选,只输出正确的组合。对于本题,回调函数可以这样实现:
void OnCharListReady(CharItem ci[max_char_count])
{
char *minuend = "WWWDOT";
char *subtrahend = "GOOGLE";
char *diff = "DOTCOM";
int m = MakeIntegerValue(ci, minuend);
int s = MakeIntegerValue(ci, subtrahend);
int d = MakeIntegerValue(ci, diff);
if((m - s) == d)
{
std::cout << m << " - " << s << " = " << d << std::endl;
}
}
3. 结果验证
根据char_item和char_val的初始数据,求解本题的Google方程式:
SearchingResult(char_item, char_val, 0, OnCharListReady);
穷举算法可以得到两个结果(M和E可以互换):
777589 – 188103 = 589486
777589 – 188106 = 589483
由于算法具有通用性,对于前文例子中的等式:
AAB – BBC = CCD
只需要构造新的字母三元组向量,并修改回调函数的过滤数据即可。新的字母三元组可按照如下方式构造:
CharItem char_item[max_char_count] = { {'A', -1, true}, {'B', -1, true}, {'C', -1, true},
{'D', -1, false} };
回调函数与前文的OnCharListReady()函数类似,此处不再列出。根据新的字符三元组和回调函数运行算法,可以得到13组结果:
443 – 331 = 112
553 – 332 = 221
554 – 441 = 113
665 – 551 = 114
774 – 443 = 331
775 – 552 = 223
776 – 661 = 115
885 – 553 = 332
886 – 662 = 224
887 – 771 = 116
995 – 554 = 441
997 – 772 = 225
998 – 881 = 117
对于加法、乘法和除法算式,同样只要使用不用的回调函数进行结果判断即可,不需要修改SearchingResult()函数,例如加法算式:
ABC + ABC = BCE
可以得到5组结果:
124 + 124 = 248
125 + 125 = 250
249 + 249 = 498
374 + 374 = 748
375 + 375 = 750
注明来源:
来源:oRbIt
链接:http://blog.csdn.net/orbit/article/details/6529277
算法系列(1):Google方程式的更多相关文章
- 高效的多维空间点索引算法 — Geohash 和 Google S2
原文地址:https://www.jianshu.com/p/7332dcb978b2 引子 每天我们晚上加班回家,可能都会用到滴滴或者共享单车.打开 app 会看到如下的界面: app ...
- [转]GPS纠偏算法,适用于google,高德体系的地图
此文是转的,算法没验证过,只是记录一下. GPS纠偏算法,适用于google,高德体系的地图,精确度还比较高.我试了一下比高德本身的纠偏还精确点. /** * gps纠偏算法,适用于google,高德 ...
- JAVA算法系列 冒泡排序
java算法系列之排序 手写冒泡 冒泡算是最基础的一个排序算法,简单的可以理解为,每一趟都拿i与i+1进行比较,两个for循环,时间复杂度为 O(n^2),同时本例与选择排序进行了比较,选择排序又叫直 ...
- JAVA算法系列 快速排序
java算法系列之排序 手写快排 首先说一下什么是快排,比冒泡效率要高,快排的基本思路是首先找到一个基准元素,比如数组中最左边的那个位置,作为基准元素key,之后在最左边和最右边设立两个哨兵,i 和 ...
- javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例
栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...
- 三白话经典算法系列 Shell排序实现
山是包插入的精髓排序排序,这种方法,也被称为窄增量排序.因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元 ...
- Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx Aitit algo fi ...
- 【C#实现漫画算法系列】-判断 2 的乘方
微信上关注了算法爱好者这个公众号,有一个漫画算法系列的文章生动形象,感觉特别好,给大家推荐一下(没收过广告费哦),原文链接:漫画算法系列.也看到了许多同学用不同的语言来实现算法,作为一枚C#资深爱好的 ...
- 玩转算法系列--图论精讲 面试升职必备(Java版)
第1章 和bobo老师一起,玩转图论算法欢迎大家来到我的新课程:<玩转图论算法>.在这个课程中,我们将一起完整学习图论领域的经典算法,培养大家的图论建模能力.通过这个课程的学习,你将能够真 ...
随机推荐
- command shell 的知识整理
cmd 也是shell windowns的外壳一种. 查看文件和文件夹 dir mkdir *** 创建文件夹 rd *** 删除文件夹(应该有参数的,递归之类的)CTR+C 终止命令 cd> ...
- struts2 拦截器
拦截器:对Action的访问.可以拦截到Action中某个方法.与过滤器不同,过滤器过滤的是请求.过滤JSP.html.但是拦截器不能拦截jsp.html的访问. Struts2 拦截器在访问某个 A ...
- 设计模式--工厂模式Factory(创建型)
工厂模式属于创建型模式,分为三类,简单工厂模式.工厂方法模式.抽象工厂模式. 一.简单工厂模式 在工厂中做判断,根据产品类型从而创造相应的产品,当增加新产品时需要修改工厂类. 例如: enum CTY ...
- python logging
参考: https://docs.python.org/2/howto/logging.html#logging-basic-tutorial https://docs.python.org/2/li ...
- 关于网络-get/post
关于网络: //英译 connection:连接 append:添加 resign:放弃 // 加载网页数据 步骤 [self.webView loadRequest:request]; NSURL ...
- matlab学习第一天
>> x=linspace(-,,); >> y=x.^.*sin(x); >> plot(x,y,) >> plot(x,y,) 分析: linspa ...
- HYSBZ 2957 分块
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2957 题意:中文题面 思路: 来自此博客 首先明确问题,对于每栋楼房的斜率K=H/X,问题 ...
- Android源码目录结构详解(转载)
转自:http://blog.csdn.net/xiangjai/article/details/9012387 在学习Android的过程中,学习写应用还好,一开始不用管太多代码,直接调用函数就可以 ...
- CodeForces 540
A. Combination Lock time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- Unity Standard Assets 简介之 Characters
这篇介绍Characters资源包.包含三个文件夹:FirstPersonCharacter.RollerBall.ThirdPersonCharacter. FirstPersonCharacter ...