String Summary


Summary of String.

LeetCode 344 Reverse String

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        left = 0
        right = len(s)-1
        while left<right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1

LeetCode 541 Reverse String II

class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        def reverse(t):
            left = 0
            right = len(t)-1
            while left<right:
                t[left], t[right] = t[right], t[left]
                left += 1
                right -= 1
            return t

        res = list(s)
        for i in range(0, len(res), 2*k):
            res[i:i+k] = reverse(res[i:i+k])
        
        return ''.join(res)

JZ Offer 5

class Solution:
    def replaceSpace(self, s: str) -> str:
        cnt = s.count(' ')
        res = list(s)
        res.extend([' '] * cnt * 2)
        left, right = len(s)-1, len(res)-1

        while left >= 0:
            if res[left] != ' ':
                res[right] = res[left]
                right -= 1
            else:
                res[right-2: right+1] = '%20'
                right -= 3
            left -= 1
        
        return ''.join(res)

LeetCode 151 Reverse Words in a String

class Solution:
    def trim_space(self, s):
        n = len(s)
        left = 0
        right = n-1
        while s[left] == ' ':
            left += 1
        while s[right] == ' ':
            right -= 1
        tmp = []
        while left <= right:
            if s[left] != ' ':
                tmp.append(s[left])
            elif tmp[-1] != ' ':
                tmp.append(s[left]) # add inner space
            left += 1
        return tmp

    def reverse(self, t, left, right):
        while left < right:
            t[left], t[right] = t[right], t[left]
            left += 1
            right -= 1
        return None
    
    def reverse_word(self, t):
        start = 0
        end = 0
        n = len(t)
        while start<n:
            while end < n and t[end] != ' ': # determine words
                end += 1
            self.reverse(t, start, end-1)
            start=end+1
            end+=1
        return None

    def reverseWords(self, s: str) -> str:
        l = self.trim_space(s)
        self.reverse(l, 0, len(l)-1) # reverse all characters
        self.reverse_word(l) # reverse back each word
        return ''.join(l)

JZ Offer 58 II

class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        s = list(s)
        s[:n] = list(reversed(s[:n]))
        s[n:] = list(reversed(s[n:]))
        s.reverse()
        return ''.join(s)
class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        def reverse_fn(s, left, right):
            while left < right:
                s[left], s[right] = s[right], s[left]
                left += 1
                right -= 1
                
        s = list(s)
        end = len(s)-1
        reverse_fn(s, 0, n-1)
        reverse_fn(s, n, end)
        reverse_fn(s, 0, end)
        return ''.join(s)

LeetCode 28 Implement strStr()

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        n = len(haystack)
        m = len(needle)
        for i in range(n-m+1):
            if haystack[i:i+m] == needle:
                return i
        return -1
# KMP
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        n = len(haystack)
        m = len(needle)
        if m == 0: return 0
        tab =self.prefix_table(needle)
        p = -1
        for i in range(n):
            while p > -1 and haystack[i] != needle[p+1]:
                p=tab[p]
            if haystack[i] == needle[p+1]:
                p += 1
            if p == m-1: # p moving to the end of the tab
                return i-m+1 # first index
        return -1

    def prefix_table(self, needle):
        a = len(needle)
        k = -1
        tab = ['' for i in range(a)]
        tab[0] = k
        for i in range(1,a):
            while k > -1 and needle[k+1] != needle[i]:
                k = tab[k]
            if needle[k+1] == needle[i]:
                k += 1
            tab[i] = k
        return tab

LeetCode 459 Repeated Substring Pattern

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        n = len(s)
        for i in range(1, n//2+1): # pattern length, cannot be longer than a half of s
            if n % i == 0: # s must be a multiple of the pattern
                if all(s[j] == s[j-i] for j in range(i, n)):
                    return True
        return False
# KMP
class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:  
        if len(s) == 0:
            return False
        tab = self.prefix_table(s)
        if tab[-1] != -1 and len(s) % (len(s) - (tab[-1] + 1)) == 0:
            return True
        return False
    
    def prefix_table(self, s):
        tab = [0] * len(s)
        tab[0] = -1
        j = -1
        for i in range(1, len(s)):
            while j >= 0 and s[i] != s[j+1]:
                j = tab[j]
            if s[i] == s[j+1]:
                j += 1
            tab[i] = j
        return tab

LeetCode 3 Longest Substring Without Repeating Characters

Longest Substring Without Repeating Characters (Medium) [link]

Using siding window and set. We choose set because we want unique characters in the substring and we are not counting occurrences. Time complexity O(n).

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        n = len(s)
        subset = set()
        right = -1
        ans = 0
        
        for i in range(n):
            if i != 0:
                subset.remove(s[i - 1])
            while right + 1 < n and s[right + 1] not in subset:
                subset.add(s[right + 1])
                right += 1
            ans = max(ans, right - i + 1)
        return ans

LeetCode 438 Find All Anagrams in a String

Find All Anagrams in a String (Medium) [link]

1] Time complexity O(nk). It exceeds the time limit because the input s string could be very long.

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        length = len(p)
        res = []
        for i in range(len(s)-length+1):
            HashTable = [0]*26
            sub = s[i:i+length]
            for j in range(len(sub)):
                HashTable[ord(sub[j]) - ord('a')] += 1
            for q in range(len(p)):
                HashTable[ord(p[q]) - ord('a')] -= 1
            res.append(i)
            for l in HashTable:
                if l != 0:
                    res.remove(i)
                    break
        return res

2] Using sliding window and dictionary.

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        len_s, len_p = len(s), len(p)
        cntp = collections.Counter(p)
        res = []
        right = len_p
        left = 0
        cnt = collections.Counter(s[left:right])
        if cntp == cnt:
            res.append(left)

        while right < len(s):
            cnt[s[right]] += 1
            cnt[s[left]] -= 1
            if cnt[s[left]] == 0:
                del cnt[s[left]]
            if cntp == cnt:
                res.append(left+1)
            right += 1
            left += 1
        return res
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        np = len(p)
        counterp = collections.Counter(p)
        right = np-1
        left = 0
        counters = collections.Counter(s[left:right])
        ans = []
        while right < len(s):
            counters[s[right]] += 1
            if counters == counterp:
                ans.append(left)
            counters[s[left]] -= 1
            if counters[s[left]] == 0:
                del counters[s[left]]
            left += 1
            right += 1
        return ans
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        len_s, len_p = len(s), len(p)
        res = []
        tab_s, tab_p = [0] * 26, [0] * 26

        if len_s < len_p:
            return res
        
        for i in range(len_p):
            tab_s[ord(s[i]) - ord('a')] += 1
            tab_p[ord(p[i]) - ord('a')] += 1

        if tab_s == tab_p:
            res.append(0)

        for i in range(len_p, len_s):
            tab_s[ord(s[i-len_p]) - ord('a')] -= 1
            tab_s[ord(s[i]) - ord('a')] += 1

            if tab_s == tab_p:
                res.append(i-len_p+1)
        
        return res

LeetCode 567 Permutation in String

Permutation in String (Medium) [link]

Use sliding window of length |s1| to move on s2, and dictionary to determine the occurrence of characters. We choose dictionary here because we need the occurrence to be exactly the same.

class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        counter1 = collections.Counter(s1)
        n1 = len(s1)
        right = n1 - 1
        left = 0
        counter2 = collections.Counter(s2[left:right])
        
        while right < len(s2):
            counter2[s2[right]] += 1
            
            if counter1 == counter2:
                return True

            counter2[s2[left]] -= 1

            if counter2[s2[left]] == 0:
                del counter2[s2[left]]

            left += 1
            right += 1
        
        return False

  TOC