3

键盘便笺

爪牙们将布尔教授的一些秘密安全地锁起来。或者他们是这么认为的。事实上,他们是如此自信,他们甚至在锁的键盘上贴了一张密码提示便条。

该锁要求您在键盘中输入一对非负整数 (a, b)。由于整数可能高达 20 亿,因此您可以查看便笺寻求帮助。

便利贴上写着两个数字,但即使是奴才也知道不把密码放在那里。他们实际上已经记下了密码整数对 (a, b) 的和(他们将其标记为 s)和按位异或(xor,标记为 x)。这样,他们只需要记住一个。如果他们在减法上有困难,他们可以使用按位异或。

即,我们有 s = a+b 和 x = a^b(其中 ^ 是按位异或运算)。

使用您的自动黑客设备,每次尝试输入猜测都需要几毫秒。由于在被发现之前您只有一点时间,因此您想知道可能需要多长时间才能尝试所有组合。多亏了便签,您现在可以消除某些组合,甚至无需将它们输入到键盘中,并且您可以准确找出破解锁可能需要多长时间 - 在最坏的情况下。

编写一个名为 answer(s, x) 的函数,它找出具有目标和和 xor 的对 (a, b) 的数量。

例如,如果 s=10 且 x=4,则 (a, b) 的可能值为 (3, 7) 和 (7, 3),因此 answer 将返回 2。

如果 s=5 且 x=3,则没有可能的值,因此 answer 将返回 0。

s 和 x 至少为 0,最多为 20 亿。

语言

要提供 Python 解决方案,请编辑 solution.py 要提供 Java 解决方案,请编辑 solution.java

测试用例

输入:(int) s = 10 (int) x = 4 输出:(int) 2

输入:(int) s = 0 (int) x = 0 输出:(int) 1

public static int answer(int s, int x) {
    List<Integer> num = new ArrayList<>();
    int a;
    int b;
    int sum;
    int finalans;

    for(int i = 0; i <=s; i++){
        for(int e = 0; e <= s; e++){
            sum = i + e;
            if(sum == s){
                if((i^e) == x){
                    if(!num.contains(i)){
                        num.add(i);
                    }
                    if(!num.contains(e)){
                        num.add(e);
                    }
                }
            }
        }
    }

    finalans = num.size();
    if((finalans%2) == 0){
        return finalans*2;
    } else if(!((finalans%2) == 0)){
        return finalans;
    }
    return 0;

}

我的代码有效,但是当 s 和 x 变得太大时,它需要很长时间。我怎样才能让这个程序运行得更快?

4

5 回答 5

1

您可以通过意识到对于传入状态(xor 数字、sum 数字、传入进位)存在有限数量的传出状态(传出进位)来解决此问题。您可以使用条件处理每个状态,if并使用递归来计算组合的总数。您可以使用记忆化来提高递归效率。我下面的解决方案O(m)及时解决了问题m,您的数字数据类型中的二进制位数在哪里。由于问题指定了m = 32(整数),因此这在技术上是一种O(1)解决方案。

如果您有任何问题,请告诉我。我试图在代码中添加有用的注释来解释各种情况。

public class SumAndXor {
    public static void main(String[] args) {
        int a = 3;
        int b = 7;

        int sum = a + b;
        int xor = a ^ b;

        System.out.println(answer(sum, xor));
    }

    private static final int NOT_SET = -1;

    // Driver
    public static int answer(int sum, int xor) {
        int numBitsPerInt = Integer.toBinaryString(Integer.MAX_VALUE).length() + 1;
        int[][] cache = new int[numBitsPerInt][2];

        for (int i = 0; i < numBitsPerInt; ++i) {
            cache[i][0] = NOT_SET;
            cache[i][1] = NOT_SET;
        }

        return answer(sum, xor, 0, 0, cache);
    }

    // Recursive helper
    public static int answer(int sum, int xor, int carry, int index, int[][] cache) {
        // Return memoized value if available
        if (cache[index][carry] != NOT_SET) {
            return cache[index][carry];
        }

        // Base case: nothing else to process
        if ((sum >> index) == 0 && (xor >> index) == 0 && carry == 0) {
            return 1;
        }

        // Get least significant bits
        int sumLSB = (sum >> index) & 1;
        int xorLSB = (xor >> index) & 1;

        // Recursion
        int result = 0;

        if (carry == 0) {
            if (xorLSB == 0 && sumLSB == 0) {
                // Since the XOR is 0, the binary digits are either [0, 0] or [1, 1]. Since the
                // sum is 0 and the incoming carry is 0, both [0, 0] and [1, 1] are valid. We
                // recurse with a carry of 0 to represent [0, 0], and we recurse with a carry of
                // 1 to represent [1, 1].
                result = answer(sum, xor, 0, index + 1, cache) + answer(sum, xor, 1, index + 1, cache);
            } else if (xorLSB == 0 && sumLSB == 1) {
                // Since the XOR is 0, the binary digits are either [0, 0] or [1, 1]. Since the
                // sum is 1 and the incoming carry is 0, neither [0, 0] nor [1, 1] is valid.
                result = 0;
            } else if (xorLSB == 1 && sumLSB == 0) {
                // Since the XOR is 1, the binary digits are either [0, 1] or [1, 0]. Since the
                // sum is 0 and the incoming carry is 0, neither [0, 1] nor [1, 0] is valid.
                result = 0;
            } else if (xorLSB == 1 && sumLSB == 1) {
                // Since the XOR is 1, the binary digits are either [0, 1] or [1, 0]. Since the
                // sum is 1 and the incoming carry is 0, both [0, 1] and [1, 0] is valid. We
                // recurse with a carry of 0 to represent [0, 1], and we recurse with a carry
                // of 0 to represent [1, 0].
                result = 2 * answer(sum, xor, 0, index + 1, cache);
            }
        } else {
            if (xorLSB == 0 && sumLSB == 0) {
                // Since the XOR is 0, the binary digits are either [0, 0] or [1, 1]. Since the
                // sum is 0 and the incoming carry is 1, neither [0, 0] nor [1, 1] is valid.
                result = 0;
            } else if (xorLSB == 0 && sumLSB == 1) {
                // Since the XOR is 0, the binary digits are either [0, 0] or [1, 1]. Since the
                // sum is 1 and the incoming carry is 1, both [0, 0] and [1, 1] are valid. We
                // recurse with a carry of 0 to represent [0, 0], and we recurse with a carry of
                // 1 to represent [1, 1].
                result = answer(sum, xor, 0, index + 1, cache) + answer(sum, xor, 1, index + 1, cache);
            } else if (xorLSB == 1 && sumLSB == 0) {
                // Since the XOR is 1, the binary digits are either [0, 1] or [1, 0]. Since the
                // sum is 0 and the incoming carry is 1, both [0, 1] and [1, 0] are valid. We
                // recurse with a carry of 0 to represent [0, 1], and we recurse with a carry
                // of 0 to represent [1, 0].
                result = 2 * answer(sum, xor, 1, index + 1, cache);
            } else if (xorLSB == 1 && sumLSB == 1) {
                // Since the XOR is 1, the binary digits are either [0, 1] or [1, 0]. Since the
                // sum is 1 and the incoming carry is 1, neither [0, 1] nor [1, 0] is valid.
                result = 0;
            }
        }

        cache[index][carry] = result;

        return result;
    }
}
于 2015-05-10T01:27:16.237 回答
1

谷歌说这花费了太长时间,因为你的算法在 O(n^2) 中运行,而谷歌希望它在 O(lg n) 中运行。如果你问我,这对于 3 级挑战来说太难了。我有更容易的4级。对此的解决方案与您所期望的完全不同。事实上,在正确答案中,您甚至永远不会将值设置为 (a, b),也不会将 (a, b) 与 (S, x) 进行比较。在您看到并理解解决方案之前,这与逻辑背道而驰。

无论如何,它有助于在二维图形或 Excel 电子表格中绘制正确答案,使用 S 表示行,x 表示列(将零留空)。然后,寻找模式。数据点实际上形成了一个谢尔宾斯基三角形(参见http://en.wikipedia.org/wiki/Sierpinski_triangle)。

您还会注意到,对于该列中的所有实例,列中的每个数据点(大于零)都是相同的,因此给定您的 x 值,只要与您对应的行,您就会自动知道最终答案应该是什么S 值与三角形中的一个数据点相交。您只需要确定 S 值(行)是否与 x 列的三角形相交。说得通?

甚至列中的值也形成了从 0 到 x 的模式:1, 2, 2, 4, 2, 4, 4, 8, 2, 4, 4, 8, 4, 8, 8, 16... I'我相信你能弄清楚。

这是“给定 x 的最终值”方法以及大部分剩余代码(在 Python 中......Java 太罗嗦和复杂)。您只需要编写三角形遍历算法(我不会放弃它,但这是朝着正确方向的坚实推动):

def final(x, t):
    if x > 0:
        if x % 2: # x is odd
            return final(x / 2, t * 2)
        else: # x is even
            return final(x / 2, t)
    else:
        return t

def mid(l, r):
    return (l + r) / 2

def sierpinski_traverse(s_mod_xms, x. lo, hi, e, l, r):
    # you can do this in 16 lines of code to end with...
    if intersect:
        # always start with a t-value of 1 when first calling final in case x=0
        return final(x, 1)
    else:
        return 0

def answer(s, x):
    print final(x, 1)

    if s < 0 or x < 0 or s > 2000000000 or x > 2000000000 or s < x or s % 2 != x % 2:
        return 0
    if x == 0:
        return 1

    x_modulus_size = 2 ** int(math.log(x, 2) + 2)
    s_mod_xms = s % x_modulus_size
    lo_root = x_modulus_size / 4
    hi_root = x_modulus_size / 2
    exp = x_modulus_size / 4    # exponent of 2 (e.g. 2 ** exp)

    return sierpinski_traverse(s_mod_xms, x, lo_root, hi_root, exp, exp, 2 * exp)


if __name__ == '__main__':
    answer(10, 4)
于 2015-05-29T12:11:15.860 回答
0

尝试将 num 更改为 HashSet。你也可以在最后清理你的 if/else。

例如

public static int answer(int s, int x) {
    HashSet<Integer> num = new HashSet<>();
    int a;
    int b;
    int sum;
    int finalans;

    for(int i = 0; i <=s; i++){
        for(int e = 0; e <= s; e++){
            sum = i + e;
            if(sum == s){
                if((i^e) == x){
                    num.add(i);
                    num.add(e);
                }
            }
        }
    }

    finalans = num.size();
    if((finalans%2) == 0){
        return finalans*2;
    } else {
        return finalans;
    }        
}
于 2015-05-04T04:02:03.150 回答
0

算法中的大多数步骤执行了太多工作:

  • 您对所有非负整数进行线性扫描,直到s. 由于问题是对称的,扫描到s/2就足够了。
  • 您进行第二次线性扫描以找到每个满足的a整数。简单代数表明只有一个这样的 ,即,因此根本不需要线性扫描。ba + b = sbs - a
  • 您进行第三次线性扫描以检查您是否已经找到了一对(a, b)。如果你循环到s/2only,它会一直保持那个a ≤ b,因此你不会遭受重复计算的痛苦。

最后,我可以想到一个简单的优化来节省一些工作:

  • 如果s是偶数,那么其中一个ab都是偶数,或者都是奇数。因此,a ^ b即使在那种情况下。
  • 如果s是奇数,要么是奇数,要么ab奇数,因此a ^ b是奇数。

您可以在执行任何工作之前添加该检查:

public static int answer(int s, int x) {
    int result = 0;
    if (s % 2 == x % 2) {
        for (int a = 0; a <= s / 2; a++) {
            int b = s - a;
            if ((a ^ b) == x) {
                result += 2;
            }
        }
        // we might have double counted the pair (s/2, s/2)
        // decrement the count if needed
        if (s % 2 == 0 && ((s / 2) ^ (s / 2)) == x) {
            result--;
        }
    }
    return result;
}
于 2015-05-04T08:00:48.147 回答
0

为了进一步解释我之前的答案,请看大局……从字面上看。三角形遍历算法的工作原理类似于二分搜索,除了使用三个选项而不是两个选项(“三元”搜索?)。查看包含 S 和 x 的最大三角形内的 3 个最大三角形。然后,选择这三个包含 S 和 x 的三角形。然后,查看新选择的三角形中最大的三个三角形,并选择包含 S 和 x 的三角形。重复直到你到达一个点。如果该点不为零,则返回我指定的“最终”值。如果您选择三角形并且行 S 不与数据点相交,则有一些 if-else 语句也会加快此速度。

于 2015-05-29T20:20:10.833 回答