leetcode
leetcode 651 ~ 700
划分字母区间

划分字母区间

难度:

标签:

题目描述

代码结果

运行时间: 25 ms, 内存: 16.1 MB


/*
 * 题目思路:
 * 1. 使用流操作遍历字符串s,记录每个字母最后出现的位置。
 * 2. 然后再次使用流操作遍历字符串,根据每个字母的最后出现位置,将字符串划分为尽可能多的片段。
 * 3. 每次划分一个片段的结束位置为当前片段内所有字母的最远位置。
 * 4. 记录每个片段的长度。
 */

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Solution {
    public List<Integer> partitionLabels(String s) {
        // 记录每个字母最后出现的位置
        int[] lastIndex = new int[26];
        IntStream.range(0, s.length()).forEach(i -> lastIndex[s.charAt(i) - 'a'] = i);

        int[] start = {0};
        int[] end = {0};

        // 使用流操作划分片段
        return IntStream.range(0, s.length()).mapToObj(i -> {
            end[0] = Math.max(end[0], lastIndex[s.charAt(i) - 'a']);
            if (i == end[0]) {
                int length = end[0] - start[0] + 1;
                start[0] = i + 1;
                return length;
            }
            return 0;
        }).filter(length -> length > 0).collect(Collectors.toList());
    }
}

解释

方法:

这个题解使用了贪心算法和哈希表的思路。首先遍历字符串,用两个哈希表 start 和 end 分别记录每个字符第一次出现的下标和最后一次出现的下标。然后再次遍历字符串,用一个变量 temp_end 记录当前片段的结束下标,初始化为第一个字符最后出现的下标。遍历过程中,如果当前下标小于 temp_end,说明当前字符属于当前片段,并更新 temp_end 为当前字符最后出现的下标和 temp_end 的较大值。如果当前下标等于 temp_end,说明当前片段结束,将片段长度加入结果数组,并将 temp_end 更新为下一个字符最后出现的下标。最后返回结果数组即可。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在构建`start`和`end`哈希表时,为什么选择记录每个字符的最后出现位置和第一次出现位置?这些信息是如何助于确定分割点的?
在`start`和`end`哈希表中记录每个字符的第一次和最后一次出现的位置,主要是为了确定每个字符在字符串中的覆盖范围。这样做的目的是在后续遍历中能够确定每个片段的最大范围,即如果一个片段起始于某个字符,那么这个片段至少要扩展到该字符的最后出现位置,以确保该字符不会在其他片段中出现。这种信息帮助我们在第二次遍历时,通过不断更新片段的结束位置`temp_end`,来确定是否可以结束当前片段并开始一个新的片段。
🦆
代码中的`temp_end`变量是如何帮助确定当前片段的终止位置的?请解释其在逻辑流程中的作用。
`temp_end`变量在代码中扮演着当前处理片段的潜在终止位置的角色。在遍历字符串的过程中,如果遇到的字符在之前的字符的最后出现位置之前,我们就更新`temp_end`为当前字符的最后出现位置和已知的`temp_end`之间的较大值。这个更新过程确保了当前片段包含了所有必须在这个片段结束之前处理的字符。当遍历的当前位置`i`达到了`temp_end`时,说明没有更多字符需要包含在当前片段中,因此可以结束这个片段并将其长度记录下来,然后开始新的片段。
🦆
当`i`等于`temp_end`时,代码是如何处理并确定一个片段已经结束的?这里的逻辑是否保证了所有字符最多只出现在一个片段中?
当`i`等于`temp_end`时,表明当前字符是当前片段的最后一个字符,因为`temp_end`是这个片段中所有字符的最后出现位置的最大值。在这种情况下,代码将当前片段的长度计算出来并加入到结果数组中,然后`i`增加1以开始新的片段。这个逻辑确实保证了每个字符只在一个片段中出现,因为每个片段的界定基于字符的最后出现位置,确保了字符不会在后续的片段中再次出现。
🦆
在处理字符串分割时,代码为什么需要再次遍历字符串?这个过程是否可以与第一次遍历合并以优化算法?
代码中的第一次遍历用于构建`end`哈希表,确定每个字符的最后出现位置;而第二次遍历则是基于这些信息来确定片段的边界。理论上,如果我们在第一次遍历时同时维护一个实时更新的`temp_end`,则可能在一个遍历中完成任务。然而,这样做可能需要更复杂的逻辑来同时处理字符最后出现位置的记录和片段的确定。实际上,两次遍历的方法因其清晰和易于理解的逻辑而更常用,尽管在某些情况下,合并为一次遍历可能略微提高效率。

相关问题

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

 

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

 

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104