算式填符号
D:来一道看起来有点麻烦的题目
匪警请拨110,即使手机欠费也可拨通!
为了保障社会秩序,保护人民群众生命财产安全,警察叔叔需要与罪犯斗智斗勇,因而需要经常性地进行体力训练和智力训练!
某批警察叔叔正在进行智力训练:
1 2 3 4 5 6 7 8 9 = 110
请看上边的算式,为了使等式成立,需要在数字间填入加号或者减号(可以不填,但不能填入其它符号)。之间没有填入符号的数字组合成一个数,例如:12+34+56+7-8+9 就是一种合格的填法;123+4+5+67-89 是另一个可能的答案。
请你利用计算机的优势,帮助警察叔叔快速找到所有答案。
每个答案占一行。形如:
12+34+56+7-8+9
123+4+5+67-89
......
Z:题目答案主要有两种形式:
- 循环的对象是符号,可能是 + 或 -
- 可能没有符号,数字会组合。
M:单单循环符号感觉就是一件很麻烦的事情,怎么做到进行符号的循环呢?
Z:如果要使用+-符号的话,其实不需要循环,直接写 +&- 的两种情况即可。但是呢,总共有9的数字,符号位置有8个,也就是说可能性为2^8次方,也可以找到答案。
但我们这里如果想要压缩八次循环为一行代码,就需要用到递归。递归循环符号的方式就是将两种符号的情况都列出来,然后用一个字符串记录符号的使用情况。
以下为代码实现:
class Main3 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9};
f(arr,8,"",110);
}
/**
* 输出填入符号
* @param arr 随时变化的数组
* @param k 当前目标的指针
* @param result 整条算式的最终结果
* @param goal 目标值
*/
private static void f(int[] arr, int k, String result, int goal) {
if(k == 0){
if(arr[0] == goal){
System.out.println(arr[0] + result);
}
return;
}
//相似性
f(arr,k-1,"+"+arr[k]+result, goal-arr[k]);
f(arr,k-1,"-"+arr[k]+result, goal+arr[k]);
//融合
int old = arr[k-1];
arr[k-1] = Integer.parseInt(""+arr[k-1]+arr[k]);
f(arr,k-1,result,goal);
arr[k-1] = old; //回溯
}
}
M:怎么确定这条递归式子的参数呢?
/**
* 输出填入符号
* @param arr 随时变化的数组
* @param k 当前目标的指针
* @param result 整条算式的最终结果
* @param goal 目标值
*/
private static void f(int[] arr, int k, String result, int goal) {
Z:因为我们这题目答案有两个形式:1.符号变化 2.数字融合
@param arr:要表现数字融合,就需要对数组进行操作,所以要把arr传进来。
@param k:因为有多个符号位置,指针用来确定当前处理哪个位置。
@param result:输出值,结果的显示。
@param goal:验证值,判断得数是否为110,需要有一个目标值进行验证。
M:为什么指针k要反着从8~0?
Z:假设算式为12+34+56+7-8+9
,那么它可以分割成12 +34 +56 +7 -8 +9
6块,其中2-6块都是符号+数字
的形式,而第一块需要进行特殊处理。编程应该先考虑普遍情况,再处理特殊值,所以从后面倒着进行循环。
M:为什么下面有一个比较特殊的递归情况,而且只有它需要回溯
//融合
int old = arr[k-1];
arr[k-1] = Integer.parseInt(""+arr[k-1]+arr[k]);
f(arr,k-1,result,goal);
arr[k-1] = old; //回溯
Z:按照规则:符号位可能有三种情况:+ ,- ,空并且与前数融合
当+ 或 - 的情况下,一旦不符合情况,返回上一层仍然与进入该方法前一样,参数int k, String result, int goal
都保持不变。
而当融合时,是需要改变arr的值的。在整个递归嵌套过程中,都是使用同一个arr,如果不在执行完之后对arr进行恢复,就会影响后边的程序运算。
简单来说:这是涉及到参数的类型,参数主要有 基本数据类型 和 引用数据类型,当参数为引用数据类型的时候(算法中一般为数组),就需要进行回溯。
M:那出口的设定是怎么想到的呢?
if(k == 0){
if(arr[0] == goal){
System.out.println(arr[0] + result);
}
return;
}
Z:观察式子的变化,其中goal是无所谓为负数的,所以只有@param k 当前目标的指针
会降为0 。当k=0的时候,脚标为0的arr还没有进行运算,所以判断它是否为剩余的goal值,是的话输出组合后的最终结果。
M:总结一下:因为多个符号需要填充,所以使用递归的方式确定符号内容,用一个数组进行记录输出。