周賽354。我又搞出一個沒看到人用的解法,還真是神奇。

題目

輸入字串word,還有一個字串陣列forbidden。

如果一個字串的所有子字串都不包含在forbidden中,則稱為有效的

求word的最長的有效子字串長度。

解法

forbidden[i]的長度最多只到10,這肯定是可以利用的地方。

若某個子字串sub包含了被禁止的字串,則以sub為基礎擴展的其他子陣列也同樣無效。
以範例1為例:

word = “cbaaaabc”, forbidden = [“aaa”,”cb”]
“aaa”是無效的,因此由”aaa”擴展出去的其他子字串也是無效的
包含”cbaaa”, “baaa”, “aaaa”等等
同理,”cb”無效,因此”cdb”, “cbaa”等也都無效

在word = “aaaxxx…”, forbidden = [“aaa”]這種例子中,從左邊向右配對:

加入word[0],”a”有效,繼續向右擴展
加入word[1],”aa”有效,繼續向右擴展
加入word[2],”aaa”無效,所有以”aaa”繼續擴展的都無效,縮減左邊界,剩”aa”
加入word[3],”aax”有效,繼續向右擴展

結論:若字串完全包含了某個被禁止的子字串word[i,j],則有效的子字串左邊界至少為i+1。

枚舉每個索引i作為左邊界,檢查長度為10以內的子陣列是否在forbidden之中,若存在則將子字串區間[i,j]保存到ban之中。
先將被禁止的區間以右邊界排序。由左到右枚舉右邊界right,並找到所有右邊界小於等於right的區間,並以該區間的左邊界更新有效左邊界left。最後[left,right]就是有效的子字串區間,以此更新答案。
最差情況下,right剛好也被禁止,而左邊界會變成left=right+1,得到有效區間長度為0,也就是空字串。

M為max(len(forbidden[i])),對於每個索引要產生M個子字串,每次O(M),共O(N * M^2)。
最多會產生N*M個被禁止的區間ban。
瓶頸在於排序ban,時間複雜度O(N*M log N*M)。
空間複雜度O(N*M)。

class Solution:
    def longestValidSubstring(self, word: str, forbidden: List[str]) -> int:
        N=len(word)
        s=set(forbidden)
        ban=[]
        for i in range(N):
            for j in range(i,min(N,i+10)):
                if word[i:j+1] in s:
                    ban.append([i,j])
                    
        ban.sort(key=itemgetter(1))
        
        ans=0
        left=0
        i=0
        for right in range(N):
            while i<len(ban) and ban[i][1]<=right:
                left=max(left,ban[i][0]+1)
                i+=1
            ans=max(ans,right-left+1)
            
        return ans

其實根本不用預處理所有禁止的區間。
只要在枚舉右邊界right的時候檢查10種子字串是否被禁止,若為真則更新左邊界。

時間複雜度O(F + N * M^2),其中F為forbidden長度,N為word長度,M為max(len(forbidden[i]))。
空間複雜度O(F)。

class Solution:
    def longestValidSubstring(self, word: str, forbidden: List[str]) -> int:
        N=len(word)
        s=set(forbidden)

        ans=0
        left=0
        for right in range(N):
            for i in range(0,10):
                j=right-i
                if j<0:
                    break
                if word[j:right+1] in s:
                    left=max(left,j+1)
                    break
            ans=max(ans,right-left+1)
                
        return ans