力扣:763. 划分字母区间(贪心,哈希)

2023-12-28 18:02:10

题目:

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

示例 1:
输入:s = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”、“defegde”、“hijhklij” 。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 这样的划分是错误的,因为划分的片段数较少。
示例 2:

输入:s = “eccbbbbdec”
输出:[10]

提示:

1 <= s.length <= 500
s 仅由小写英文字母组成

思路:

题目要求同一字母最多出现在一个片段中,那么如何把同一个字母的都圈在同一个区间里呢?

如果没有接触过这种题目的话,还挺有难度的。

在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。

可以分为如下两步:

  • 统计每一个字符最后出现的位置
  • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

以S = "ababcbacadefe"为例,进行详细分析如何切割。

在这里插入图片描述
首先进行遍历,遍历到第一个字符串为a, a的最后出现位置的下标为8,因为题目要求同一字母最多出现在一个片段,所以为了第一个片段包含所有的a,继续向下进行遍历,遍历到b,b的最后出现的位置的下标为5,继续遍历 此时需要包含a的同时包含所有的b(因为b的最远下标小于a,所以只要包含了所有的a则必定包含所有的b),继续向下遍历,又新加入了a和b,之后又加入了c,同理,接下来的遍历需要包含a,b的同时包含c。直到包含了所有遍历时加入的元素时,开始进行切割,这就是所求的一个片段的长度。下一个片段切割同理。

理解了切割过程,代码依然不好写,如何得知字符最后出现的位置?得知了位置又人如何切割?

这里需要用到哈希来获得字符最后出现的位置,通过字符串的ASCLL,减去a的ASCLL,每次遍历来更新字符串最后出现的位置。
代码如下:

        # 初始化一个长度为26的数组,用于存储每个字母最后出现的位置
        path = [0] * 26
        for i in range(len(s)):
            # 更新字母最后出现的位置
            path[ord(s[i]) - ord('a')] = i

ord()函数的作用是获取字母的ASCLL值

接下来进行切割
每次遍历更新当前区间的右边界,直到右边界等于当前节点的值,则找到了切割点。
代码如下:

        result = []
        left = 0
        right = 0
        for i in range(len(s)):
            # 更新当前区间的右边界
            right = max(path[ord(s[i]) - ord('a')], right)
            # 如果右边界等于当前位置,说明找到了一个划分点,将当前区间长度加入结果中,并更新左边界
            if right == i:
                result.append(right - left + 1)
                left = i + 1

本题还有一个思路,跟之前的452. 用最少数量的箭引爆气球435. 无重叠区间一样的思路,不过写起来很麻烦这里就不进行详细说明,代码和注释在下面完整代码中给出,思路见:452. 用最少数量的箭引爆气球(贪心)

完整代码:

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        # 初始化一个长度为26的数组,用于存储每个字母最后出现的位置
        path = [0] * 26
        for i in range(len(s)):
            # 更新字母最后出现的位置
            path[ord(s[i]) - ord('a')] = i
        result = []
        left = 0
        right = 0
        for i in range(len(s)):
            # 更新当前区间的右边界
            right = max(path[ord(s[i]) - ord('a')], right)
            # 如果右边界等于当前位置,说明找到了一个划分点,将当前区间长度加入结果中,并更新左边界
            if right == i:
                result.append(right - left + 1)
                left = i + 1
        return result

贪心(版本二)与452.用最少数量的箭引爆气球435.无重叠区间相同的思路。

class Solution:
    def countLabels(self, s):
        # 初始化一个长度为26的区间列表,初始值为负无穷
        hash = [[float('-inf'), float('-inf')] for _ in range(26)]
        hash_filter = []
        # 遍历字符串s,更新每个字母的区间范围
        for i in range(len(s)):
            if hash[ord(s[i]) - ord('a')][0] == float('-inf'):
                hash[ord(s[i]) - ord('a')][0] = i
            hash[ord(s[i]) - ord('a')][1] = i
        # 将没有出现过的字母的区间过滤掉
        for i in range(len(hash)):
            if hash[i][0] != float('-inf'):
                hash_filter.append(hash[i])
        return hash_filter

    def partitionLabels(self, s):
        res = []
        hash = self.countLabels(s)
        hash.sort(key=lambda x: x[0])  # 按左边界从小到大排序
        rightBoard = hash[0][1]  # 记录当前区间的最大右边界
        leftBoard = 0
        for i in range(1, len(hash)):
            if hash[i][0] > rightBoard:  # 如果出现分割点,将当前区间长度加入结果中,并更新左边界
                res.append(rightBoard - leftBoard + 1)
                leftBoard = hash[i][0]
            rightBoard = max(rightBoard, hash[i][1])  # 更新当前区间的最大右边界
        res.append(rightBoard - leftBoard + 1)  # 将最后一个区间的长度加入结果中
        return res

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1),使用的hash数组是固定大小

文章来源:https://blog.csdn.net/2301_77160836/article/details/135272655
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。