在不到2秒的时间内从控制台读取600,000输入

时间:2016-04-09 19:47:12

标签: java performance time execution-time

目标

我正在解决this问题:

  

小女孩和最大总和

     

小女孩非常喜欢数组查询的问题。

     

有一天,她遇到了一个众所周知的问题:你有一个   n个元素的数组(数组的元素从索引开始   来自1);另外,还有q个查询,每个查询由一对定义   整数li,ri(1≤li≤ri≤n)。你需要为每个查询找到   具有从li到ri的索引的数组元素的总和,包括在内。

     小女孩发现这个问题很无聊。她决定   在以某种方式回复查询之前重新排序数组元素   这使得查询回复的总和最大化。你的任务是   找到这个最大总和的值。

     

输入第一行包含两个以空格分隔的整数n   (1≤n≤2·105)和q(1≤q≤2·105) - 元素的数量   数组和查询的数量,相应地。

     

下一行包含n个以空格分隔的整数ai(1≤ai≤2·105)    - 数组元素。

     

以下q行中的每一行包含两个以空格分隔的整数li   和ri(1≤li≤ri≤n) - 第i个查询。

     

输出在一行中打印一个整数 - 最大总和   重新排序数组元素后查询回复。

使用测试7(请参阅问题末尾的测试结果),输入是一个大小为200,000且包含200,000个查询(具有rl值)的数组。

输入如下:

200000 200000
189622 189286 194361 184457 182376 183471 197548 184736 195806 ... 200,000 integers

188738 290041
33738 90041
122738 390041
... 200,000 line

您可以download a sample input file,也可以创建自己的示例输入。数字本身并不重要。

问题

我需要读取600,000条输入线,而不超过2秒的执行时间。问题是,它甚至没有在2秒内读取前200,000个输入。

如何在2秒内加快代码读取所有600,000输入?

守则

这是我的第一次尝试:

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int q = scanner.nextInt();
        int[] array = new int[n];
        for (int i=0; i<n; i++) {
            array[i] = scanner.nextInt();
        }
        int[][] qArray = new int[q][2];
        for (int i=0; i<q; i++) {
            qArray[i][0] = scanner.nextInt();
            qArray[i][1] = scanner.nextInt();
        }

        int[] index = new int[n];
        Arrays.sort(array);
        for (int i=0; i<q; i++) {
            for (int j = qArray[i][0]-1; j<qArray[i][1]; j++) {
                index[j]++;
            }
        }
        Arrays.sort(index);
        long sum =0;
        for (int i = 0; i<n; i++) {
            sum += index[i]*array[i];
        }
        System.out.println(sum);
    }
}

这是我的第二次尝试:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            String input = bufferedReader.readLine();
            String[] SplitInput = input.split(" ");
            int n = Integer.parseInt(SplitInput[0]);
            int q = Integer.parseInt(SplitInput[1]);

            String input2 = bufferedReader.readLine();

            int[][] qArray = new int[q][2];
            for (int i=0; i<q; i++) {
                input = bufferedReader.readLine();
                SplitInput = input.split(" ");
                qArray[i][0] = Integer.parseInt(SplitInput[0]);
                qArray[i][1] = Integer.parseInt(SplitInput[1]);
            }

            String[] SplitInput2 = input2.split(" ");
            int[] array = new int[n];
            for(int i=0; i<n; i++){
                array[i] = Integer.parseInt(SplitInput2[i]);
            }

            int[] index = new int[n];
            Arrays.sort(array);
            for (int i=0; i<q; i++) {
                for (int j = qArray[i][0]-1; j<qArray[i][1]; j++) {
                    index[j]++;
                }
            }
            Arrays.sort(index);
            long sum = 0;
            for (int i=0; i<n; i++) {
                sum += index[i]*array[i];
            }
            System.out.println(sum);
        }
        catch (NumberFormatException ex) {
            System.out.println("Not a number !");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试结果

尝试1

  

7   时间:2000毫秒,内存:20612 KB   判决:TIME_LIMIT_EXCEEDED

尝试2

  

7   时间:2000毫秒,内存:41340 KB   判决:TIME_LIMIT_EXCEEDED

您可以查看我的完整测试结果herehere。同样,问题出在测试7上。

1 个答案:

答案 0 :(得分:6)

免责声明,我说我能帮助你,我是,但我不能为你解决。我无法在2秒内限制它,因为我没有正确理解问题本身。从技术上讲,我理解你的算法是做什么的,但我在概念上有一些问题需要理解,这让我无法找到 大优化。我发现了大优化。见答案的底部。

备注:我已经在测试页面上看到了较小测试的结果,并且绝对没有理由说您的第一次测试持续200+ ms。我只是不明白。它们在我的计算机上一直运行不到2毫秒(使用Java内部System.nanotime()方法)。我相信测试包括JVM的启动。如果情况确实如此,我是否可以建议您切换到更优化的语言,如C或C++?这意味着测试在某种程度上违背了解释语言。

算法

第一个问题是你的算法本身。它很慢:你实际上正在迭代200,000×x int s(平均值很高,来自你的文件)。在最坏的情况下,您将迭代200,000×200,000 = 40,000,000,000英镑。难怪你的时间大约是20秒。

这太过分了。理想情况下,您应该能够像使用地图一样使用优化来减少双循环。你有大量的内存(256 MB),使用它。你已经做到了;做得更多。

重大优化在于此处。我相信,不是通过索引递增索引,而是应该通过跳过此索引机制并使用更好的索引机制来找到另一个优化。我相信这就是为什么存在这个问题的原因:找到那个算法而不是其他算法。我不喜欢这样,但我不判断它。

读取数据

我测试了通过输入读取数据,你的方法很慢。我责备你使用Scanner

我建议你使用这种结构和这种在&lt;我的计算机上有100毫秒的总测试文件(我坚持认为......我的电脑不是你的电脑而我们的电脑不是你的测试评估的计算机)。我相信它可以进一步减少,但我会说它已经足够了。这不是我们正在寻找的重大优化,但我相信它是第二个。

try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
  int[] counts = split(reader.readLine(), new int[2]);
  int n = c[0], q = c[1];
  int[] array = split(reader.readLine(), new int[n]);
  int[][] queries = new int[q][]; // Note, no size in the second part of the array creation.
  for (int i = 0; i < q; i++) {
    queries[i] = split(reader.readLine(), new int[2]);
  }
  ...
}

使用针对您的用例优化的适当拆分方法:

static int[] split(String s, int[] a) {
  int n = 0, aIndex = 0;
  for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
    char c = s.charAt(sIndex);
    if (c == ' ') { // Separator
      a[aIndex++] = n;
      n = 0;
    } else if ('0' <= c && c <= '9') { // Number
      n = n * 10 + (c - '0'); // Add a digit to the current number
    }
  }
  a[aIndex] = n;
  return a;
}

小优化

从概念上讲,您有以下代码:

for (int i = 0; i < q; i++) {
  // Fill qArray
}

for (int i = 0; i < q; i++) {
  // Work with index.
}

这两个循环可以合并,甚至可以消除对qArray的需求。您读取数据,然后直接处理它。如果循环彼此相邻,那么这并不重要,但是在您第一次尝试中对数组中的东西进行排序之间,并且您在第二次尝试中对数组进行排序并解析输入。这使得您的数据一方面远离CPU缓存,但您在另一方面处理I / O.我不知道哪一个更好。

您的代码中有错误

我试图重新思考这个问题并找到了解决方案,但你的答案与我的答案完全不同。我实际上在你的代码中发现了一个错误。我的文件无法获得与您相同的结果。

在你的最后一个循环中,sum-loop,你将所有东西存储在一个long中,但它实际上可以获得一个int溢出。所以你应该这样做:

sum += (long)(index[i]) * array[i];

找到了它!

关于您的代码,正如我所说,您遇到了问题,因为您可能会获得超过400亿条指令。我可以用你在下面看到的内容来展平你的解决方案。我现在一直达到500毫秒。

public static void main(String[] args) throws IOException {
  long nanos = System.nanoTime();
  myMain();
  nanos = System.nanoTime() - nanos;
  System.out.printf("Time: %sms%n", MILLISECONDS.convert(nanos, NANOSECONDS));
}

static void myMain() throws IOException {
  try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
    int[] counts = split(reader.readLine(), new int[2]);
    int n = counts[0], q = counts[1];
    int[] array = split(reader.readLine(), new int[n]);
    int[] indices = new int[n];
    for (int i = 0; i < q; i++) {
      int[] query = split(reader.readLine(), new int[2]);
      indices[query[0] - 1]++;
      if (query[1] < n) {
        indices[query[1]]--;
      }
    }
    for (int i = 1; i < n; i++) {
      indices[i] += indices[i - 1];
    }
    sort(array, 200_000);
    sort(indices, 200_000);
    long sum = 0;
    for (int i = 0; i < n; i++) {
      sum += (long)array[i] * indices[i];
    }
    System.out.println(sum);
  }
}

static void sort(int[] array, int n) {
  int[] counts = new int[n+1];
  for (int element: array) {
    counts[element]++;
  }
  int current = 0;
  for (int i = 0; i < counts.length; i++) {
    Arrays.fill(array, current, current + counts[i], i);
    current += counts[i];
  }
}

static int[] split(String s, int[] a) {
  int n = 0, aIndex = 0;
  for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
    char c = s.charAt(sIndex);
    if (c == ' ') {
      a[aIndex++] = n;
      n = 0;
    } else if ('0' <= c && c <= '9') {
      n = n * 10 + (c - '0');
    }
  }
  a[aIndex] = n;
  return a;
}

享受!

如果您对此优化有任何疑问,请不要犹豫;)