选择最佳运营商组合以查找目标号码

时间:2016-04-01 13:44:20

标签: 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 {
    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);
            }
        }
    }
}

它还没有用,但我想我更接近于解决我的问题。我只是不知道如何结束我的递归。

但也许我不使用递归,而应该只列出所有组合。我只是不知道该怎么做。

例如,如果我有3个操作并且我想计算所有组合,我得到2 ^ 3个组合

111
110
101
011
000
001
010
100

其中1表示使用了操作,0表示未使用该操作。

这样做应该相当简单,然后选择哪个组合得到最好的结果(最接近目标数的数字),但我不知道如何在java中这样做。

3 个答案:

答案 0 :(得分:1)

在伪代码中,您可以尝试强力反向跟踪,如:

// 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)

保证找到最佳答案。但是,它会一次又一次地重复许多操作。您可以通过避免重复计算来节省一些时间,这可以使用“此子表达式如何评估”的缓存来实现。当您包含缓存时,您将进入“动态编程”领域(=在以后的计算中重用较早的结果)。

编辑:添加更多OO-ish变体

Variant返回最佳结果,并避免使用best[]数组。需要使用包含字段Answerops的辅助类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

答案 1 :(得分:0)

动态编程

如果目标值为t,并且列表中有n个操作,并且通过组合它们的某些子序列可以创建的最大绝对值是k,并且所有值的乘积的绝对值显示为除法运算的操作数为d,然后有一个简单的 O(dkn)-time和-space dynamic programming算法,用于确定是否可以计算该值我使用了前j个操作的某个子集,并将此答案(单个位)存储在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]被视为评估为{{1} }。所有其他操作都有独特的反转。

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

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

C ++实现

我已在https://ideone.com/hU1Rpq用C ++实现了这一点。 &#34;有趣&#34;部分是dp[i][j];在此之前的函数只是管道来处理memoisation表。首先使用目标值指定stdin上的输入,然后是以空格分隔的操作列表,其中运算符紧接其操作数值之前,例如

canReach(i, j)

10 +8 +11 /2

第二个例子,它应该给出与第一个相同的答案(9.5),似乎是在ideone(和我的)内存限制附近,尽管可以通过使用10 +4000 +5500 /1000 而不是{{ 1}}和long long int的2位表,而不是在每个条目上浪费一个完整的字节。

指数最坏情况时间和空间复杂度

注意,通常,dk或者甚至只是k本身可以是输入大小的指数:例如如果有一个加法,接着是n-1个乘法运算,每个运算都涉及一个大于1的数。通过一个不同的DP来简单地查找可达的最大和最小数字,这并不太难以计算k。使用所有1&lt; = i&lt; = n的第一个i操作,但我们真正需要的是一个上限,并且它很容易得到一个(有点松散):简单地丢弃所有的符号乘法操作数,将所有int操作转换为_m[][][]操作,然后执行所有乘法和加法操作(即忽略除法)。

还可以应用其他优化措施,例如通过任何公共因子进行划分。

答案 2 :(得分:0)

这是一个使用memoization的Java 8示例。我想知道是否可以应用退火......

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);
    }

}

值得注意的是,“获胜序列”是OP使用的内容和非内容的位表示(显示为十进制),正如OP在问题中所做的那样。

对于那些来自Java 7的人来说,这就是我为lambdas引用的内容:Lambda Expressionsin GUI Applications。因此,如果你被限制在7,你仍然可以很容易地完成这项工作。