google竞赛题SecretSum的另一种C++解法, 使用递归生成代替循环 -- 1

这个程序最早是在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, 然后在判断结果是否合格, 这样可以大量减少内存使用. 当然, 如果只输出结果的个数, 不需要打印出结果的话, 也就不需要保存结果了. 以后如果有时间在改一下吧, 呵呵.

 

下面给出完整的代码, 还有一些测试样例:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值