帮助小胖计算24点。
问题描述
给定四个数,四个基本运算符+,-,*,/
,计算二十四点。可以添加括号。
分析
对于具体的一组形如a op1 b op2 c op3 d
的算式,其添加括号就相当于指定三个操作符的顺序。例如,(a op1 b) op2 (c op3 d)
可看成先执行op1
、op3
再执行op2
。
故理论上遍历4!
种数字排列*(4*4*4)
种操作符排列*3!
种操作符调用顺序即可,实际代码中优化避免重复计算。
参考代码
const cal=(a,op,b)=>{
if(op==='+'){
return {
val:a.val+b.val,
exp:`(${a.exp}${op}${b.exp})`
}
}
else if(op==='-'){
return {
val:a.val-b.val,
exp:`(${a.exp}${op}${b.exp})`
}
}
else if(op==='*'){
return {
val:a.val*b.val,
exp:`(${a.exp}${op}${b.exp})`
}
}
else if(op==='/'){
return {
val:a.val/b.val,
exp:`(${a.exp}${op}${b.exp})`
}
}
else{
throw new Error('unknow op')
}
}
const schemeFrom=(a,opi,b,opj,c,opk,d)=>mode=>{
if(mode===123){
return cal(cal(cal(a,opi,b),opj,c),opk,d)
}
else if(mode===132){
return cal(cal(a,opi,b),opj,cal(c,opk,d))
}
else if(mode===213){
return cal(cal(a,opi,cal(b,opj,c)),opk,d)
}
else if(mode===231){
return cal(a,opi,cal(cal(b,opj,c),opk,d))
}
else if(mode===312){
return cal(cal(a,opi,b),opj,cal(c,opk,d))
}
else if(mode===321){
return cal(a,opi,cal(b,opj,cal(c,opk,d)))
}
else{
throw new Error('unknow mode')
}
}
const isAddOrSub=op=>op==='+'||op==='-'
const mode=(opi,opj,opk)=>{
let i=isAddOrSub(opi)?0:1
let j=isAddOrSub(opj)?0:1
let k=isAddOrSub(opk)?0:1
return [i,j,k].join('')
}
const mathEqual=(a,b)=>Math.abs(a-b)<1e-10
const solve=target=>{
return function*(a,b,c,d){
const ops=['+','-','*','/']
for(let i=0;i<4;i++){
for(let j=0;j<4;j++){
for(let k=0;k<4;k++){
let opi=ops[i]
let opj=ops[j]
let opk=ops[k]
let md=mode(opi,opj,opk)
let scheme
let schemeBy=schemeFrom({
val:a,
exp:a.toString()
},
opi,{
val:b,
exp:b.toString()
},
opj,{
val:c,
exp:c.toString()
},
opk,{
val:d,
exp:d.toString()
})
if(md==='001'||md==='110'){
scheme=schemeBy(123)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(132)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(231)
if(mathEqual(scheme.val,target)){
yield scheme
}
}
else if(md==='010'||md==='101'){
scheme=schemeBy(123)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(132)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(213)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(321)
if(mathEqual(scheme.val,target)){
yield scheme
}
}
else if(md==='100'||md==='011'){
scheme=schemeBy(123)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(213)
if(mathEqual(scheme.val,target)){
yield scheme
}
scheme=schemeBy(231)
if(mathEqual(scheme.val,target)){
yield scheme
}
}
else if(md==='111'||md==='000'){
scheme=schemeBy(123)
if(mathEqual(scheme.val,target)){
yield scheme
}
}
else{
throw Error('unknow mode')
}
}
}
}
}
}
const solveOneOrder=target=>(a,b,c,d)=>{
const schemeGenerator=solve(target)(a,b,c,d)
const one=schemeGenerator.next()
return one.done?-1:one.value
}
const solveAllOrder=target=>(a,b,c,d)=>{
const schemeGenerator=solve(target)(a,b,c,d)
const schemes=[]
for(let scheme of schemeGenerator){
schemes.push(scheme)
}
return schemes
}
const next_perm=arr=>{
if(arr.length===1){
return false
}
else{
let cp=arr.slice()
let l=cp.length-2
while(l && cp[l]>=cp[l+1]){
l--
}
if(cp[l]>=cp[l+1]){
return false
}
else{
let r=l+1
while(cp[r+1]>cp[l]){
r++
}
let t=cp[l]
cp[l]=cp[r]
cp[r]=t
let tail=cp.splice(l+1).reverse()
cp=cp.concat(tail)
}
return cp
}
}
const perm=arr=>{
let curr=arr.slice().sort((a,b)=>a-b)
let ret=[]
while(curr){
ret.push(curr)
curr=next_perm(curr)
}
return ret
}
const solveOneGenerator=target=>function*(a,b,c,d){
const perms=perm([a,b,c,d])
for(let currPerm of perms){
yield*solve(target)(...currPerm)
}
}
const solveOne=target=>(a,b,c,d)=>{
const schemeGenerator=solveOneGenerator(target)(a,b,c,d)
const one=schemeGenerator.next()
return one.done?-1:one.value
}
const solveAll=target=>(a,b,c,d)=>{
const perms=perm([a,b,c,d])
const allSchemes=perms.reduce((allS,currPerm)=>{
const currScheme=solveAllOrder(target)(...currPerm)
return allS.concat(currScheme)
},[])
return allSchemes
}
console.log(solveOne(24)(11,9,5,7))
/*
[ { val: 24, exp: '((5+7)*(11-9))' },
{ val: 24, exp: '((7+5)*(11-9))' },
{ val: 24, exp: '((11-9)*(5+7))' },
{ val: 24, exp: '((11-9)*(7+5))' } ]
*/