解决黑客地球问题的算法

时间:2019-03-28 22:08:13

标签: algorithm recursion

我一直在研究Hackerearth问题。问题陈述如下:
我们有三个变量a,b和c。我们需要将a转换为b并允许以下操作:
1.可以递减1.
2.可以递减2.
3.可以乘以c。
将a转换为b所需的最少步骤。
这是我想出的算法:
增量计数为0。
循环直到a === b:
1.执行(x = a * c),(y = a-1)和(z = a-2)。
2.在x,y和z中,选择与b的绝对差最小的那个。
3.将a的值更新为x,y和z中选择的值。
4.将计数增加1。
我可以通过基本测试用例,但是我所有的高级用例都没有通过。我猜我的逻辑是正确的,但是由于复杂性,它似乎失败了。
有人可以提出更优化的解决方案吗?
编辑1
示例代码

function findMinStep(arr) {
    let a = parseInt(arr[0]);
    let b = parseInt(arr[1]);
    let c = parseInt(arr[2]);
    let numOfSteps = 0;
    while(a !== b) {
        let multiply = Math.abs(b - (a * c));
        let decrement = Math.abs(b - (a - 1));
        let doubleDecrement = Math.abs(b - (a - 2));
        let abs = Math.min(multiply, decrement, doubleDecrement);
        if(abs === multiply) a = a * c;
        else if(abs === decrement) a -= 1; 
        else a -= 2;
        numOfSteps += 1;
    }
    return numOfSteps.toString()
}

示例输入: a = 3,b = 10,c = 2
说明:将3与2相乘得到6,从6中减去1得到5,将5与2相乘得到10。 同时标记Python和JS的原因:两者都适合,但我不是在寻找代码,只是一种优化的算法和分析思维。

编辑2:

function findMinStep(arr) {
    let a = parseInt(arr[0]);
    let b = parseInt(arr[1]);
    let c = parseInt(arr[2]);
    let depth = 0;
    let queue = [a, 'flag'];
    if(a === b ) return 0
    if(a > b) {
        let output = Math.floor((a - b) / 2);
        if((a - b) % 2) return output + 1;
        return output

    }
    while(true) {
        let current = queue.shift();
        if(current === 'flag') {
            depth += 1;
            queue.push('flag');
            continue;
        }
        let multiple = current * c;
        let decrement = current - 1;
        let doubleDecrement = current -2;
        if (multiple !== b) queue.push(multiple);
        else return depth + 1
        if (decrement !== b) queue.push(decrement);
        else return depth + 1
        if (doubleDecrement !== b) queue.push(doubleDecrement);
        else return depth + 1
    }
}

仍然超时。还有其他建议吗?
Link供您参考的问题。

1 个答案:

答案 0 :(得分:0)

BFS

贪婪的方法在这里行不通。

但是它已经在正确的轨道上了。考虑图G,其中每个节点代表一个值,每个边沿代表一个操作,并连接与该操作相关的两个值(例如:4和3通过“减1”连接)。使用此图,我们可以轻松地执行BFS-search来找到最短路径:

def a_to_b(a, b, c):
    visited = set()
    state = {a}
    depth = 0

    while b not in state:
        visited |= state
        state = {v - 1 for v in state if v - 1 not in visited} | \
                {v - 2 for v in state if v - 2 not in visited} | \
                {v * c for v in state if v * c not in visited}

        depth += 1

    return 1

此查询通过逐步测试来系统地测试所有可能的操作组合,直到达到b。即从a生成一个操作可以达到的所有值,然后测试两个操作等可以达到的所有值,直到b在生成的值之中。

深入分析

(假设c >= 0,但可以一概而论)

到目前为止,对于很少进行分析的标准方法。这种方法的优点是它可以解决任何此类问题,并且易于实现。但是,它的效率不是很高,一旦数字增长,它将很快达到极限。因此,我将展示一种方法来深入分析问题并获得(更多)性能更高的解决方案:

第一步,此答案将分析问题:

我们需要进行-->op操作,以使a -->op b-->op

的序列
  • 减去1
  • 减去2
  • 乘以c

首先,如果我们先相减然后相乘会怎样?

(a - x) * c = a * c - x * c

接下来,如果我们先相乘然后相减,会发生什么?

a * c - x'

位置系统

嗯,对此没有简化的转换。但是,我们已经掌握了分析更复杂的操作链的基础知识。让我们看看当我们交替链接减法和乘法时会发生什么:

(((a - x) * c - x') * c - x'') * c - x'''=
((a * c - x * c - x') * c - x'') * c - x''' =
(a * c^2 - x * c^2 - x' * c - x'') * c - x''' =
a * c^3 - x * c^3 - x' * c^2 - x'' * c - x'''

您熟悉吗?我们距离以positional system为基础的a定义bc之间的差异只有一步之遥:

a * c^3 - x * c^3 - x' * c^2 - x'' * c - x''' = b
x * c^3 + x' * c^2 + x'' * c + x''' = a * c^3 - b

不幸的是,以上仍然不是我们所需要的。我们所能知道的是,方程的LHS始终为>=0。通常,我们首先需要导出适当的指数n(在上面的示例中为3)s.t。它是最小的,非负的和a * c^n - b >= 0。对于所有系数均为非负数的单个系数(xx',...),解决这个问题是一件很琐碎的事情。

我们可以从上面展示两件事:

  • 如果a < ba < 0没有解决办法
  • 如上所述求解并将所有系数转换为适当的运算会得出最优解

最优性证明

可以通过对n的归纳来证明上面的第二个语句。

n = 0:在这种情况下为a - b < c,因此只有一个-->op

n + 1:让d = a * c^(n + 1) - b。让d' = d - m * c^(n + 1)(其中选择了m)使d'最小且为非负数。每个归纳假设d'可以通过位置系统最佳地生成。相差m * c^n。通过低阶项无法比通过m / 2减法更有效地弥补这种差异。

算法(TLDR部分)

a * c^n - b视为数字基数c,并尝试查找数字。最终数字应包含n + 1个数字,其中每个数字代表一定数量的减法。多个减法通过将减法值相加来用一位数字表示。例如。 5表示-2 -2 -1。从最高有效数字到最低有效数字,该算法的操作如下:

  1. 执行数字所指定的减法
  2. 如果当前数字是最后一位,则终止
  3. 乘以c,然后从1开始重复下一位数字

例如:

  

a = 3,b = 10,c = 2
  选择n = 2
  a * c ^ n-b = 3 * 4-10 = 2
  二进制2是010   执行的步骤:3-0 = 3,3 * 2 = 6,6-1 = 5,5 * 2 = 10

  

a = 2,b = 25,c = 6
  选择n = 2
  a * c ^ n-b = 47
  47底数6是115
  执行的步骤:2-1 = 1,1 * 6 = 6,6-1 = 5,5 * 6 = 30,30-2-2-1 = 25

在python中:

def a_to_b(a, b, c):
    # calculate n
    n = 0
    pow_c = 1
    while a * pow_c - b < 0:
        n += 1
        pow_c *= 1

    # calculate coefficients
    d = a * pow_c - b
    coeff = []
    for i in range(0, n + 1):
        coeff.append(d // pow_c) # calculate x and append to terms
        d %= pow_c               # remainder after eliminating ith term
        pow_c //= c

    # sum up subtractions and multiplications as defined by the coefficients
    return n + sum(c // 2 + c % 2 for c in coeff)