如何使程序更快[Keypad_Sticky_Note]

时间:2015-05-04 03:08:23

标签: java algorithm challenge-response

键盘便笺

仆从们布鲁恩教授的一些秘密安全地被锁定了。或者他们认为。事实上,他们是如此自信,他们甚至有一个密码暗示粘滞在键盘上的粘滞便笺。

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

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

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

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

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

例如,如果s = 10且x = 4,则(a,b)的可能值为(3,7)和(7,3), 所以答案会回归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变得太大时,它需要很长时间。如何让这个程序更快地运行?

5 个答案:

答案 0 :(得分:1)

您可以通过实现对于传入状态(xor数字,总和数字,传入进位)来解决这个问题,因此存在有限数量的传出状态(传出进位)。您可以使用if条件处理每个状态,并使用递归来计算组合的总数。您可以使用memoization使递归有效。我的解决方案解决了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;
    }
}

答案 1 :(得分:1)

Google表示,由于您的算法在O(n ^ 2)中运行,并且Google希望它在O(lg n)中运行,因此Google表示它耗时太长。如果你问我,这个对于3级挑战来说太困难了。我有更容易的4级。对此的解决方案与您的预期完全不同。事实上,你甚至都没有在(a,b)中设置值,也没有在正确答案中将(a,b)与(S,x)进行比较。在您看到并理解解决方案之前,它与逻辑相反。

无论如何,在2D图形或Excel电子表格中使用S表示行,x表示列(将零留空)有助于绘制正确答案。然后,寻找模式。数据点实际上形成了一个Sierpinski三角形(见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 ..我相信你能搞清楚。

这是&#34;给出的最终值x&#34;方法以及大多数剩余的代码(在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)

答案 2 :(得分:0)

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

e.g。

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

答案 3 :(得分:0)

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

  • 对所有非负整数进行线性扫描,直到s。由于问题是对称的,因此扫描到s/2就足够了。
  • 您进行第二次线性扫描,以查找满足a的每个b另一个整数a + b = s。简单代数表明只有一个bs - a,因此根本不需要线性扫描。
  • 您执行第三次线性扫描以检查您是否已找到一对(a, b)。如果您只循环到s/2,它将始终保持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;
}

答案 4 :(得分:0)

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