google竞赛题SecretSum的另一种C++解法, 使用递归生成代替循环 -- 1
2009-07-04 11:07
876 查看
这个程序最早是在2007年在CSDN上Ackarlix大虾的blog上看到的, http://blog.csdn.net/Ackarlix/archive/2007/08/30/1764739.aspx
当时自己也写了一个用递归算法的程序,但没有贴出来(本人比较懒, 习惯潜水,呵呵). 最近整理以前做过的一些东西和一些代码, 把它又做了一些改进, 例如, 原先只支持等长的模式,例如 AAAABBBBCCCC+BBBBCCCCDDDD=CCCCDDDDEEEE等, 修改后可以支持变长的模式(如 A+BC=CDE)以及带限定性的模式(如 A+BC=CD2, X+156=157, 等).
先来看看问题定义吧.
SecretSum 是本次 google 竞赛中第二轮淘汰赛的一道分值为 500 分竞赛题。事实上,这道题目反而比同轮比赛中的那道 1000 分值的RecurringNumbers 难(RecurringNumbers 的难度水准充其量不过是道初一学生奥数竞赛题)。好了,闲话少叙,来看 SecretSum 的题目吧:
一、竞赛题目
Problem Statement
We can substitute each digit of a number with a unique letter from ''A'' to ''Z''. This way we can write groups of numbers in a single representation. For example "ABA" can represent any of the following: 101, 151, 343, 767, 929. However, "ABA" cannot mean 555 because each letter must stand for a distinct digit. Furthermore, numbers cannot begin with 0 and thus ''A'' cannot be replaced by 0. Given two such representations num1 and num2 and the result of their summation return the total number of possible combinations of numbers for which the equation holds. If no combinations are possible then return 0.
Definition
Class: SecretSum
Method: countPossible
Parameters: String, String, String
Returns: int
Method signature: int countPossible(String num1, String num2, String result)
(be sure your method is public)
Constraints
- num1, num2, and result will each contain exactly 3 uppercase letters (''A'' - ''Z'').
Examples
0)
"AAA"
"BBB"
"CCC"
Returns: 32
1)
"ABB"
"DEE"
"TTT"
Returns: 112
2)
"ABC"
"ABA"
"ACC"
Returns: 0
Leading zeroes are not allowed.
3)
"AAA"
"CDD"
"BAA"
Returns: 32
4)
"TEF"
"FET"
"AAA"
Returns: 12
5)
"ABC"
"ABC"
"BCE"
Returns: 5
We can have the following 5 sums:
124 + 124 = 248
125 + 125 = 250
249 + 249 = 498
374 + 374 = 748
375 + 375 = 750
6)
"AAA"
"AAA"
"BBB"
Returns: 4
We can have the following 4 sums:
111 + 111 = 222
222 + 222 = 444
333 + 333 = 666
444 + 444 = 888
This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.
题目的意思大致是这样的:数字0-9可以分别用A-Z中不相同的字母替代,这样就可以用字母组成的字符串代表一个数了。例如"ABA"可以代表: 101, 151, 343, 767, 929。但"ABA"不可以代表555,因为不同的字母必须取不同的数字。此外每个作为开头的字母不取0值。编写一个类 SecretSum,该类中有一个原型为 int countPossible(string num1, string num2, string result)的函数。num1、num2 和 result 就是上面所描述的类型的字符串,其中 result 代表的数为 num1 和 num2 代表的数的和,并且这几个字符串都只含有3个A-Z的字母。返回值为可能取得的组合的总数。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Ackarlix/archive/2007/08/30/1764739.aspx
下面算法的主要特点是使用了递归来生成符合模式(规则)的数, 滤掉不符合规则的数. 这个函数就是根据规则生成操作数的, 这里的参数 Status curr_state是一个std::map<char, char>, 用于传递规则中的字母的当前的映射状态, 而pattern则是输入的规则了(如abc, e3fg, etc.). 其中另一个用到的数据结构是 std::set leading_char; 用来记录打头的字母(因为打头不能为0). 另一个用到的支持函数是find_available_choices, 用来返回在当前状态下可用的数字.
vector<string> gen_num(Status curr_state, const string& pattern)
{
vector<string> result;
if(pattern.length()==1)
{
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
result.push_back(string(1, pattern[0]));
else if(curr_state[pattern[0]] < '0')
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, range from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1, availables[i]));
}
else //not a leading char, can be '0'-'9'(if it's unique)
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1,availables[i]));
}
}
else
result.push_back(string(1, curr_state[pattern[0]]));
return result;
}
vector<char> available_choices;
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
available_choices.push_back(pattern[0]);
else if(curr_state[pattern[0]] < '0') //it's empty, could be assigned any legal value
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, ranging from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
else //not a leading char, can be '0'-'9'
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
}
else //the value is already set, the only choice
available_choices.push_back(curr_state[pattern[0]]);
//prepare to start the loop(and recursion), find the sub patterns!
string sub_pattern = pattern.substr(1, pattern.length()-1);
for(int idx=0; idx<available_choices.size(); ++idx)
{
string leading(1, available_choices[idx]);
curr_state[pattern[0]] = available_choices[idx];
vector<string> temp = gen_num(curr_state, sub_pattern);
for(int i=0; i<temp.size(); ++i)
result.push_back(leading+temp[i]);
}
return result;
}
这个函数被get_all_results函数调用两次, 一次生成num1, 另一次生成num2, 然后调用check_sum_pattern对结果进行检测, 如果符合sum的模式才是一个合格的解.
vector<vector<string> > get_all_results(const string& num1, const string& num2, const string& sum)
{
vector<vector<string> > output;
Status clean_state;
string s =init(clean_state, num1, num2, sum);
if(s.compare("OK")!=0)
{
cout<<s<<endl;
return output;
}
vector<string> num1_vals = gen_num(clean_state, num1);
if(num1_vals.size()==0)
return output;
for(int i=0; i<num1_vals.size(); ++i)
{
Status curr_state=clean_state;
string v_num1 = num1_vals[i];
set_state(curr_state, v_num1, num1);
vector<string> num2_vals = gen_num(curr_state, num2);
if(num2_vals.size()==0)
continue;
string v_sum;
for(int j=0; j<num2_vals.size(); ++j)
{
string v_num2 = num2_vals[j];
set_state(curr_state, v_num2, num2);
v_sum = str_add(v_num1, v_num2);
if(check_sum_pattern(curr_state, v_sum, sum)==true)
{
vector<string> temp;
temp.push_back(v_num1);
temp.push_back(v_num2);
temp.push_back(v_sum);
output.push_back(temp);
}
else
continue;
}
}
return output;
}
check_sum_pattern函数如下:
bool check_sum_pattern(Status state, const string& v_sum, const string& pattern)
{
if(v_sum.length()!=pattern.length())
return false;
for(int i=0; i<pattern.length(); ++i)
{
if(pattern[i]>='0' && pattern[i]<='9') //pattern is a number, just use it
{
if(v_sum[i]!=pattern[i])
return false;
}
else if(state[pattern[i]] < '0')
{
vector<char> availables;
if(leading_chars.find(pattern[i]) != leading_chars.end())
availables = find_available_choices(state, '1', '9');
else //not a leading char, can be '0'-'9'(if it's unique)
availables = find_available_choices(state, '0', '9');
if(find(availables.begin(), availables.end(), v_sum[i]) != availables.end())
{
state[pattern[i]] = v_sum[i]; // shall I pass this change to the caller???
continue; // NO! it's only used to validate the
} // left digits in this v_sum
else
return false;
}
else if(state[pattern[i]] != v_sum[i])
return false;
}
return true;
}
程序运行还需要一些支持函数, 用于检查输入有效性的check_validity, 用于状态初始化的init, 等. 因为用规则滤掉了那些不符合规则的数, 这个程序在运行性能上还比较令人满意, 但可改进的地方还有很多, 比如根据第一个数的取值来进一步压缩第二个数的取值范围, 等. 另一个可以改进的地方就是减少内存的占用. 因为我的目的是打印出所有解, 因此需要保留中间值, 特别是num1. 可以考虑在求num1时, 当递归结束返回一个有效值时,不保存而直接根据它求num2, 然后在判断结果是否合格, 这样可以大量减少内存使用. 当然, 如果只输出结果的个数, 不需要打印出结果的话, 也就不需要保存结果了. 以后如果有时间在改一下吧, 呵呵.
下面给出完整的代码, 还有一些测试样例:
当时自己也写了一个用递归算法的程序,但没有贴出来(本人比较懒, 习惯潜水,呵呵). 最近整理以前做过的一些东西和一些代码, 把它又做了一些改进, 例如, 原先只支持等长的模式,例如 AAAABBBBCCCC+BBBBCCCCDDDD=CCCCDDDDEEEE等, 修改后可以支持变长的模式(如 A+BC=CDE)以及带限定性的模式(如 A+BC=CD2, X+156=157, 等).
先来看看问题定义吧.
SecretSum 是本次 google 竞赛中第二轮淘汰赛的一道分值为 500 分竞赛题。事实上,这道题目反而比同轮比赛中的那道 1000 分值的RecurringNumbers 难(RecurringNumbers 的难度水准充其量不过是道初一学生奥数竞赛题)。好了,闲话少叙,来看 SecretSum 的题目吧:
一、竞赛题目
Problem Statement
We can substitute each digit of a number with a unique letter from ''A'' to ''Z''. This way we can write groups of numbers in a single representation. For example "ABA" can represent any of the following: 101, 151, 343, 767, 929. However, "ABA" cannot mean 555 because each letter must stand for a distinct digit. Furthermore, numbers cannot begin with 0 and thus ''A'' cannot be replaced by 0. Given two such representations num1 and num2 and the result of their summation return the total number of possible combinations of numbers for which the equation holds. If no combinations are possible then return 0.
Definition
Class: SecretSum
Method: countPossible
Parameters: String, String, String
Returns: int
Method signature: int countPossible(String num1, String num2, String result)
(be sure your method is public)
Constraints
- num1, num2, and result will each contain exactly 3 uppercase letters (''A'' - ''Z'').
Examples
0)
"AAA"
"BBB"
"CCC"
Returns: 32
1)
"ABB"
"DEE"
"TTT"
Returns: 112
2)
"ABC"
"ABA"
"ACC"
Returns: 0
Leading zeroes are not allowed.
3)
"AAA"
"CDD"
"BAA"
Returns: 32
4)
"TEF"
"FET"
"AAA"
Returns: 12
5)
"ABC"
"ABC"
"BCE"
Returns: 5
We can have the following 5 sums:
124 + 124 = 248
125 + 125 = 250
249 + 249 = 498
374 + 374 = 748
375 + 375 = 750
6)
"AAA"
"AAA"
"BBB"
Returns: 4
We can have the following 4 sums:
111 + 111 = 222
222 + 222 = 444
333 + 333 = 666
444 + 444 = 888
This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.
题目的意思大致是这样的:数字0-9可以分别用A-Z中不相同的字母替代,这样就可以用字母组成的字符串代表一个数了。例如"ABA"可以代表: 101, 151, 343, 767, 929。但"ABA"不可以代表555,因为不同的字母必须取不同的数字。此外每个作为开头的字母不取0值。编写一个类 SecretSum,该类中有一个原型为 int countPossible(string num1, string num2, string result)的函数。num1、num2 和 result 就是上面所描述的类型的字符串,其中 result 代表的数为 num1 和 num2 代表的数的和,并且这几个字符串都只含有3个A-Z的字母。返回值为可能取得的组合的总数。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Ackarlix/archive/2007/08/30/1764739.aspx
下面算法的主要特点是使用了递归来生成符合模式(规则)的数, 滤掉不符合规则的数. 这个函数就是根据规则生成操作数的, 这里的参数 Status curr_state是一个std::map<char, char>, 用于传递规则中的字母的当前的映射状态, 而pattern则是输入的规则了(如abc, e3fg, etc.). 其中另一个用到的数据结构是 std::set leading_char; 用来记录打头的字母(因为打头不能为0). 另一个用到的支持函数是find_available_choices, 用来返回在当前状态下可用的数字.
vector<string> gen_num(Status curr_state, const string& pattern)
{
vector<string> result;
if(pattern.length()==1)
{
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
result.push_back(string(1, pattern[0]));
else if(curr_state[pattern[0]] < '0')
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, range from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1, availables[i]));
}
else //not a leading char, can be '0'-'9'(if it's unique)
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1,availables[i]));
}
}
else
result.push_back(string(1, curr_state[pattern[0]]));
return result;
}
vector<char> available_choices;
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
available_choices.push_back(pattern[0]);
else if(curr_state[pattern[0]] < '0') //it's empty, could be assigned any legal value
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, ranging from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
else //not a leading char, can be '0'-'9'
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
}
else //the value is already set, the only choice
available_choices.push_back(curr_state[pattern[0]]);
//prepare to start the loop(and recursion), find the sub patterns!
string sub_pattern = pattern.substr(1, pattern.length()-1);
for(int idx=0; idx<available_choices.size(); ++idx)
{
string leading(1, available_choices[idx]);
curr_state[pattern[0]] = available_choices[idx];
vector<string> temp = gen_num(curr_state, sub_pattern);
for(int i=0; i<temp.size(); ++i)
result.push_back(leading+temp[i]);
}
return result;
}
这个函数被get_all_results函数调用两次, 一次生成num1, 另一次生成num2, 然后调用check_sum_pattern对结果进行检测, 如果符合sum的模式才是一个合格的解.
vector<vector<string> > get_all_results(const string& num1, const string& num2, const string& sum)
{
vector<vector<string> > output;
Status clean_state;
string s =init(clean_state, num1, num2, sum);
if(s.compare("OK")!=0)
{
cout<<s<<endl;
return output;
}
vector<string> num1_vals = gen_num(clean_state, num1);
if(num1_vals.size()==0)
return output;
for(int i=0; i<num1_vals.size(); ++i)
{
Status curr_state=clean_state;
string v_num1 = num1_vals[i];
set_state(curr_state, v_num1, num1);
vector<string> num2_vals = gen_num(curr_state, num2);
if(num2_vals.size()==0)
continue;
string v_sum;
for(int j=0; j<num2_vals.size(); ++j)
{
string v_num2 = num2_vals[j];
set_state(curr_state, v_num2, num2);
v_sum = str_add(v_num1, v_num2);
if(check_sum_pattern(curr_state, v_sum, sum)==true)
{
vector<string> temp;
temp.push_back(v_num1);
temp.push_back(v_num2);
temp.push_back(v_sum);
output.push_back(temp);
}
else
continue;
}
}
return output;
}
check_sum_pattern函数如下:
bool check_sum_pattern(Status state, const string& v_sum, const string& pattern)
{
if(v_sum.length()!=pattern.length())
return false;
for(int i=0; i<pattern.length(); ++i)
{
if(pattern[i]>='0' && pattern[i]<='9') //pattern is a number, just use it
{
if(v_sum[i]!=pattern[i])
return false;
}
else if(state[pattern[i]] < '0')
{
vector<char> availables;
if(leading_chars.find(pattern[i]) != leading_chars.end())
availables = find_available_choices(state, '1', '9');
else //not a leading char, can be '0'-'9'(if it's unique)
availables = find_available_choices(state, '0', '9');
if(find(availables.begin(), availables.end(), v_sum[i]) != availables.end())
{
state[pattern[i]] = v_sum[i]; // shall I pass this change to the caller???
continue; // NO! it's only used to validate the
} // left digits in this v_sum
else
return false;
}
else if(state[pattern[i]] != v_sum[i])
return false;
}
return true;
}
程序运行还需要一些支持函数, 用于检查输入有效性的check_validity, 用于状态初始化的init, 等. 因为用规则滤掉了那些不符合规则的数, 这个程序在运行性能上还比较令人满意, 但可改进的地方还有很多, 比如根据第一个数的取值来进一步压缩第二个数的取值范围, 等. 另一个可以改进的地方就是减少内存的占用. 因为我的目的是打印出所有解, 因此需要保留中间值, 特别是num1. 可以考虑在求num1时, 当递归结束返回一个有效值时,不保存而直接根据它求num2, 然后在判断结果是否合格, 这样可以大量减少内存使用. 当然, 如果只输出结果的个数, 不需要打印出结果的话, 也就不需要保存结果了. 以后如果有时间在改一下吧, 呵呵.
下面给出完整的代码, 还有一些测试样例:
相关文章推荐
- google竞赛题SecretSum的另一种C++解法, 使用递归生成代替循环 -- 2
- 使用md5sum递归生成整个目录的sum
- LeetCode 374 使用循环代替递归的二分查找从而避免栈溢出的错误
- 使用md5sum递归生成整个目录的sum
- Google Programming Test Problem SecretSum C++ 11 Solution
- 使用md5sum递归生成整个目录的sum
- mvc在视图中使用递归生成树状结构
- C++ - 使用复合(composition) 代替 private继承
- c++中.dll与.lib文件的生成与使用的详解
- C/C++学习----使用C语言代替cmd命令、cmd命令大全
- 使用递归生成文件目录树
- C++在多线程中使用UINT做循环判断变量的不确定性问题
- C/C++ 关于生成静态库(lib)/动态库(dll)文件如何使用(基于windows基础篇)
- 使用doxygen为C/C++程序生成中文文档
- C++使用容器存储指针变量代替链表
- Android Studio2.2.3使用C++生成so文件
- 在c++中.dll与.lib文件的生成与使用的详细介绍
- C++:使用用户控制循环/使用哨兵值控制循环
- 【LeetCode】113. Path Sum II 基于Java和C++的解法及分析
- 简单谈谈 C/C++ 递归的思想,实现,以及和循环的关系。