
本文介绍一种高效、可靠的算法,用于在由多个字符串片段组成的列表中查找跨片段目标子串(如[{searched_placeholder}])的起始和结束位置索引,并返回其在原始列表中的最小覆盖区间。
本文介绍一种高效、可靠的算法,用于在由多个字符串片段组成的列表中查找跨片段目标子串(如[{searched_placeholder}])的起始和结束位置索引,并返回其在原始列表中的最小覆盖区间。
在实际开发中,常遇到文本被预分割为 List<string></string> 的场景(如模板引擎、富文本解析或流式内容处理),而待搜索的目标字符串(例如占位符 [{searched_placeholder}])可能恰好横跨多个列表元素。此时,不能简单使用 String.contains() 或逐项匹配,而需确定该子串首次完整出现所覆盖的最小连续子列表范围——即起始索引 startIdx 和结束索引 endIdx。
核心思路是:构建累积拼接的字符串流,在拼接过程中动态判断目标子串是否首次“完全可见”;再反向收缩前缀,精确定位起始位置。以下为优化后的实现方案:
public static int[] findSubstringSpan(List<String> fragments, String target) {
if (fragments == null || target == null || fragments.isEmpty()) {
return new int[]{-1, -1};
}
StringBuilder sb = new StringBuilder();
int endIndex = -1;
// 第一遍:找到目标子串首次完整出现时的结束索引
for (int i = 0; i < fragments.size(); i++) {
sb.append(fragments.get(i));
if (sb.indexOf(target) != -1) {
endIndex = i;
break;
}
}
if (endIndex == -1) {
return new int[]{-1, -1}; // 未找到
}
// 第二遍:从头开始逐步移除前缀,直到目标子串“消失”,则上一个位置即为起始索引
StringBuilder temp = new StringBuilder(sb);
int startIndex = 0;
for (int i = 0; i <= endIndex; i++) {
String prefix = fragments.get(i);
int prevLen = temp.length();
temp.delete(0, Math.min(prefix.length(), temp.length()));
if (temp.indexOf(target) == -1 && prevLen > 0) {
startIndex = i;
break;
}
if (i == endIndex && temp.indexOf(target) != -1) {
startIndex = i; // 全部在最后一个片段中
}
}
return new int[]{startIndex, endIndex};
}✅ 使用示例:
List<String> str = new ArrayList<>();
str.add("This is a ");
str.add("[{searched");
str.add("_placeholder}]");
str.add(" in this string.");
int[] span = findSubstringSpan(str, "searched_placeholder");
System.out.println("Start index: " + span[0] + ", End index: " + span[1]);
// 输出:Start index: 1, End index: 2⚠️ 注意事项:
- 该算法时间复杂度为 O(n·m)(n 为列表长度,m 为平均片段长度),适用于中小规模数据;若性能敏感,可改用 KMP 或 Rabin-Karp 预处理目标串,但需重写匹配逻辑。
- 严格区分“子串存在”与“精确边界”:本方案返回的是覆盖目标子串所需的最短连续索引区间,不保证目标串起始字符一定位于
fragments[startIndex]开头,仅保证其跨越startIndex到endIndex(含)。 - 若目标串为空或列表为空,方法安全返回
[-1, -1],调用方应做空值校验。
该方案兼顾可读性与鲁棒性,可直接集成至模板解析器、动态文本替换等业务模块中。

















