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