Java 选择运算符的最佳组合以查找目标编号

Java 选择运算符的最佳组合以查找目标编号,java,algorithm,recursion,dynamic-programming,Java,Algorithm,Recursion,Dynamic Programming,我有一个操作数组和一个目标编号 行动可能是 + 3 - 3 * 4 / 2 我想知道,通过使用这些操作,我可以接近目标数量 我从0开始,需要按照这个顺序迭代操作,我可以选择使用该操作或不使用该操作 因此,如果目标数字是13,我可以使用+3和*4得到12,这是我能得到的最接近目标数字13的数字 我想我需要计算所有可能的组合(我想计算的数量是2^n,其中n是操作的数量) 我曾尝试用java和 import java.util.*; public class Instruction { p

我有一个操作数组和一个目标编号

行动可能是

+ 3
- 3
* 4
/ 2
我想知道,通过使用这些操作,我可以接近目标数量

我从0开始,需要按照这个顺序迭代操作,我可以选择使用该操作或不使用该操作

因此,如果目标数字是13,我可以使用
+3
*4
得到12,这是我能得到的最接近目标数字13的数字

我想我需要计算所有可能的组合(我想计算的数量是2^n,其中n是操作的数量)

我曾尝试用java和

import java.util.*;

public class Instruction {
    public static void main(String[] args) {
        // create scanner
        Scanner sc = new Scanner(System.in);

        // number of instructions
        int N = sc.nextInt();

        // target number
        int K = sc.nextInt();

        //
        String[] instructions = new String[N];

        // N instructions follow
        for (int i=0; i<N; i++) {
            //
            instructions[i] = sc.nextLine();
        }

        //
        System.out.println(search(instructions, 0, N, 0, K, 0, K));
    }

    public static int search(String[] instructions, int index, int length, int progressSoFar, int targetNumber, int bestTarget, int bestDistance) {
        //
        for (int i=index; i<length; i++) {
            // get operator
            char operator = instructions[i].charAt(0);

            // get number
            int number = Integer.parseInt(instructions[i].split("\\s+")[1]);

            //
            if (operator == '+') {
                progressSoFar += number;
            } else if (operator == '*') {
                progressSoFar *= number;
            } else if (operator == '-') {
                progressSoFar -= number;
            } else if (operator == '/') {
                progressSoFar /= number;
            }

            //
            int distance = Math.abs(targetNumber - progressSoFar);

            // if the absolute distance between progress so far
            // and the target number is less than what we have
            // previously accomplished, we update best distance
            if (distance < bestDistance) {
                bestTarget = progressSoFar;
                bestDistance = distance;
            }

            //
            if (true) {
                return bestTarget;
            } else {
                return search(instructions, index + 1, length, progressSoFar, targetNumber, bestTarget, bestDistance);
            }
        }
    }
}
其中1表示已使用该操作,0表示未使用该操作


这样做应该很简单,然后选择哪种组合会产生最好的结果(最接近目标数字的数字),但我不知道如何在java中做到这一点。

在伪代码中,您可以尝试暴力回溯,如:

// ops: list of ops that have not yet been tried out
// target: goal result
// currentOps: list of ops used so far
// best: reference to the best result achieved so far (can be altered; use
//     an int[1], for example)
// opsForBest: list of ops used to achieve best result so far
test(ops, target, currentOps, best, opsForBest)
      if ops is now empty,
         current = evaluate(currentOps)
         if current is closer to target than best,
            best = current
            opsForBest = a copy of currentOps
      otherwise, 
         // try including next op
         with the next operator in ops,
            test(opsAfterNext, target, 
                currentOps concatenated with next, best, opsForBest)
         // try *not* including next op
         test(opsAfterNext, target, currentOps, best, opsForBest)
这肯定会找到最好的答案。然而,它会一次又一次地重复许多操作。您可以通过避免重复计算来节省一些时间,这可以通过使用“此子表达式如何计算”的缓存来实现。当包含缓存时,就进入了“动态编程”(即在以后的计算中重用早期结果)的领域


编辑:添加一个更加面向对象的变体

变量返回最佳结果,并避免使用一个的
best[]
数组。需要使用辅助类
Answer
,并带有字段
ops
result

// ops: list of ops that have not yet been tried out
// target: goal result
// currentOps: list of ops used so far
Answer test(ops, target, currentOps, opsForBest)
      if ops is now empty,
         return new Answer(currentOps, evaluate(currentOps))
      otherwise, 
         // try including next op
         with the next operator in ops,
            Answer withOp = test(opsAfterNext, target, 
                currentOps concatenated with next, best, opsForBest)
         // try *not* including next op
         Answer withoutOp = test(opsAfterNext, target, 
                currentOps, best, opsForBest)
         if withOp.result closer to target than withoutOp.target,
            return withOp
         else
            return withoutOp
动态规划 如果目标值是t,列表中有n个运算,通过组合它们的子序列可以创建的最大绝对值是k,并且作为除法运算的操作数出现的所有值的乘积的绝对值是d,则存在一个简单的O(dkn)-时间和空间算法,用于确定是否可以使用前j个操作的某个子集计算值i,并将此答案(单个位)存储在
dp[i][j]
中:

dp[i][j] = dp[i][j-1] || dp[invOp(i, j)][j-1]
其中,
invOp(i,j)
计算对值i的第j次操作的倒数。请注意,如果第j次运算是x的乘法,并且i不能被x整除,则该运算被认为没有逆运算,并且术语
dp[invOp(i,j)][j-1]
被认为计算为
false
。所有其他操作都有唯一的反转

为了避免浮点代码的精度损失问题,首先将原始目标值t以及所有加减运算的操作数乘以d。这确保了我们遇到的任何除法运算
/x
将只应用于已知可被x整除的值。我们将基本上一直使用1/d的整数倍

由于某些操作(即减法和除法)需要为更高的目标值求解子问题,因此我们通常无法以自下而上的方式计算
dp[i][j]
。相反,我们可以使用自顶向下递归的记忆,从(缩放的)目标值t*d开始,在每个方向上以1的步长向外工作

C++实现

我在C++中实现了这个。“有趣”的部分是

canReach(i,j)
;前面的函数只是处理备忘录表的管道。首先用目标值指定stdin上的输入,然后是一个空格分隔的操作列表,在该列表中,运算符立即在其操作数值之前,例如

10 +8 +11 /2

第二个例子应该给出与第一个相同的答案(9.5),似乎是关于ideone(和我的)内存限制,尽管这可以通过使用
long-long-int
而不是
int
进行扩展,并为
_m[][]
创建一个2位表,而不是在每个条目上浪费一个完整的字节

指数最坏情况时空复杂度
请注意,一般来说,dk或甚至仅k本身可以是输入大小的指数:例如,如果有一个加法,然后是n-1乘法运算,每个乘法运算都涉及一个大于1的数字。通过一个不同的DP精确地计算k并不太困难,该DP只需查找使用所有1的第一个i操作可以达到的最大和最小数字,这里是一个Java 8示例,使用memonization。我想知道退火是否可以应用

public class Tester {

    public static interface Operation {
        public int doOperation(int cur);
    }

    static Operation ops[] = { // lambdas for the opertions
            (x -> x + 3),
            (x -> x - 3),
            (x -> x * 4),
            (x -> x / 2), 
    };

    private static int getTarget(){
        return 2;
    }

    public static void main (String args[]){
        int map[];
        int val = 0;
        int MAX_BITMASK = (1 << ops.length) - 1;//means ops.length < 31 [int overflow]
        map = new int[MAX_BITMASK];
        map[0] = val;

        final int target = getTarget();// To get rid of dead code warning

        int closest = val, delta = target < 0? -target: target;
        int bestSeq = 0;

        if (0 == target) {
            System.out.println("Winning sequence: Do nothing");
        }

        int lastBitMask = 0, opIndex = 0;
        int i = 0;
        for (i = 1; i < MAX_BITMASK; i++){// brute force algo
            val = map[i & lastBitMask]; // get prev memoized value
            val = ops[opIndex].doOperation(val); // compute
            map[i] = val; //add new memo

            //the rest just logic to find the closest
            // except the last part
            int d = val - target;
            d = d < 0? -d: d;
            if (d < delta) {
                bestSeq = i;
                closest = val;
                delta = d;
            }
            if (val == target){ // no point to continue
                break;
            }

            //advance memo mask 0b001 to 0b011 to 0b111, etc.
            // as well as the computing operation.
            if ((i & (i + 1)) == 0){ // check for 2^n -1
                lastBitMask = (lastBitMask << 1) + 1;
                opIndex++;
            }
        }
        System.out.println("Winning sequence: " + bestSeq);
        System.out.println("Closest to \'" + target + "\' is: " + closest);
    }

}
公共类测试器{
公共静态接口操作{
公共int-DOO操作(int-cur);
}
静态操作ops[]={//lambdas用于操作
(x->x+3),
(x->x-3),
(x->x*4),
(x->x/2),
};
私有静态int getTarget(){
返回2;
}
公共静态void main(字符串参数[]){
int-map[];
int-val=0;

int MAX_BITMASK=(1有趣!这让我想起了。好头痛!:)会很乐意在这里提供帮助。您不需要到处传递'length'。Java数组可以随时告诉您它们的长度。示例:
int[]a=new int[5];System.out.println(“+a.length”);输出5;
Oh。您是如何解决这个问题的?但是在这种情况下,每个操作只能使用一次。但是谢谢!@Paul我怀疑OP不允许重复使用同一个操作,您不会返回它。一旦它退出,
best
中的值是正确的,
opsForBest
包含生成的运算符序列当然,你可以让它返回到最佳状态(这样你就不需要传递它了).在变体中编辑。@tucuxi我想你要找的词是分期付款,而不是按话缓存。@SGM1我真的不明白在上面的上下文中,
分期付款
这个词怎么能代替
缓存
。你能解释一下吗?哎呀,我错了,回忆录是t
10 +4000 +5500 /1000
public class Tester {

    public static interface Operation {
        public int doOperation(int cur);
    }

    static Operation ops[] = { // lambdas for the opertions
            (x -> x + 3),
            (x -> x - 3),
            (x -> x * 4),
            (x -> x / 2), 
    };

    private static int getTarget(){
        return 2;
    }

    public static void main (String args[]){
        int map[];
        int val = 0;
        int MAX_BITMASK = (1 << ops.length) - 1;//means ops.length < 31 [int overflow]
        map = new int[MAX_BITMASK];
        map[0] = val;

        final int target = getTarget();// To get rid of dead code warning

        int closest = val, delta = target < 0? -target: target;
        int bestSeq = 0;

        if (0 == target) {
            System.out.println("Winning sequence: Do nothing");
        }

        int lastBitMask = 0, opIndex = 0;
        int i = 0;
        for (i = 1; i < MAX_BITMASK; i++){// brute force algo
            val = map[i & lastBitMask]; // get prev memoized value
            val = ops[opIndex].doOperation(val); // compute
            map[i] = val; //add new memo

            //the rest just logic to find the closest
            // except the last part
            int d = val - target;
            d = d < 0? -d: d;
            if (d < delta) {
                bestSeq = i;
                closest = val;
                delta = d;
            }
            if (val == target){ // no point to continue
                break;
            }

            //advance memo mask 0b001 to 0b011 to 0b111, etc.
            // as well as the computing operation.
            if ((i & (i + 1)) == 0){ // check for 2^n -1
                lastBitMask = (lastBitMask << 1) + 1;
                opIndex++;
            }
        }
        System.out.println("Winning sequence: " + bestSeq);
        System.out.println("Closest to \'" + target + "\' is: " + closest);
    }

}