leetcode
leetcode 1351 ~ 1400
最多的不重叠子字符串

最多的不重叠子字符串

难度:

标签:

题目描述

代码结果

运行时间: 156 ms, 内存: 50.7 MB


/*
 * 题目思路:
 * 1. 遍历字符串,记录每个字符第一次和最后一次出现的位置。
 * 2. 使用贪心算法,依次选择每个子字符串,使得每个字符只出现在一个子字符串中。
 * 3. 记录选择的子字符串,返回最终结果。
 */
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Solution {
    public List<String> maxNumOfSubstrings(String s) {
        int n = s.length();
        int[] start = new int[26];
        int[] end = new int[26];
        IntStream.range(0, 26).forEach(i -> {
            start[i] = n;
            end[i] = -1;
        });
        IntStream.range(0, n).forEach(i -> {
            char c = s.charAt(i);
            start[c - 'a'] = Math.min(start[c - 'a'], i);
            end[c - 'a'] = Math.max(end[c - 'a'], i);
        });

        List<String> res = new ArrayList<>();
        int[] right = {-1};
        IntStream.range(0, n).forEach(i -> {
            if (i == start[s.charAt(i) - 'a']) {
                int newRight = expand(s, i, end);
                if (newRight != -1) {
                    res.add(s.substring(i, newRight + 1));
                    right[0] = newRight;
                }
            }
            if (i == right[0]) {
                right[0] = -1;
            }
        });
        return res;
    }

    private int expand(String s, int i, int[] end) {
        int right = end[s.charAt(i) - 'a'];
        for (int j = i; j <= right; j++) {
            if (end[s.charAt(j) - 'a'] > right) {
                return -1;
            }
        }
        return right;
    }
}

解释

方法:

这道题的解决方案基于图的深度优先搜索。首先,记录每个字符在字符串中的所有位置,然后构建一个图,图中的每个节点代表一个字符,边表示两个字符的位置有重叠,即一个字符的位置区间包含了另一个字符。对每个字符执行深度优先搜索,以确定可以合并为一个最小区间的所有字符,这个区间就代表了一个有效的子字符串。所有这些子字符串根据长度进行排序,最后通过遍历这些区间,选择那些不重叠的区间,即构成解的一部分。算法需要确保每个字符至多只在一个选择的子字符串中出现。

时间复杂度:

O(n + 26^2 + V log V)

空间复杂度:

O(n + V)

代码细节讲解

🦆
为什么在构建图时,需要比较每个字符的位置区间是否有重叠?这种重叠的具体条件是什么?
在构建图时,比较每个字符的位置区间是否有重叠是为了确定字符间的依赖关系,以便找到可以合并为一个连续区间的所有字符。具体条件是,如果一个字符c1的位置区间(从第一个出现到最后一个出现的位置)与另一个字符c2的某个位置有重叠,即c1的最右位置大于c2的第一个位置且c1的第一个位置小于c2的最后一个位置,这两个字符的区间就被视为有重叠。这种情况下,在图中创建从c1到c2的边,表示在选择子字符串时需要同时考虑这两个字符。
🦆
在DFS遍历的过程中,为何要更新和比较字符的最左和最右位置?这种更新对解决问题有什么帮助?
在DFS遍历过程中,更新和比较字符的最左和最右位置有助于确定能够包含该字符及其相关重叠字符的最小连续区间。这是因为如果一个字符与其他字符有重叠,那么在构成一个有效的子字符串时,这些字符应该被合并到一个更大的区间中。通过递归地更新这些值,我们能够找到包含所有相关字符的最小区间。这对于后续的选择最大数量的不重叠子字符串是非常关键的,因为它保证了每个选择的子字符串都是最小且必要的。
🦆
解题思路中提到,算法最终需要选择不重叠的子字符串。请问在实现中是如何确保选择的子字符串之间不会重叠的?
在实现中,首先将所有可能的子字符串按照长度进行排序,然后从最短的子字符串开始选择。在选择过程中,维护一个集合来记录已经被包含在选择的子字符串中的字符。对于每个待选择的子字符串,检查它包含的字符集合是否与已选择的子字符串中的字符有重叠。如果没有重叠,就将这个子字符串添加到答案中,并更新已包含字符的集合。这种方法通过贪心策略确保了每次选择的都是最小的且不与已选择的子字符串重叠的区间,从而达到了题目要求的不重叠条件。
🦆
你如何理解题目中要求的 ‘如果一个子字符串包含字符char,则s中所有char字符都应该在这个子字符串中’ 这一条件?这在你的算法实现中是如何体现的?
这个条件意味着如果选择的子字符串中包含某个字符char,则这个子字符串必须包括字符串s中该字符char的所有出现。这确保了子字符串的完整性和独立性。在算法实现中,这通过记录每个字符在字符串s中的第一个位置和最后一个位置(即完整区间)来体现。在使用DFS搜索合适的子字符串时,对于每个字符,我们都会计算包含它及其所有相关(重叠)字符的最小区间,从而确保如果子字符串中包括了字符char,就一定包含了s中该字符的所有出现。

相关问题