如何使用Java Stream正确实现最长重复子串查找并修复代码错误
问题描述
在完成课程作业时,我需要实现一个方法,用于返回给定字符串中的最长重复子串,且要求仅使用Stream API完成该功能。
以下是我目前编写的核心方法代码:
public static String biggestRedundantSubstring(String s) { Stream.Builder<String> stringBuilder = Stream.builder(); while (!Objects.equals(s, "")) { stringBuilder.add(s); s = s.substring(1); } return stringBuilder.build().sorted().reduce("", (String biggestRedundantSubstring, String matchingPrefix) -> biggestRedundantSubstring.length() > matchingPrefix.length() ? biggestRedundantSubstring : matchingPrefix, (String sub1, String sub2) -> { String matchingPrefix = ""; int limitIndex = Math.max(sub1.length(), sub2.length()) - 1; for (int i = 0; i < limitIndex; i++) { if (sub1.charAt(i) == sub2.charAt(i)) { matchingPrefix += sub1.charAt(i); } else { break; } } return matchingPrefix; }); }
对应的main()方法代码如下:
public static void main(String[] args) { if (args.length != 0) { for (String s : args) { System.out.println(s + " " + biggestRedundantSubstring(s)); } } else { String s = "ababaac"; System.out.println(s + " " + biggestRedundantSubstring(s)); } }
运行现象:
- 无参数运行该程序时,预期控制台输出为:
ababaac aba
- 实际运行输出为:
ababaac ababaac
待确认问题:
- 仅使用Stream API实现该需求是否可行?
- 此前尝试使用三参数的
reduce()方法但依然无法得到正确结果;也考虑过在流外部定义变量来追踪更新最长重复子串的值,但这种写法违背了Stream的设计使用初衷,并不合适。现有代码存在什么问题,该如何修正?
解答
完全使用Stream API实现该需求是可行的,现有代码的问题集中在对Stream API执行逻辑的理解偏差,以及算法落地的细节错误,具体如下:
现有代码核心错误
- 三参数
reduce()方法使用错误:该重载方法的第三个combiner函数,仅在并行流场景下才会被调用,用于合并多个分片的计算结果。你当前构建的是串行流,写在combiner里的公共前缀计算逻辑根本不会触发,实际生效的accumulator逻辑只会单纯比较字符串长度,返回流中最长的元素也就是原串本身,这就是输出错误的根本原因。 - 公共前缀计算边界错误:你用
Math.max(sub1.length(), sub2.length()) - 1作为循环上限,当两个字符串长度不相等时,会触发StringIndexOutOfBoundsException,正确的循环上限应该取两个字符串长度的最小值。 - 算法逻辑缺失:你选择的「生成所有后缀+字典序排序+相邻后缀求最长公共前缀」是最长重复子串的经典实现思路,但你没有实现「相邻后缀两两比对」的核心步骤,直接把所有后缀丢进
reduce()无法获取元素的相邻关系,自然算不出正确结果。
修正后实现
以下实现完全基于Stream API,没有使用流外的可变状态追踪结果,符合Stream的函数式设计规范,可直接运行得到预期结果:
import java.util.Objects; import java.util.stream.IntStream; public class LrsSolver { public static String biggestRedundantSubstring(String s) { if (Objects.isNull(s) || s.length() < 2) { return ""; } // 生成所有后缀并按字典序排序 String[] sortedSuffixes = IntStream.range(0, s.length()) .mapToObj(s::substring) .sorted() .toArray(String[]::new); // 相邻后缀两两计算公共前缀,取最长的结果 return IntStream.range(0, sortedSuffixes.length - 1) .mapToObj(i -> { String pre = sortedSuffixes[i]; String next = sortedSuffixes[i+1]; int minLen = Math.min(pre.length(), next.length()); int prefixLen = 0; while (prefixLen < minLen && pre.charAt(prefixLen) == next.charAt(prefixLen)) { prefixLen++; } return pre.substring(0, prefixLen); }) .max((a, b) -> a.length() - b.length()) .orElse(""); } public static void main(String[] args) { if (args.length != 0) { for (String s : args) { System.out.println(s + " " + biggestRedundantSubstring(s)); } } else { String s = "ababaac"; System.out.println(s + " " + biggestRedundantSubstring(s)); } } }
运行无参数main方法,会正确输出ababaac aba,和预期一致。
补充说明
- 不要强行用三参数
reduce()承载串行流的业务逻辑,它的设计目标是服务并行流的结果合并,串行流场景下不会触发combiner逻辑,硬写逻辑只会静默失效。 - 如果需要更简洁的链式写法,可以把公共前缀计算抽成独立工具函数,不会违反Stream的使用规范。
内容的提问来源于stack exchange,提问作者Mr.D
相关产品推荐
相关产品推荐

