Untitled

 avatar
unknown
python
15 days ago
2.8 kB
4
Indexable
# this is basically finding and merging intervals:
# 1. we track an interval [x, y] per character c in s where x is the first occurence of c and y is the last occurrence
# 2. we then merge overlapping intervals and return the lengths of each disjoint interval
# the tricky part is ensuring O(n) runtime because normally merge intervals requires some sorting which is O(nlogn)
# however, we can take advantage of the fact that letters appear sequentially and no intervals will ever have the same start point
# this means that the earlier letters will always have distinct start positions and we can use a basic list to track intervals
# any new letter we find will have its interval added to the end of the list (i.e. we open a new interval)
# to ensure O(1) lookup of letters, we have a map from character -> position of its tracked interval in the interval list
#
# O(n) time, O(1) space
# where n is the length of s
# the space complexity of O(1) is because the intervals list scales with the number of distinct letters in s (since we create an interval per distinct character)
# however, since there are only 26 possible characters, the intervals list will always contain between 0 - 26 items
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        if len(s) <= 1:
            return len(s)

        # list of intervals that represent the first and last positions of a letter in s
        intervals = []

        # letter -> its position in the intervals array
        letters_seen = {}

        for i, char in enumerate(s):
            if char in letters_seen:
                # update the end of the interval
                intervals[letters_seen[char]][1] = i
            else:
                intervals.append([i, i])
                letters_seen[char] = len(intervals) - 1

        return [i[1] - i[0] + 1 for i in self.merge_intervals(intervals)]

    def merge_intervals(self, intervals):
        if len(intervals) <= 1:
            return intervals

        result = [intervals[0]]
        for current in range(1, len(intervals)):
            if self.intersects(result[-1], intervals[current]):
                result[-1] = self.merge(result[-1], intervals[current])
            else:
                result.append(intervals[current])

        return result

    def intersects(self, x, y):
        x_start, x_end = x
        y_start, y_end = y

        return (
            (x_start >= y_start and x_start <= y_end)
            or (x_end >= y_start and x_end <= y_end)
            or (y_start >= x_start and y_start <= x_end)
            or (y_end >= x_start and y_end <= x_end)
        )

    def merge(self, x, y):
        return [min(x[0], y[0]), max(x[1], y[1])]
Editor is loading...
Leave a Comment