编程问题 - 翻转硬币

时间:2011-03-13 16:15:19

标签: java memory memory-management puzzle

我需要优化以下代码以减少内存。 我无法找到内存被浪费的地方。它给出了正确的结果。 可在此处查看问题陈述http://www.codechef.com/problems/FLIPCOIN/

import java.util.Scanner;

public class FLIPCOIN4 {
    public static void main(String args[])
    {
        Scanner in = new Scanner(System.in);
        //take N = no of coins, Q = no of commands as input
        String s =  in.nextLine();
        String[] str = s.split(" ");
        int N = Integer.parseInt(str[0]);
        int Q= Integer.parseInt(str[1]);
        int[] output = new int[Q];
        int[] input;
        //input - array of integers (one integer for 32 coins)
        if(N%32>0)
            input = new int[N/32 + 1];
        else
            input = new int[N/32];
        //initialize to 0 = tails
        for(int i=0;i<input.length;i++)
            {
                input[i] = 0;

            }
        int out = 0;
        int temp1 = 0;
        int temp2 = 0;
        int command = 0;
        int RangeL = 0;
        int RangeR = 0;
        //looping over all Q commands
        while(Q>0) {

            s = in.nextLine();
            str = s.split(" ");
            //command - command code
            //RangeL,RangeR - range of coins which are affected
            command = Integer.parseInt(str[0]);
            RangeL = Integer.parseInt(str[1]);
            RangeR = Integer.parseInt(str[2]);
            // command==0 => if coins are to be flipped
            if(command==0) {
                //if the coins range is over multiple numbers in array input[]
                if(RangeR/32>RangeL/32) {
                    if(RangeL%32==0)
                        input[RangeL/32] = input[RangeL/32] ^ -1;
                    else if(RangeL%32==1)
                        input[RangeL/32] =
                            input[RangeL/32] ^ Integer.MAX_VALUE;
                    else
                        input[RangeL/32] =
                            input[RangeL/32] ^ (int)Math.pow(2, 32-(RangeL%32));

                    if(RangeR%32==0)
                        input[RangeR/32] = input[RangeL/8] ^ Integer.MIN_VALUE;
                    else 
                        input[RangeR/32] =
                            input[RangeR/8] ^
                            (Integer.MIN_VALUE
                             + ( Integer.MAX_VALUE +1-
                                 (int) Math.pow(2, 31-(RangeL%32))));
                    if(RangeR/32 - RangeL/32 > 1) {
                        for(int i=RangeL/32+1; i <RangeR/32 ; i++) {
                            input[i] = input[i] ^ -1;
                        }
                    }
                }//if the coins range is contained in one single integer in input[]
                else if(RangeR/32==RangeL/32) {
                    if(RangeL%32==0 && RangeR%32==0)
                        input[RangeL/32] = input[RangeL/32] ^
                            Integer.MIN_VALUE;
                    else if(RangeL%32==0 && RangeR%32==1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MIN_VALUE + (int) Math.pow(2, 30));
                    else if(RangeL%32==0 && RangeR%32 >1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MIN_VALUE +  Integer.MAX_VALUE +1 -
                             (int) Math.pow(2, 31-(RangeR%32)));
                    else if(RangeL%32==1 && RangeR%32 ==1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (int) Math.pow(2, 30);
                    else if(RangeL%32==1 && RangeR%32 >1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MAX_VALUE +1 -
                             (int) Math.pow(2, 31-(RangeR%32)));
                    else
                        input[RangeL/32] = input[RangeL/32] ^
                            ((int) Math.pow(2, 32-(RangeL%32)) -
                             (int) Math.pow(2, 31-(RangeR%32)) );


                }
            }// command==1 => no of heads is to be reported
            else if(command==1) {//if the coins range is contained in a single integer
                if(RangeR/32 == RangeL/32) {
                    temp1 = input[RangeL/32]<< RangeL%32;
                    temp1 = temp1 >>> RangeL%32;
                    temp1 = temp1 >>> (31 - RangeR%32);
                    temp1 = temp1 << (31 - RangeR%32);
                    output[out] = Integer.bitCount(temp1);
                }
                //if the coins range is over multiple numbers in array input[]
                else if(RangeR/32>RangeL/32) {
                    temp1 = input[RangeL/32]<< RangeL%32;
                    temp1 = temp1 >>> RangeL%32;

                    temp2 = input[RangeL/32] >>> (31 - RangeR%32);

                    temp2 = temp2 << (31 - RangeR%32);
                    output[out] =
                        Integer.bitCount(temp1)+ Integer.bitCount(temp2);

                }

                if(RangeR/32 - RangeL/32 > 1) {
                    for(int i=RangeL/32+1; i <RangeR/32 ; i++) {
                        output[out] = output[out] + Integer.bitCount(input[i]);
                    }
                }
                out++;
            }
            Q--;
        }
        for(int i =0;i<out;i++) {
            System.out.println(output[i]);
        }
    }
}

2 个答案:

答案 0 :(得分:2)

关于你的程序的一些一般性评论(与内存问题无关,但可能有助于解决它):

import java.util.Scanner;

public class FLIPCOIN4 {

你的班级名字有点奇怪 - 这是以某种方式强制执行的任务吗? (在Java中,对于类名,通常使用camel-case。FlipCoin将是一个示例。)

    public static void main(String args[])
    {

您正在使用这个非常大的方法进行所有处理。更好地构建代码,将其放在几个方法中,每个方法只有一个任务。

        Scanner in = new Scanner(System.in);
        //take N = no of coins, Q = no of commands as input
        String s =  in.nextLine();
        String[] str = s.split(" ");
        int N = Integer.parseInt(str[0]);
        int Q= Integer.parseInt(str[1]);

Scanner课程的方法多于nextLine() - 如果你直接使用nextInt,你的课程会缩短很多。

        int[] output = new int[Q];

您需要output数组用于什么?您可以在生成后立即输出每个数字。这样可以节省200 kB(如果他们的测试输入真的是Q = 50000)。

        int[] input;
        //input - array of integers (one integer for 32 coins)
        if(N%32>0)
            input = new int[N/32 + 1];
        else
            input = new int[N/32];

你可以在这里使用[(N-1)/32 +1]并避免这两种情况,但我不确定这是否真的更具可读性。

input数组名称错误:它不是输入,但它代表硬币的当前状态。

        //initialize to 0 = tails
        for(int i=0;i<input.length;i++)
            {
                input[i] = 0;
            }

使用0(或'\0'0.0null)自动初始化数组元素,因此完全不需要此循环。

        int out = 0;
        int temp1 = 0;
        int temp2 = 0;
        int command = 0;
        int RangeL = 0;
        int RangeR = 0;

大多数这些变量在首次使用时会被更好地声明(和初始化)。

        //looping over all Q commands
        while(Q>0) {

            s = in.nextLine();
            str = s.split(" ");
            //command - command code
            //RangeL,RangeR - range of coins which are affected
            command = Integer.parseInt(str[0]);
            RangeL = Integer.parseInt(str[1]);
            RangeR = Integer.parseInt(str[2]);

如前所述,您可以轻松地使用in.nextInt()

我还认为s.split方法每次创建一个新的正则表达式模式,这不会花费太长时间,但仍然是多余的。

            // command==0 => if coins are to be flipped

对于命令,你真的不需要将它解析为int,使用String比较也可以。

            if(command==0) {
                //if the coins range is over multiple numbers in array input[]
                if(RangeR/32>RangeL/32) {
                    if(RangeL%32==0)

您反复使用四个数字RangeR/32RangeL/32RangeR%32RangeL%32。将它们分配给适当的变量(甚至常量,final) - 这首先会使你的程序更快,而且(更重要的是)如果你使用正确的名称,则更容易辨认。

                        input[RangeL/32] = input[RangeL/32] ^ -1;
                    else if(RangeL%32==1)
                        input[RangeL/32] =
                            input[RangeL/32] ^ Integer.MAX_VALUE;
                    else
                        input[RangeL/32] =
                            input[RangeL/32] ^ (int)Math.pow(2, 32-(RangeL%32));

不要将Math.pow用于小幂2的整数指数。

2 x 可写为1 << x(如果0 <= x <32)。

                    if(RangeR%32==0)
                        input[RangeR/32] = input[RangeL/8] ^ Integer.MIN_VALUE;
                    else 
                        input[RangeR/32] =
                            input[RangeR/8] ^
                            (Integer.MIN_VALUE
                             + ( Integer.MAX_VALUE +1-
                                 (int) Math.pow(2, 31-(RangeL%32))));
嗯,这是一个残酷的公式。你知道Integer.MAX_VALUE + 1 == Integer.MIN_VALUE吗?

                    if(RangeR/32 - RangeL/32 > 1) {
                        for(int i=RangeL/32+1; i <RangeR/32 ; i++) {
                            input[i] = input[i] ^ -1;
                        }
                    }

你的循环在没有if-check之前会有相同的效果(那时它将是一个空循环)。

                }//if the coins range is contained in one single integer in input[]
                else if(RangeR/32==RangeL/32) {

此评论看起来与闭幕大括号有关,而实际上与以下if有关。

                    if(RangeL%32==0 && RangeR%32==0)
                        input[RangeL/32] = input[RangeL/32] ^
                            Integer.MIN_VALUE;
                    else if(RangeL%32==0 && RangeR%32==1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MIN_VALUE + (int) Math.pow(2, 30));
                    else if(RangeL%32==0 && RangeR%32 >1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MIN_VALUE +  Integer.MAX_VALUE +1 -
                             (int) Math.pow(2, 31-(RangeR%32)));
                    else if(RangeL%32==1 && RangeR%32 ==1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (int) Math.pow(2, 30);
                    else if(RangeL%32==1 && RangeR%32 >1)
                        input[RangeL/32] = input[RangeL/32] ^ 
                            (Integer.MAX_VALUE +1 -
                             (int) Math.pow(2, 31-(RangeR%32)));
                    else
                        input[RangeL/32] = input[RangeL/32] ^
                            ((int) Math.pow(2, 32-(RangeL%32)) -
                             (int) Math.pow(2, 31-(RangeR%32)) );

如前所述:将其放在另一种方法中,不要使用Math.pow。

                }
            }// command==1 => no of heads is to be reported
            else if(command==1) {//if the coins range is contained in a single integer
                if(RangeR/32 == RangeL/32) {
                    temp1 = input[RangeL/32]<< RangeL%32;
                    temp1 = temp1 >>> RangeL%32;
                    temp1 = temp1 >>> (31 - RangeR%32);
                    temp1 = temp1 << (31 - RangeR%32);

啊,你知道如何换班: - )

也可以掩盖它们,而不是将这些位移掉。 final int mask = 1<<(32-RangeL%32) - 1<<(31-RangeR%32)或类似内容,然后是temp1 = mask & input[RangeL/32]

                    output[out] = Integer.bitCount(temp1);
                }
                //if the coins range is over multiple numbers in array input[]
                else if(RangeR/32>RangeL/32) {
                    temp1 = input[RangeL/32]<< RangeL%32;
                    temp1 = temp1 >>> RangeL%32;

                    temp2 = input[RangeL/32] >>> (31 - RangeR%32);

                    temp2 = temp2 << (31 - RangeR%32);
                    output[out] =
                        Integer.bitCount(temp1)+ Integer.bitCount(temp2);
                }

                if(RangeR/32 - RangeL/32 > 1) {
                    for(int i=RangeL/32+1; i <RangeR/32 ; i++) {
                        output[out] = output[out] + Integer.bitCount(input[i]);
                    }
                }

我之前说过的内容也适用于此循环。

                out++;

此时你可以输出结果,而根本没有这个数组。

            }
            Q--;
        }
        for(int i =0;i<out;i++) {
            System.out.println(output[i]);
        }

(此时不需要输出。)

    }
}

要考虑的另一件事:如果你反过来命令这些位,你就不必在所有的指数中使用这些减法。

使用类似java.util.BitSet的东西会使你的整个程序变得非常短,因为这会为你完成所有的逻辑逻辑。

答案 1 :(得分:1)

所以,让我们看看你的程序,特别是你的内存分配。

    int[] output = new int[Q];
    int[] input;
    //input - array of integers (one integer for 32 coins)
    if(N%32>0)
        input = new int[N/32 + 1];
    else
        input = new int[N/32];

这里你要创建两个大数组。如果N和Q都最大为50000,那么这些只需要206250个字节,加上一点开销。

        s = in.nextLine();
        str = s.split(" ");

这是在循环中完成的,每个都创建一个新字符串(从输入读取)和一个新字符串[]。也许还有一个新的Regexp模式和匹配器(我不知道这些是否被追逐)。但无论如何,你只保留这些对象直到下一次迭代,所以垃圾收集器应该能够扔掉它们。

总而言之,没有任何理由让您的程序在运行50000(短)行长输入时需要177 MB。

我认为CodeChef的评估程序有点愚蠢,你不应该担心它说的是什么。也许它给程序一个非常大的堆,所以他们不需要做任何垃圾收集,然后当它们达到177 MB时杀死它们。 (它也因为跑得太久而拒绝了我的答案。)

我会在另一个答案中向您提供有关您的计划的一般提示。