题目:若有5个整数和4个运算符(从加减乘除中指定4个),使用任意多的括号,求最大值。
举例
2 + 3 - 4 * 4 / 5 = 5 - 16 / 5 = 5 - 3 = 2;
(4 / (3 - 2) + 4) * 5=(4 + 4) * 5 = 40;
题目解析
求解这道题目只需要穷举所有表达式的结果即可。但如果直接把所有数字和运算符进行排列,不定数目的括号排列将是一个难题。考虑到括号的本质是优先运算,所得的结果再加入下一次的运算中,所以可以采取如下的穷举方法:
1、将整数部分保存为一维数组A{2,3,4,4,5};
2、将运算符加减乘除分别映射到自然数1,2,3,4并保存为一维数组B{1,2,3,4};
3、从数组B中取出一个元素作运算符有4种取法,再从数组A中取出两个元素作优先运算,考虑到两个数的的运算有前后之分,有A(5,2) = 20种取法,计算结果放回数组A中,这之后两数组的长度都会减少1。
假设分别从数组A中取出元素2和3,从数组B中取出元素2,进行正序运算2 - 3 = -1,则数组A变为{-1,4,4,5},数组B变为{1,3,4},两者的长度都减少了1。
4、 采取递归的方式重复步骤3直到数组A长度为1,数组B为空,就完成了一次表达式的结果运算。比较所有表达式的结果即可找出最大值。
数组A长度由5到4的方式有 A ( 5 , 2 ) × 4 = 5 × 4 × 4 A(5,2) \times 4 = 5 \times 4 \times 4 A(5,2)×4=5×4×4种;由4到3的方式有 A ( 4 , 3 ) × 3 = 4 × 3 × 3 A(4,3) \times 3 = 4 \times 3 \times 3 A(4,3)×3=4×3×3种…依次类推,所有的表达式共有 ( 5 × 4 × 4 ) × ( 4 × 3 × 3 ) × ( 3 × 2 × 2 ) × ( 2 × 1 × 1 ) × 1 = 5 × ( 4 ! ) 3 = 69120 (5 \times 4 \times 4) \times (4 \times 3 \times 3)\times (3 \times 2 \times 2) \times (2 \times 1 \times 1) \times 1 = 5 \times(4!)^3 = 69120 (5×4×4)×(4×3×3)×(3×2×2)×(2×1×1)×1=5×(4!)3=69120种。同理,若数组A的初始长度为N,则其表达式共有 N × ( ( N − 1 ) ! ) 3 N\times ((N-1)!) ^ 3 N×((N−1)!)3种。当N = 6时,将有10368000种表达式。
算法优化
由于加法和乘法没有前后之分,则相应的排列A(5,2)可以改为组合C(5,2),数组B中每含有一个加号或乘号,穷举数目都会减半一次。
注意事项
在除法运算中0不能作分母,且0作分子时结果均为0,所以遇到这种情况时不经计算直接给出结果0。
部分代码说明
C++中数组长度是固定的,数组的长度不可以缩短但数组的“有效长度”可以,比如数组{2,3,4,4,5}的有效长度为5;数组{-1,0xFFFF,4,4,5}的有效长度为4。在每次递归前都需要对两数组分别进行整理,将无效的0xFFFF后移。
完整的C++代码
#include "stdio.h"
#define N 5
int num[N] = {2,3,4,4,5};
int way[N-1] = {1,2,3,4};
int methos(int a,int b,int c){//整数a,b和运算符c
if(c>=1&&c<=4) switch (c){
case 1:return a+b;break;//加法
case 2:return a-b;break;//减法
case 3:return a*b;break;//乘法
case 4:if(b!=0) return a/b;//除法
else return -0xFFFF;break;//若b=0不能做分母
default:return -0xFFFF;break;
}
}
void zhengli_A(int num[N]){//整理数组A,有效数字左移
int *p=num;
for(int i=0;i<N-1;i++) if(p[i]==0xFFFF){
int Y=0;//有效移动标志
for(int j=i;j<N-1;j++){
p[j]=p[j+1];//左移
if(p[j+1]!=0xFFFF) Y=1;//至少有效移动一次
}
if(Y==1) i--;//下标左移
p[N-1]=0xFFFF;//置后
}
}
void zhengli_B(int way[N-1]){//整理数组B,有效数字左移
int *p=way;
for(int i=0;i<N-2;i++) if(p[i]==0xFFFF){
int Y=0;
for(int j=i;j<N-2;j++){
p[j]=p[j+1];//左移
if(p[j+1]!=0xFFFF) Y=1;//至少有效移动一次
}
if(Y==1) i--;//下标左移
p[N-2]=0xFFFF;//置后
}
}
void buzhou_printf(int buzhou[N-1][4]){//步骤显示输出
int (*pzb)[4]=buzhou;
for(int i=0;i<N-1;i++) {//每个表达式都有N-1个步骤
printf("(%d",pzb[i][0]);//显示第一个数
switch(pzb[i][1]){//显示运算符
case 1:printf("+");break;
case 2:printf("-");break;
case 3:printf("*");break;
case 4:printf("/");break;
default:printf("?");break;
} printf("%d=%d)",pzb[i][2],pzb[i][3]);//显示第二个数及结果
} printf("\n");//每个表达式后换行
}
int C_max(int num[N],int way[N-1],int buzhou[N-1][4],int sum_max[2]){//数组A,数组B,步骤,结果
int *pa=num, *pb=way, (*pzb)[4]=buzhou, *psm=sum_max;
int i,j,k,m;
int length=0; for(i=0;i<N;i++) if(pa[length]<0xFFFF) length++;//获取数组A有效长度
int num_new[N]; for(i=0;i<length;i++) num_new[i]=pa[i];//暂存数组A
int way_new[N-1]; for(i=0;i<length-1;i++) way_new[i]=pb[i];//暂存数组B
if(length==1) {//若数组A有效数字只有1个
psm[0]++;//表达式总数加1
if(pa[0]>=psm[1]){//若不小于当前最大值
psm[1]=pa[0];//更新最大值
buzhou_printf(pzb);//显示步骤
}
return pa[0];//返回数组A中唯一有效的数
}
else for(k=0;k<length-1;k++) //先从数组B中取运算符以决定采用排列或是组合
for(i=0;i<length;i++)//从数组A中取出一个数作前数
if(pb[k]==2||pb[k]==4){//减或除采用排列
for(j=0;j<length;j++) if(j!=i) {//从数组A中取出不同下标的第二个数作后数
pzb[N-length][0]=pa[i];pzb[N-length][1]=pb[k];pzb[N-length][2]=pa[j];//保存两个整数和运算符
pzb[N-length][3]=methos(pa[i],pa[j],pb[k]);//保存结果
pa[i]=pzb[N-length][3];pa[j]=0xFFFF;pb[k]=0xFFFF;//结果放回数组
zhengli_A(pa);zhengli_B(pb);//整理数组
C_max(pa,pb,pzb,psm);//递归
for(m=0;m<length;m++) pa[m]=num_new[m];//还原数组A
for(m=0;m<length-1;m++) pb[m]=way_new[m];//还原数组B
}
}
else for(j=i+1;j<length;j++){//加或乘采用组合,从数组A中取出第二个数(下标大于第一个数的下标)
pzb[N-length][0]=pa[i];pzb[N-length][1]=pb[k];pzb[N-length][2]=pa[j];//保存两个整数和运算符
pzb[N-length][3]=methos(pa[i],pa[j],pb[k]);//保存结果
pa[i]=pzb[N-length][3];pa[j]=0xFFFF;pb[k]=0xFFFF;//结果放回数组
zhengli_A(pa);zhengli_B(pb);//整理数组
C_max(pa,pb,pzb,psm);//递归
for(m=0;m<length;m++) pa[m]=num_new[m];//复原数组A
for(m=0;m<length-1;m++) pb[m]=way_new[m];//复原数组B
}
return 0;
}
int main(){
int sum_max[2]={0,-0xFFFF},buzhou[N-1][4];//初始表达式总数为0,最大值为-0xFFFF,步骤为(N-1)*4的二维数组
zhengli_A(num);zhengli_B(way);//整理数组,有效数字左移
C_max(num,way,buzhou,sum_max);//穷举所有表达式,得出最大值及其步骤
printf("\n[sum=%d max=%d]",sum_max[0],sum_max[1]);//显示结果
return 0;
}
运行效果图
拓展延伸
若有指定的N个数和N-1个双目运算符,使用任意多的括号,求最大值。