簡體   English   中英

選擇運營商的最佳組合以查找目標編號

[英]Choose best combinations of operators to find target number

我有一系列操作和目標號碼。

這些行動可能是

+ 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中這樣做。

在偽代碼中,您可以嘗試強力反向跟蹤,如:

// 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變體

變量返回最佳結果,並避免使用best[]數組。 需要使用輔助類使用字段opsresult Answer

// 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,並且所有值的乘積的絕對值表示為a的操作數。除法操作是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 invOp(i, j)個運算的倒數。 注意,如果第j個操作是乘以x,並且i不能被x整除,那么該操作被認為沒有逆,並且術語dp[invOp(i, j)][j-1]被視為評估為false 所有其他操作都有獨特的反轉。

為避免浮點代碼的精度損失問題,首先將原始目標值t以及所有操作數乘以加法和減法運算。 這確保了我們遇到的任何除法運算/ x將僅被應用於已知可被x整除的值。 我們將基本上使用1 / d的整數倍進行工作。

因為一些操作(即減法和除法)需要求解更高目標值的子問題,所以我們通常不能以自下而上的方式計算dp[i][j] 相反,我們可以使用自上而下遞歸的記憶,從(縮放的)目標值t * d開始,並在每個方向上以1為步長向外工作。

C ++實現

我已經在https://ideone.com/hU1Rpq上用C ++實現了這一點。 “有趣”部分是canReach(i, j) ; 在此之前的函數只是管道來處理memoisation表。 首先使用目標值指定stdin上的輸入,然后是以空格分隔的操作列表,其中運算符緊接其操作數值之前,例如

10 +8 +11 /2

要么

10 +4000 +5500 /1000

第二個例子,它應該給出與第一個相同的答案(9.5),似乎是在ideone(和我的)內存限制附近,雖然這可以通過使用long long int而不是int和2位表來擴展。 for _m[][][]而不是在每個條目上浪費一個完整的字節。

指數最壞情況時間和空間復雜性

請注意,一般來說,dk甚至只是k本身可以是輸入大小的指數:例如,如果有一個加法,接着是n-1個乘法運算,每個運算都涉及一個大於1的數。它也不是很難通過一個不同的DP來精確計算k,它只是查找使用所有1 <= i <= n的第一個i操作可達到的最大和最小數字,但我們真正需要的只是一個上限,並且它很容易獲得一(有所松動)之一:簡單地放棄所有的乘法操作數的符號,將所有-操作+操作,然后執行所有的乘法和加法運算(即忽略師)。

還可以應用其他優化,例如通過任何公共因子進行划分。

這是一個使用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,你仍然可以很容易地完成這項工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM