查找最短的数学运算顺序

时间:2018-11-13 10:27:30

标签: javascript algorithm

我在数学上尝试确定最短的移动顺序以达到所需的数值结果。我有两个函数,两个函数都将一个数字乘以2,然后减去另一个数字的值。

到目前为止,我已经包含了我的代码,这使我可以手动调用这两个函数以获得所需的结果。但是,我想帮助您弄清楚循环自动执行此操作的逻辑。

function findShortestSequence(number) {
    let left = 0;
    let right = 1;
    let moves = [];

    const moveLeft = () => {
        moves.push('L');
        left = 2 * left - right;
    }

    const moveRight = () => {
        moves.push('R');
        right = 2 * right - left;
    }

    moveLeft();
    moveLeft();
    moveRight();
    moveLeft();

    console.log(left, right, moves);
}

findShortestSequence(-11)

5 个答案:

答案 0 :(得分:3)

我只是看-11,考虑11是二进制的1011,这与您对LLRL的手动解决方案类似,只是倒退了。测试表明,这可能是负数的关键:获取其绝对值,然后开始向右移动直到变为零。当您移出1时,请移至左侧;当您移出0时,请移至右侧。最后一步是向左移动,结果进入left
然后,我只是检查了正数,简单地交换了一下举动(因为将其留在原处会提供负数的结果),它看起来比目标高出一个数。所以我只从原始文件中减去了一个,然后开始工作。当然这次,最后一步将是右移,结果进入right

function findShortestSequence(number) {
    let org = number;
    if(number<=0)number=-number; // work with absolute values when input is not positive
    else number--;               // work with one less, if input is positive
    let left = 0;
    let right = 1;
    let moves = [];

    const moveLeft = () => {
        moves.push('L');
        left = 2 * left - right;
    }

    const moveRight = () => {
        moves.push('R');
        right = 2 * right - left;
    }

    if(org<=0)
        while(number!=0){
          if(number&1)moveLeft();
          else moveRight();
          number>>=1;
        }
    else
        while(number!=0){
          if(number&1)moveRight();
          else moveLeft();
          number>>=1;
        }

    console.log(org, left, right, moves.join(''), (org==left)||(org==right));
}

for(var i=-20;i<=20;i++)
    findShortestSequence(i);

虽然我不追求提供完整的解释,但我可以提供一些可能有用的片段:

  • “减去1如果为正”部分就像创建反二的补码(在二的补码中,如果为正数,则不执行任何操作;如果为负数,则得到其正对,将其位反转) ,然后在结果中加1)
  • 从远处看,“乘以2并进行修复”并不是那么极端:
    如果以某种方式以10001001(137)结尾并且1001是固定器,则乘以2将所有内容向左移动(100010010,274),并且如果要保留该值0001001部分恢复到其原始位置,减去该部分返回其原始位置的固定器“局部分割”(100010010-1001 = 100001001),这或多或少moveRight对正数的作用
  • 更新修复程序的过程比较复杂,尽管其中的某些部分确实与以前的想法类似:2 * 1001变成10010,减去10001001后修复1001较低的位置,并从较高的位置开始介绍1。令人讨厌的部分是10001001明显大于10010,因此结果是一个负数,而实际上固定器(如果目标数为正,则为left1001,因为在有史以来的第一次更新中,它变为“ 2 * 0-something”(其中“ something”是正数,因为right从1开始)。确实,在示例循环中,left似乎总是以非正数结尾,而right似乎是非负的
  • 反二的补码也使情况更糟,首先考虑负目标数可能会更干净,因为那里的固定器(right)是非负数,而positive*2-negative也保持不变正面:
    ...11110001001left)和1001right),...11110001001 * 2是...111100010010,而...111100010010-{{1 }} = {1001,任务的第一部分完成了(将...111100001001模式保留在原处)
    并且如果目标是稍后1001(在2个...1110010001001-s之后),则moveLeft确实会moveRight * 2 = 1001,{{1 }}-10010(-119)= 10010,这正是将...11110001001模式扩展到10001001的最低8个位置所需的条件)
  • 关于“最短”:增长最快的部分是1001,如果10001001始终保持为0,则moveRight会跳到下一步的二阶幂,所以{{ 1}}步骤需要达到或超过给定的数字,该数字等于表示该数字所需的有效二进制数,它等于循环所采取的步骤。

答案 1 :(得分:1)

我认为我得出的结论与特维玛达相同。在代码中:

function confirm(str, n){
  let l = 0;
  let r = 1;
  let i = 0;
  
  while(str[i]){
    if (str[i++] == 'L')
      l = 2*l - r;
    else
      r = 2*r - l;
  }
  
  if ([l, r].includes(n))
    return true;
    
  return false;
}

function f(n){
  if ([0, 1].includes(n))
    return '';
  else if (n > 1)
    return (n - 1)
      .toString(2)
      .split('')
      .map(x => x & 1 ? 'R' : 'L')
      .reverse()
      .join('');
  else
    return (-n)
      .toString(2)
      .split('')
      .map(x => x & 1 ? 'L' : 'R')
      .reverse()
      .join('');
}

for (let i=-11; i<=11; i++){
  fi = f(i);
  console.log(i + ': ' + fi + ', ' + confirm(fi, i));
}

答案 2 :(得分:0)

对于下一个状态,您可以采用带有堆栈的迭代方法来检查结果。

这种方法首先测试最小量的更改,然后采用越来越多的可能性。

function findShortestSequence(number) {
    const
        moveLeft = (left, right, moves) => [left * 2 - right, right, [...moves, 'L']],
        moveRight = (left, right, moves) => [left, right * 2 - left, [...moves, 'R']],
        functions = [moveLeft, moveRight];

    var stack = [[0, 1, []]],
        left, right, moves;

    while ([left, right, moves] = stack.shift()) {
        if (left === number) return moves;
        functions.forEach(fn => stack.push(fn(left, right, moves)));
    }
}

console.log(findShortestSequence(-11));
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 3 :(得分:0)

编辑:由于我们对这个问题有正确而正确的解决方案,因此CommandBuilder中对正整数应用的镜像模式是一种误解,不应使用:它会生成重复的命令字符串对于一些整数。请参阅CommandBuilderTwo,以获取更合适的解决方案。


鉴于L = 0和R = 1,我们可以将所需的十进制数P转换为二进制数,并且当负数为L时将每个数字反向存储为1,R为0。

让我们也考虑这些因素。对于任何给定的数字P:

    P可以是一个正数,它是(2 ^ N)的结果;其中0 P可以是一个正数,它是(2 ^ N)-1的结果;其中0
  1. P可以是任何其他正数
  2. P可以是任何负数

我们可以使用以下解决方案有效地确定构建所需输入数字所需的命令:

public class CommandBuilder {

    private static String getCommand(long N)
    {

        if(N == 0 || N == 1 )
            return "no command can be returned because of formulae constraints";

        boolean negated = false;
        boolean isPowerOfTwo = false;
        boolean isPowerOfTwoMinusOne = false;

        if(N < 0){
            N = -N;
            negated = true;
        } else {
            isPowerOfTwo = isPowerOfTwo(N);
            isPowerOfTwoMinusOne = isPowerOfTwoMinusOne(N);
        }

        //Extract the binary representation as L's and R's

        ArrayList<String> commands = new ArrayList<>();
        while (N > 0) {
            if( N % 2 == 0) {
                commands.add("R");
            } else {
                if(isPowerOfTwo)
                    commands.add("R");
                else
                    commands.add("L");
            }
            N /= 2;
        }

        StringBuilder finalCommand = new StringBuilder();

        if(negated) {
            for (String command : commands) {
                finalCommand.append(command);
            }
        }else if (isPowerOfTwo || isPowerOfTwoMinusOne){
            if(isPowerOfTwoMinusOne)
                finalCommand.append("L");

            for(int i = 1; i < commands.size(); i++) {
                finalCommand.append("R");
            }
        }else {
            //Mirroring here 
            for(int i = commands.size() - 1; i >= 0; i--) {
                finalCommand.append(commands.get(i));
            }
        }

        return finalCommand.toString();
    }

    private static boolean isPowerOfTwo(long val) {
        return (int) Math.ceil( Math.log(val) / Math.log(2))
                == (int) Math.floor(Math.log(val) / Math.log(2));
    }

    private static boolean isPowerOfTwoMinusOne(long val) {
        int root = (int) Math.ceil(Math.log(val) / Math.log(2));
        return Math.pow(2, root) - 1 == val;
    }

    //Driver method
    public static void main(String[] args) {

        for(int i = 0; i <= 25; i++){
            System.out.println("The command required to produce " + i + ": " + getCommand(i) );
            System.out.println("The command required to produce -" + i + ": "  + getCommand(-i) );
        }

        int edge = Integer.MAX_VALUE;
        System.out.println("The command required to produce " + edge + ": " + getCommand(edge) );
        System.out.println("The command required to produce -" + edge + ": " + getCommand(-edge) );

    }
}

这是一种等效于@tevemadar的解决方案的解决方案,尽管使用的是Java。

public class CommandBuilderTwo {

    private static String buildCommand(int N) {

        if(N == 0 || N == 1)
            return "no command can be built";

        boolean negated = false;

        if(N < 0) {
            N = -N;
            negated = true;
        } else {
            --N;
        }

        String[] bin = Integer.toBinaryString(N).split("");

        StringBuilder res = new StringBuilder();

        if(negated) {
            for (String c: bin) {
                if((Integer.valueOf(c) & 1) == 1)
                    res.append('L');
                else
                    res.append('R');
            }
        }else{
            for (String c: bin) {
                if((Integer.valueOf(c) & 1) == 1)
                    res.append('R');
                else
                    res.append('L');
            }
        }

        //Reverse string built
        String command = res.toString();
        res = new StringBuilder();
        for(int i = command.length() - 1; i >= 0; i--) {
            res.append(command.charAt(i));
        }

        return res.toString();
    }

    //Driver method
    public static void main (String[] args) {
        for(int i = 0; i <= 25; i++){
            System.out.println("The command required to produce " + i + ": " + buildCommand(i) );
            System.out.println("The command required to produce -" + i + ": "  + buildCommand(-i) );
        }

        int edge = Integer.MAX_VALUE;
        System.out.println("The command required to produce " + edge + ": " + buildCommand(edge) );
        System.out.println("The command required to produce -" + edge + ": " + buildCommand(-edge) );
    }
}

答案 4 :(得分:0)

是的,我也完全同意自己的看法,并发现我的提示很有用(好的,答案已经消失了)。
如果不需要验证,则生成步骤非常简单:

function getSequence(n){
  if(n==0 || n==1)return "";
  var steps=n<0?["R","L"]:["L","R"];
  return (n<0?-n:n-1).toString(2)                        // get binary number
         .replace(/0/g,steps[0]).replace(/1/g,steps[1])  // replace digits with L/R
         .split('').reverse().join('');                  // reverse order
}

for(var i=-20;i<=20;i++)
  console.log(i,getSequence(i));
.as-console-wrapper { max-height: 100% !important; top: 0; }