【问题标题】:What's the time complexity of Java Stream distinct().sorted()?Java Stream distinct().sorted() 的时间复杂度是多少?
【发布时间】:2021-03-25 11:17:37
【问题描述】:

每次编码面试时,我总是避免使用Java流,因为我不能很好地分析时间复杂度。

例如:在我的日常工作中,我可能会这样写:

Arrays.stream(a).distinct().sorted().toArray();

获取唯一编号并对其进行排序。

但我很好奇时间复杂度会是……? is distinct().sorted 会变成嵌套循环吗?

我应该把它们分开吗?

int[] arr = Arrays.stream(a).distinct().toArray();
Arrays.stream(arr).sorted().toArray();

所以有时当我接受采访时,我会使用 set 来区分然后对它们进行排序.... 但我真的很想写一个干净的代码...

如果有人可以帮忙!谢谢!

【问题讨论】:

  • "... 一次编程面试,我总是避免使用 Java 流" - 这是非常错误的。
  • 忽略并行流,对于distinct(),它对未排序的流使用HashSet(O(n) 摊销)并简单地比较排序流的相邻值(O(n))。对于sorted(),性能一般为O(n*log(n))。这意味着整体性能是O(n*log(n))。
  • "当我接受编码面试时,我总是避免使用 Java 流,因为我不能很好地分析时间复杂度" 作为面试官,我会寻找人谁可以编写几乎所有其他人的可读代码。如果某件事效率低下,并且我认为效率低下很重要,我可以决定询问候选人是否有其他方法可以采取,以及为什么他们倾向于他们选择的方法。我认为作为一般规则,尝试将您的行为改变为您认为他们想要的行为是一个坏主意。做自然而然的事。
  • 我不知道!因为上次面试官告诉我,distinct().sorted() 的时间复杂度会是 O(n)*O(nlogn) = O(n^2logn),所以我才这么糊涂……现在我想他错了,如果这是一个嵌套循环,逻辑就错了! distinct() 应该在 sorted() 之前完成吗?对吧~?
  • 如果你的面试官说是 O(n^2 log n) 那么你的面试官错了,可能不应该面试人。

标签: java java-stream


【解决方案1】:

没有明确的说法,因为您正在针对接口进行开发,并且规范没有强制要求特定的排序算法。

对于通用的Stream,我们必须假设一个比较排序,它的worst case of O(n log n) 是任何算法都无法避免的。

但是您的示例使用 IntStream,原则上可以使用 counting sort 或类似的,具有 O(n)。参考实现在实践中不会发生这种情况,因为具有更好的最坏情况时间复杂度并不一定会在实践中带来更好的性能,并且最大元素数受限于 JVM 的最大数组大小。

distinct() 的时间复杂度是 O(n),因为它只是检查添加到 HashSet 是否成功。将 O(n) 与 O(n log n) 相结合会导致 O(n log n) 的整体复杂度。也许面试官把这种时间复杂度的组合弄错了。

但这是一个很好的例子,证明时间复杂度不等于性能。当您使用sorted().distinct() 时,distinct() 操作将对传入元素进行utilize the sorted nature,这使得无需在幕后构建HashSet。由于参考实现没有原始值集,这消除了很多装箱开销。另一方面,使用distinct().sorted() 可以减少要排序的元素数量,但它需要的不同元素比总流元素要少得多。

时间复杂度未涵盖此类性能差异,这两种方法仍然相同。但如前所述,对于原始类型的流,具有不同时间复杂度的不同算法是可能的。

但有一件事,我们可以肯定地说。当您将操作拆分为两个流操作时,例如从第一个请求一个结果数组并再次将其传递给Arrays.stream,底层实现不可能在下一个操作中利用有关上一个操作的知识。

请注意,上面的语句假设一个终端操作,如您的示例中的 toArray,它消耗所有元素并需要维护生成的遭遇顺序。对于其他短路或无序终端操作,整体时间复杂度可能会发生变化,例如sorted().findFirst() 可以优化为等效于 min() 或者对于无序的终端操作可以消除排序步骤,例如 sum()。在当前的参考实现中不会发生这种情况,但是,如前所述,您正在针对接口进行编程。


¹ 对于原始流,这仅适用于 Java 9+。如前所述,distinct() 没有原始特化,它的实现类似于boxed().distinct().mapToInt(i -> i),在 Java 8 中,boxed() 实现为mapToObj(Integer::valueOf),它丢失了有关已排序输入的信息,如@的最后一节中所述987654324@.

【讨论】:

  • 感谢您的回答!你太专业了!我认为“底层实现不可能在下一个操作中利用有关上一个操作的知识。”很有帮助!正如您所说,“整体时间复杂度可能会发生变化,例如 sorted().findFirst() 可以优化为等效于 min()”,因此使用流似乎有其优势。谢谢!
  • 正如你所说...我认为面试官错误的时间复杂度但是根据流的解释,stream() 将依赖于前面的操作,这听起来像一个嵌套操作,所以我认为那是为什么面试官认为时间复杂度应该 O(n*nlogn)
猜你喜欢
  • 2013-01-04
  • 2012-02-13
  • 2013-05-21
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
  • 2018-11-24
  • 2014-05-27
相关资源
最近更新 更多