【问题标题】:java - how to reduce execution time for this program [closed]java - 如何减少该程序的执行时间[关闭]
【发布时间】:2012-01-16 15:41:16
【问题描述】:
int n, k;
int count = 0, diff;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] input;
input = br.readLine().split(" ");
n = Integer.parseInt(input[0]);
int[] a = new int[n];
k = Integer.parseInt(input[1]);
input = br.readLine().split(" ");
for (int i = 0; i < n; i++) {
  a[i] = Integer.parseInt(input[i]);
     for (int j = 0; j < i; j++) {
        diff = a[j] - a[i];
        if (diff == k || -diff == k) {
           count++;
        }
     }
}
System.out.print(count);

这是一个示例程序,我在其中打印特定的差异计数,其中 n 范围为

提前感谢您的建议

【问题讨论】:

  • “我正在打印特定的差异计数” - 你需要解释更多你想要实现的目标。
  • 你能解释一下程序应该做什么。问题可能是你有一个嵌套的for循环......如果你可以先以某种方式对值进行排序,也许你可以优化整个事情?您也可以使用 java.lang.Math.abs() 方法代替 if(diff == k || -diff == k) ...我不知道它是否会更快,只是看起来更好;-)
  • 您希望输入的值是唯一的吗?
  • 这个问题似乎是题外话,因为这可能属于codereview.stackexchange.com
  • 该问题不要求一般审查或改进;而是代码存在速度不够快的具体问题,作者正在寻求帮助以使其更快。

标签: java performance algorithm loops


【解决方案1】:

我不明白为什么你在另一个里面有一个循环。那边是O(n^2)

您还可以将读取此整数数组与获取此计数混合在一起。我会将两者分开 - 阅读整个内容,然后扫描并获取差异计数。

也许我误解了你在做什么,但感觉就像你在内部循环中重新做了很多工作。

【讨论】:

  • 为什么这被否决了?在我之后还有另一个答案说的完全一样。
  • 它不包含任何加速OP代码的想法。
  • 是的。如果您将阅读与查找差异计数分开,则可以有效地取出内部循环。现在是两个 O(n) 循环,也就是 O(n)。这是一个更好的设计:两种方法,每一种都做一件事。如所写,如果你有一个数组并想要它的差异计数,你会怎么做?您无法调用此代码,因为它与获取计数混合在一起。
【解决方案2】:

为什么不使用 java.util.Scanner 类而不是 BufferReader。

例如:-

Scanner sc = new Scanner(System.in); int number = sc.nextInt();

这可能会更快,因为它们涉及的包装器更少....See this link

【讨论】:

  • 在幕后,Scanner 类只是将 System.in 包装在 InputStreamReader 中(如果它想读取字符,则必须这样做,因为 System.in 是数据的二进制表示)。此外,与涉及大量数字的嵌套 for 循环相比,额外方法调用的开销不太可能产生太大影响。
  • 嵌套for循环无疑是主要问题。我只是想提一下问题的其他方面...@Dunes 提到的带有 String.split() 的 Scanner 的 nextLine() 可以进一步提高效率...
【解决方案3】:

由于split()使用正则表达式来分割字符串,你应该测量StringTokenizer是否会加快速度。

【讨论】:

  • 根据 API,不鼓励使用 StringTokenizer,新代码应使用 String.split。此外,所使用的模式只能匹配一种组合(空格),因此不会受到任何性能损失。
【解决方案4】:

从文件中读取数字并将它们放入 Map(数字作为键,频率作为值)。遍历它们一次,并为每个数字检查地图是否包含添加了k 的数字。如果是这样,请增加您的计数器。如果您使用 HashMap,则它是 O(n),而不是您的算法的 O(n^2)

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int k = Integer.parseInt(br.readLine().split(" ")[1]);
Map<Integer, Integer> readNumbers = new HashMap<Integer, Integer>();

for (String aNumber : br.readLine().split(" ")) {
    Integer num = Integer.parseInt(aNumber);
    Integer freq = readNumbers.get(num);
    readNumbers.put(num, freq == null ? 1 : freq + 1);
}

int count = 0;
for (Integer aNumber : readNumbers.keySet()) {
    int freq = readNumbers.get(aNumber);
    if (k == 0) {
        count += freq * (freq - 1) / 2;
    } else if (readNumbers.containsKey(aNumber + k)) {
        count += freq * readNumbers.get(aNumber + k);
    }
}
System.out.print(count);

EDIT 修复了重复项和 k = 0

【讨论】:

  • 只需将if (readNumbers.contains(aNumber + k)) 更改为if (readNumbers.contains(aNumber + k) || readNumbers.contains(aNumber - k)) [他想要k 的差异,并且不在乎哪个更大] 从我这里获得+1。
  • 我几乎可以肯定它不是 O(n),但我认为它比 OP 更好。 HashSet 它不是一个简单的数组。无论如何,+1。
  • @amit 他不需要那个。三思而后行。它会计算两次。
  • @FlorinGhita:HashSet 在平均情况下是机械化的O(n),在最坏的情况下衰减到 O(n^2),但在大多数情况下可以假定为O(n)。如果您担心最坏的情况,使用TreeSet 而不是HashSet 的相同算法可以达到O(nlogn),对于较大的输入,这仍然比O(n^2) 要好得多。
  • @PeterLawrey,是的,但稍作修正 - 如果 freq 为空,则放一,而不是零。更新了我的答案。
【解决方案5】:

您正在尝试查找与k 不同的元素。试试这个:

  • 对数组进行排序。
  • 您可以在排序后一次性完成,方法是使用两个指针并根据差异大于或小于k 调整其中一个指针

【讨论】:

  • 谢谢@unbeli。这个解决方案对我来说看起来不错。我试试看
【解决方案6】:

值的稀疏映射,以及它们的出现频率。

SortedMap<Integer, Integer> a = new TreeMap<Integer, Integer>();
for (int i = 0; i < n; ++i) {
    int value = input[i];
    Integer old = a.put(value, 1);
    if (old != null) {
        a.put(value, old.intValue() + 1);
    }
}
for (Map.Entry<Integer, Integer> entry : a.entrySet()) {
    Integer freq = a.get(entry.getKey() + k);
    count += entry.getValue() * freq; // N values x M values further on.
}

这个 O(n)。

如果成本太高,您可以对输入数组进行排序并做类似的事情。

【讨论】:

  • 它不是 O(n),因为 TreeMap.put 是 O(log n),在最坏的情况下你必须做 n 次放置。
  • @soulcheck 对于地图循环的填充以及从地图循环的访问,您是正确的 O(n log n)。仍然肯定比 O(n²) 好得多。我承认,如果全部排序,TreeMap 可能会恶化到 n²。 HashMap 会做得更好。
【解决方案7】:

这里是@Socha23使用HashSet、TIntIntHashSet的方案与原方案的对比。

对于 100,000 个数字,我得到了以下结果(没有读取和解析)

对于 100 个唯一值,k=10

Set: 89,699,743 took 0.036 ms
Trove Set: 89,699,743 took 0.017 ms
Loops: 89,699,743 took 3623.2 ms

对于 1000 个唯一值,k=10

Set: 9,896,049 took 0.187 ms
Trove Set: 9,896,049 took 0.193 ms
Loops: 9,896,049 took 2855.7 ms

代码

import gnu.trove.TIntIntHashMap;
import gnu.trove.TIntIntProcedure;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

class Main {
    public static void main(String... args) throws Exception {
        Random random = new Random(1);
        int[] a = new int[100 * 1000];
        int k = 10;
        for (int i = 0; i < a.length; i++)
            a[i] = random.nextInt(100);

        for (int i = 0; i < 5; i++) {
            testSet(a, k);
            testTroveSet(a, k);
            testLoops(a, k);
        }
    }

    private static void testSet(int[] a, int k) {
        Map<Integer, Integer> readNumbers = new HashMap<Integer, Integer>();
        for (int num : a) {
            Integer freq = readNumbers.get(num);
            readNumbers.put(num, freq == null ? 1 : freq + 1);
        }

        long start = System.nanoTime();
        int count = 0;
        for (Integer aNumber : readNumbers.keySet()) {
            if (readNumbers.containsKey(aNumber + k)) {
                count += (readNumbers.get(aNumber) * readNumbers.get(aNumber + k));
            }
        }
        long time = System.nanoTime() - start;
        System.out.printf("Set: %,d took %.3f ms%n", count, time / 1e6);
    }

    private static void testTroveSet(int[] a, final int k) {
        final TIntIntHashMap readNumbers = new TIntIntHashMap();
        for (int num : a)
            readNumbers.adjustOrPutValue(num, 1,1);

        long start = System.nanoTime();
        final int[] count = { 0 };
        readNumbers.forEachEntry(new TIntIntProcedure() {
            @Override
            public boolean execute(int key, int keyCount) {
                count[0] += readNumbers.get(key + k) * keyCount;
                return true;
            }
        });
        long time = System.nanoTime() - start;
        System.out.printf("Trove Set: %,d took %.3f ms%n", count[0], time / 1e6);
    }

    private static void testLoops(int[] a, int k) {
        long start = System.nanoTime();
        int count = 0;
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < i; j++) {
                int diff = a[j] - a[i];
                if (diff == k || -diff == k) {
                    count++;
                }
            }
        }
        long time = System.nanoTime() - start;
        System.out.printf("Loops: %,d took %.1f ms%n", count, time / 1e6);
    }

    private static long free() {
        return Runtime.getRuntime().freeMemory();
    }
}

【讨论】:

  • 干得好@Peter Lawrey
【解决方案8】:

使用集合和地图,正如其他用户已经解释过的,所以我不再重复他们的建议。

我会提出其他建议。 停止使用String.split。它编译并使用正则表达式。 String.split 中有这一行:Pattern.compile(expr).split(this)。 如果您想沿单个字符拆分,您可以编写自己的函数,它会快得多。我相信 Guava (ex-Google collections API) 具有字符串拆分功能,它可以在不使用正则表达式的情况下拆分字符。

【讨论】:

    猜你喜欢
    • 2021-07-09
    • 1970-01-01
    • 2014-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多