Given a list of words, each word consists of English lowercase letters.
Let's say word1 is a predecessor of word2 if and only if we can add exactly one letter anywhere in word1 to make it equal to word2.  For example, "abc" is a predecessor of "abac".
A word chain is a sequence of words [word_1, word_2, ..., word_k] with k >= 1, where word_1 is a predecessor of word_2, word_2 is a predecessor of word_3, and so on.
Return the longest possible length of a word chain with words chosen from the given list of words.
 
Example 1:
Input: ["a","b","ba","bca","bda","bdca"]
Output: 4
Explanation: one of the longest word chain is "a","ba","bda","bdca".

 
Note:

1 <= words.length <= 1000
1 <= words[i].length <= 16
words[i] only consists of English lowercase letters.

Difficulty: interview

Starter code:

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        

Solutions:
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        by_length = collections.defaultdict(set)
        for word in words:
            by_length[len(word)].add(word)

        longest = 1
        seen = {*()}
        mx = len(by_length)
        mn = min(by_length)

        for length in sorted(by_length, reverse=True):
            if length - mn < longest:
                break
            for word in by_length[length]:
                if length - mn < longest:
                    break
                if word in seen:
                    continue
                stk = [(word, length, 1)]
                while stk:
                    word, k, n = stk.pop()
                    seen.add(word)
                    if n > longest:
                        longest = n
                    for i in range(k):
                        pre = word[:i] + word[i+1:]
                        if pre not in seen and pre in by_length[k-1]:
                            stk.append((pre, k-1, n+1))
                if longest == mx:
                    return longest

        return longest   

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        by_length = collections.defaultdict(set)
        for word in words:
            by_length[len(word)].add(word)

        longest = 1
        seen = {*()} # set()
        mx = len(by_length)
        mn = min(by_length)
        # in descending order
        for length in sorted(by_length, reverse=True):
            if length - mn < longest:
                break
            for word in by_length[length]:
                if length - mn < longest:
                    break
                if word in seen:
                    continue
                stk = [(word, length, 1)]
                while stk:
                    word, k, n = stk.pop()
                    seen.add(word)
                    if n > longest:
                        longest = n
                    for i in range(k):
                        pre = word[:i] + word[i+1:]
                        if pre not in seen and pre in by_length[k-1]:
                            stk.append((pre, k-1, n+1))
               # if longest == mx:
               #     return longest

        return longest   

class Solution:
    def issubsequence(self, str1, str2):
        str1_pointer = 0
        for i in range(len(str2)):
            if str2[i] == str1[str1_pointer]:
                str1_pointer += 1
            if str1_pointer == len(str1):
                return True
        return False
        
    def longestStrChain(self, words: List[str]) -> int:
        word_lengths = {}
        for i in words:
            if len(i) not in word_lengths:
                word_lengths[len(i)] = [i]
            else:
                word_lengths[len(i)].append(i)
        max_length = max(list(word_lengths.keys()))
        @lru_cache(None)
        def dp(word):
            if len(word) not in word_lengths:
                return 0
            if len(word) == max_length:
                return 1
            else:
                max_chain = 1
                for i in word_lengths[len(word) + 1]:
                    if self.issubsequence(word, i):
                        max_chain = max(max_chain, 1 + dp(i))
                return max_chain
        res = 0
        for i in words:
            res = max(res, dp(i))
        return res

class Solution:
    _max = 0
    def longestStrChain(self, words: List[str]) -> int:
        
        def dfs(cur, step):
            if cur not in s:
                return
            if len(cur) == 0:
                return
            self._max = max(step, self._max)   
            for i in range(len(cur)):
                t = cur[:i] + cur[i+1:]
                dfs(t, step+1)
        s = set(words) 
        
        for w in sorted(words, key = len, reverse = True):
            dfs(w, 1)
        return self._max


from collections import defaultdict


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = defaultdict(lambda: 0)
        for word in sorted(words, key=len):
            dp[''.join(sorted(word))] = max(dp[''.join(sorted(word[:idx] + word[idx + 1:]))] for idx in range(len(word))) + 1
        return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def compare(s1,s2):
            if abs(len(s1)-len(s2)) != 1:
                return False
            if len(s2) > len(s1):
                s1,s2=s2,s1
            broke = False
            s1,s2=list(s1),list(s2)
            while s1 and s2:
                a,b = s1.pop(), s2.pop()
                if a != b:
                    if broke or s1.pop() != b:
                        return False
                    broke = True
            return True
        
        G = collections.defaultdict(list)
        i = float('inf')
        j = float('-inf')
        for w in words:
            i = min(i, len(w))
            j = max(j, len(w))
            G[len(w)].append((w,1))
        res = 1
        m = i+1
        while m <= j:
            for a,w1 in enumerate(G[m]):
                for b,w2 in enumerate(G[m-1]):
                    if compare(w1[0],w2[0]):
                        G[m][a] = (w1[0],1+w2[1])
                        res = max(1+w2[1],res)
            m+=1
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def precedent(w1_c,w2_c):
            if len(w1_c)>len(w2_c):
                return False
            
            count = 0
            for c in w2_c:
                if c in w1_c:
                    count+= w2_c[c]-w1_c[c]
                else:
                    count+= w2_c[c]
            return count==1
                    
        minL = float('inf')
        maxL = float('-inf')
        
        longest = 1
        
        len_count = collections.defaultdict(list)
        subsequent_of_word = collections.defaultdict(int)
        count_of_word = collections.defaultdict(Counter)
        
        for w in words:
            len_count[len(w)].append(w)
            subsequent_of_word[w] = 1
            count_of_word[w] = Counter(w)
            maxL= max(maxL,len(w))
            minL= min(minL,len(w))

        for i in range(maxL-1,minL-1,-1):
            if i in len_count:
                if i+1 in len_count:
                    for w1 in len_count[i]:
                        for w2 in len_count[i+1]:
                            if precedent(count_of_word[w1],count_of_word[w2]):
                                subsequent_of_word[w1] = max(subsequent_of_word[w1], subsequent_of_word[w2]+1)
                                longest = max(longest, subsequent_of_word[w1])
                                
                else:
                    for w in len_count[i]:
                        subsequent_of_word[w]=1
        
        return longest
                        
        
                    
            
        
        
            
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x), reverse=True)
        minLen = len(words[-1])
        
        dic = set(words)
        seen = set()
        self.result = 0
        for word in words:
            if word not in seen:
                length = 1
                self.dfs(word, dic, seen, minLen, length)
        
        
        return self.result
    
    def dfs(self, word, dic, seen, minLen, length):
        self.result = max(self.result, length)
        if len(word) == minLen:
            return
        
        seen.add(word)
        
        for i in range(len(word)):
            new = word[:i] + word[i+1:]
            if new in dic:
                self.dfs(new, dic, seen, minLen, length+1)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        all_chains = []
        for word in words:
            all_chains.extend(self.createChains(word, words))
        return max(map(len,all_chains))

    def createChains(self, word, words):
        result = []
        step = [word]
        dictionary = set(words)

        self.helper(word, step, dictionary, result)
        return result

    def helper(self, word, step, dictionary, result):
        result.append(step[:])

        for i in range(len(word)-1,-1, -1):
            substring = word[:i]+word[i+1:]

            if substring in dictionary: 
                step.append(substring) # choose
                self.helper(substring, step, dictionary, result) # explore
                step.pop() # unchoose

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def precedent(w1_c,w2_c):
            count = 0
            for c in w2_c:
                if c in w1_c:
                    count+= w2_c[c]-w1_c[c]
                else:
                    count+= w2_c[c]
            return count==1
                    
        minL = float('inf')
        maxL = float('-inf')
        
        longest = 1
        
        len_count = collections.defaultdict(list)
        subsequent_of_word = collections.defaultdict(int)
        count_of_word = collections.defaultdict(Counter)
        
        for w in words:
            len_count[len(w)].append(w)
            subsequent_of_word[w] = 1
            count_of_word[w] = Counter(w)
            maxL= max(maxL,len(w))
            minL= min(minL,len(w))

        for i in range(maxL-1,minL-1,-1):
            if i in len_count:
                if i+1 in len_count:
                    for w1 in len_count[i]:
                        for w2 in len_count[i+1]:
                            if precedent(count_of_word[w1],count_of_word[w2]):
                                subsequent_of_word[w1] = max(subsequent_of_word[w1], subsequent_of_word[w2]+1)
                                longest = max(longest, subsequent_of_word[w1])
                                
                else:
                    for w in len_count[i]:
                        subsequent_of_word[w]=1
        
        return longest
                        
        
                    
            
        
        
            
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        self._len_to_words = collections.defaultdict(list)
        for w in words:
            self._len_to_words[len(w)].append(w)
        self._cache = {}
            
        self._visited = {}
        max_so_far = 0
        for w in words:
            max_so_far = max(max_so_far, self._find_longest(w))
        return max_so_far
    
    def _find_longest(self, w):
        if w in self._visited:
            return self._visited[w]
        
        len_w = len(w)
        if len_w+1 not in self._len_to_words:
            self._visited[w] = 1
            return 1
        
        max_chain_len = 1
        for cand in self._len_to_words[len_w+1]:
            if self.is_predecessor(w, cand):
                max_chain_len = max(max_chain_len, 1+self._find_longest(cand))
        
        self._visited[w] = max_chain_len
        return max_chain_len
        
    def is_predecessor(self, w1, w2):
        if (w1, w2) in self._cache:
            return self._cache[(w1, w2)]
        
        is_p = True
        if len(w1) + 1 != len(w2):
            is_p = False
        else:
            count = {}
            for c in w1:
                if c in count:
                    count[c] += 1
                else:
                    count[c] = 1
            found_diff = False
            for c in w2:
                if c in count:
                    count[c] -= 1
                    if count[c] == 0:
                        del count[c]
                else:
                    if found_diff:
                        is_p = False
                        break
                    else:
                        found_diff = True
                        count[c] = 1
        if is_p:
            keys = list(count.keys())
            is_p = len(keys) == 1 and count[keys[0]] == 1
        
        self._cache[(w1, w2)] = is_p
        return is_p

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def precedent(w1_c,w2_c):
            count = 0
            for c in w2_c:
                if c in w1_c:
                    count+= w2_c[c]-w1_c[c]
                else:
                    count+= w2_c[c]
            return count==1
                    
        minL = float('inf')
        maxL = float('-inf')
        
        longest = 1
        
        len_count = collections.defaultdict(list)
        subsequent_of_word = collections.defaultdict(int)
        count_of_word = collections.defaultdict(Counter)
        
        for w in words:
            len_count[len(w)].append(w)
            subsequent_of_word[w] = 1
            count_of_word[w] = Counter(w)
            maxL= max(maxL,len(w))
            minL= min(minL,len(w))

        for i in range(maxL,minL-1,-1):
            if i in len_count:
                if i+1 in len_count:
                    for w1 in len_count[i]:
                        for w2 in len_count[i+1]:
                            if precedent(count_of_word[w1],count_of_word[w2]):
                                subsequent_of_word[w1] = max(subsequent_of_word[w1], subsequent_of_word[w2]+1)
                                longest = max(longest, subsequent_of_word[w1])
                                
                else:
                    for w in len_count[i]:
                        subsequent_of_word[w]=1
        
        return longest
                        
        
                    
            
        
        
            
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def f(x, y):
            if len(x) != len(y) - 1:
                return False
            p = len(x)
            for i in range(len(x)):
                if x[i] != y[i]:
                    p = i
                    break
            
            for i in range(p, len(x)):
                if x[i] != y[i + 1]:
                    return False
            return True

        words.sort(key=lambda x:len(x))
        d = {words[i]: i for i in range(len(words))}
        
        a = []
        for _ in range(len(words)):
            a.append([False] * len(words))
            
        
        for j in range(len(words)):
            for k in range(len(words[j])):
                i = d.get(words[j][:k] + words[j][k + 1:], -1)
                if i >= 0:
                    a[i][j] = True
                
        # print(a)
                
        dp = [1] * len(words)
        for i in range(1, len(words)):
            for j in range(i):
                if a[j][i]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        hashtable = {}
        record = [1 for i in range(17)]
        for w in words:
            if(len(w) not in hashtable):
                hashtable[len(w)] = [{w:1}]
            else:
                hashtable[len(w)].append({w:1})
        # print(hashtable)
        for i in range(17):
            if(i in hashtable):
                if(i-1 in hashtable):
                    # print(hashtable[i])
                    n = 0
                    tmp_max = 0
                    for word in hashtable[i]:
                        word = list(word.keys())[0]
                        m = 0
                        for preword in hashtable[i-1]:
                            preword =  list(preword.keys())[0]
                            if(self.ispreword(preword,word)):
                                hashtable[i][n][word] = max(hashtable[i-1][m][preword] + 1, hashtable[i][n][word] )
                                tmp_max = max(tmp_max, hashtable[i][n][word])
                            m+=1
                        n+=1
                    record[i] = tmp_max
                    # print( hashtable[i])
        
        # print(record)
        return max(record)
    
    def ispreword(self,preword, word):
        check = [i for i in word]
        for w in preword:
            if w not in check:
                return False
            else:
                check.remove(w)
        return True

from functools import lru_cache
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        d = collections.defaultdict(list)
        for w in words:
            d[len(w)].append(w)
        
        @lru_cache(None)
        def dp(w):
            if len(w) == 1:
                return 1
            for candidate in d[len(w) - 1]:
                for i in range(len(w)):
                    if candidate == w[:i] + w[i+1:]:
                        return dp(candidate) + 1
            return 1
        
        return max(dp(i) for i in words)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        N = len(words)
        if N<2: return N
        
        words.sort(key=len)
        Table = {word:1 for word in words}
        result = 1
        for word in words:
            for j in range(len(word)):
                subword = word[:j] + word[j+1:]
                if subword in Table and Table[subword]>=Table[word]:
                    Table[word] = Table[subword] + 1
            if Table[word] > result:
                result = Table[word]
        
        return result

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # group by length
        length2words = defaultdict(list)
        for w in words:
            length2words[len(w)].append(w)
    
        # for each length l -> check words in previous l-1 that form longer chain
        word2chain = dict()
        for l in sorted(length2words):
            for w in length2words[l]:
                word2chain[w] = 1
                if l-1 not in length2words:
                    continue
                for prev_w in length2words[l-1]:
                    if word2chain[prev_w]+1 < word2chain[w]:
                        continue
                    if self.isPredecessor(prev_w, w):
                        word2chain[w] = max(word2chain[w], word2chain[prev_w]+1)
        
        return max(word2chain.values())
        
    def isPredecessor(self, word1, word2):
        # try skip letter in word2
        for i in range(len(word2)):
            skip = word2[:i]+word2[i+1:]
            if skip == word1:
                return True
        return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # group by length
        length2words = defaultdict(list)
        for w in words:
            length2words[len(w)].append(w)
    
        # for each length l -> check words in previous l-1 that form longer chain
        word2chain = {w: 1 for w in words}
        for l in sorted(length2words):
            if l-1 not in length2words:
                continue
            for w in length2words[l]:
                for prev_w in length2words[l-1]:
                    if word2chain[prev_w]+1 < word2chain[w]:
                        continue
                    if self.isPredecessor(prev_w, w):
                        word2chain[w] = max(word2chain[w], word2chain[prev_w]+1)
        
        return max(word2chain.values())
        
    def isPredecessor(self, word1, word2):
        # try skip letter in word2
        for i in range(len(word2)):
            skip = word2[:i]+word2[i+1:]
            if skip == word1:
                return True
        return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def longest_chain(word, word_set, memo):
            
            if word not in word_set:
                return 0
            
            if word not in memo:
                longest = 1
                for i in range(len(word)):
                    next_word = word[:i] + word[i+1:]
                    longest = max(1 + longest_chain(next_word, word_set, memo), longest)
                memo[word] = longest
                
            return memo[word]
        
        
        
        longest = 0
        for word in words:
            longest = max(longest_chain(word, set(words), {}), longest)
        return longest
    
#     n = len(words)
#     m = max length of word in words
    
#     Time: O(n * m!)
#     Space: O(n*m)
    


from collections import defaultdict, deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) <= 1:
            return len(words)
        
        graph_dict, len_words_dict = self.getGraphDict(words)
 
        word_lengths = list(len_words_dict.keys())
        word_lengths.sort()
        
        length_index_dict = defaultdict()
        for i in range(len(word_lengths)):
            length_index_dict[word_lengths[i]] = i
        
        return self.bfsWordChain(graph_dict, len_words_dict, word_lengths, length_index_dict)
    
    def bfsWordChain(self, graph_dict, len_words_dict, word_lengths, length_index_dict):
        word_lenghts_index = 0
        initial_words = len_words_dict[word_lengths[0]]
        
        max_possible_length = len(word_lengths)
        curr_length = 1

        visited = set()
        
        queue = deque()
        for word in initial_words:
            queue.append((word, 1))
            visited.add(word)
        
        max_len_so_far = 1
        
        while queue:
            this_item = queue.popleft()
            this_word = this_item[0]
            this_chain_len = this_item[1]
            max_len_so_far = max(max_len_so_far, this_chain_len)
            this_item_possible_length = this_chain_len + len(word_lengths) - length_index_dict[len(this_word)]
            if this_item_possible_length > max_len_so_far:
                neighbors = graph_dict[this_word]
                for neighbor in neighbors:
                    if neighbor not in visited:
                        visited.add(neighbor)
                        queue.append((neighbor, this_chain_len + 1))

                if len(queue) == 0:
                    word_found = False
                    for word_length in word_lengths:
                        for word in len_words_dict[word_length]:
                            if word not in visited and not word_found:
                                queue.append((word, 1))
                                visited.add(word)
                                word_found = True
                                break
        
        return max_len_so_far
            
        
        

    
    def getGraphDict(self, words):
        len_words_dict = defaultdict(list)
        for word in words:
            len_words_dict[len(word)].append(word)
        word_lenghts_arr = []
        graph_dict = defaultdict(list)

        for word_length in len_words_dict:
            for predecessor in len_words_dict[word_length]:
                if word_length + 1 in len_words_dict:
                    for word in len_words_dict[word_length + 1]:
                        if self.isPredecessor(predecessor, word):
                            graph_dict[predecessor].append(word)

        return graph_dict, len_words_dict
    
    def isPredecessor(self, predecessor, word):
        predecessor_index = 0
        word_index = 0
        while predecessor_index < len(predecessor) and word_index < len(word):
            if predecessor[predecessor_index] == word[word_index]:
                predecessor_index += 1
                word_index += 1
            else:
                word_index += 1
        
        if word_index == predecessor_index + 1 or word_index == predecessor_index:
            return True
        else:
            return False


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        d = collections.defaultdict(list)
        for word in words:
            d[len(word)].append(word)
        
        lengths = sorted(list(d.keys()))
        self.mem = {}
        
        def dfs(word):
            if len(word)==lengths[-1]:
                return 1
            if word in self.mem:
                return self.mem[word]
            
            nxt_length = 0
            for nxt_word in d[len(word)+1]:
                for j in range(len(nxt_word)):
                    if word==nxt_word[:j]+nxt_word[j+1:]:
                        nxt_length = max(nxt_length, dfs(nxt_word))
            
            self.mem[word] = nxt_length+1
            return nxt_length+1
        
        res = 0
        for word in words:
            res = max(res,dfs(word))
            
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:   return 0
        words.sort(key=len)
        #print(words)
        
        def isNeighbor(a,b):
            if len(a)+1 != len(b):  return False
            if a==b[1:] or a==b[:-1]:   return True
            ia,ib = 0,0
            flag = True
            while ia < len(a):
                if a[ia]==b[ib]:
                    ia,ib = ia+1,ib+1
                elif flag:
                    flag = False
                    ib += 1
                else:
                    return False
            return True
        
        dp = [1] * len(words)
        ans = 0
        #longest so far
        for i in range(len(words)):
            for j in range(i):
                if len(words[j])+1<len(words[i]):
                    continue
                elif len(words[j])+1>len(words[i]):
                    break
                else:
                    if isNeighbor(words[j],words[i]):
                        dp[i] = max(dp[i],dp[j]+1)
            ans = max(ans,dp[i])
        #print(dp)
        return ans

class Solution:
  #[\"a\",\"b\",\"ba\",\"bca\",\"bda\",\"bdca\"]
  def can_convert(self, word1, word2):
    for missing_idx in range(len(word2)):
      if word2[:missing_idx] + word2[missing_idx+1:] == word1:
          return True
    return False
    

  def longestStrChain(self, words: List[str]) -> int:
    #{1: [0,1], 2: [2], 3: [3,4], 4: [5]}
    words_by_length = collections.defaultdict(list)
    #keep list of word idxs mapped to their length
    for idx, word in enumerate(words):
        words_by_length[len(word)].append(idx)

    #{3: [5], 4: [5], 0: [2], 1: [2], 2: [3,4]}
    #[0,0,2,1,1,2]
    adj_list = collections.defaultdict(list)
    #word idx can act as a node id

    indegree = [0]*len(words)
    for length in words_by_length:
      #edge only poss if there are words of length + 1
      if (length+1) in words_by_length:
        for word_1_idx in  words_by_length[length]:
          for word_2_idx in words_by_length[length + 1]:
            if self.can_convert(words[word_1_idx], words[word_2_idx]):
              adj_list[word_1_idx].append(word_2_idx)
              indegree[word_2_idx] += 1
        
            

    #[5]
    queue = collections.deque([word_idx for word_idx in range(len(words)) if indegree[word_idx] == 0])

    max_length = 0
    while(queue):
        length = len(queue)
        max_length += 1
        for i in range(length):
            curr_word_idx = queue.popleft()
            for successor in adj_list[curr_word_idx]:
                indegree[successor] -= 1
                if indegree[successor] == 0:
                    queue.append(successor)


    return max_length

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        d = dict()
        for word in words:
            d[word] = 1
        longest = 1
        for word in sorted(words, key=len):
            for i in range(len(word)):
                prev = word[:i] + word[i + 1:]
                if prev in d:
                    d[word] = max(d[word], d[prev] + 1)
            longest = max(longest, d[word])
        return longest

from collections import defaultdict, deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) <= 1:
            return len(words)
        
        graph_dict, len_words_dict = self.getGraphDict(words)
 
        word_lengths = list(len_words_dict.keys())
        word_lengths.sort()
        
        return self.bfsWordChain(graph_dict, len_words_dict, word_lengths)
    
    def bfsWordChain(self, graph_dict, len_words_dict, word_lengths):
        word_lenghts_index = 0
        initial_words = len_words_dict[word_lengths[0]]
        
        max_possible_length = len(word_lengths)
        curr_length = 1

        visited = set()
        
        queue = deque()
        for word in initial_words:
            queue.append((word, 1))
            visited.add(word)
        
        max_len_so_far = 1
        
        while queue:
            this_item = queue.popleft()
            this_word = this_item[0]
            this_chain_len = this_item[1]
            max_len_so_far = max(max_len_so_far, this_chain_len)
            # this_item_possible_length = this_chain_len + len(word_lengths[])
            neighbors = graph_dict[this_word]
            for neighbor in neighbors:
                if neighbor not in visited:
                    visited.add(neighbor)
                    queue.append((neighbor, this_chain_len + 1))
            
            if len(queue) == 0:
                word_found = False
                for word_length in word_lengths:
                    for word in len_words_dict[word_length]:
                        if word not in visited and not word_found:
                            queue.append((word, 1))
                            visited.add(word)
                            word_found = True
                            break
        
        return max_len_so_far
            
        
        

    
    def getGraphDict(self, words):
        len_words_dict = defaultdict(list)
        for word in words:
            len_words_dict[len(word)].append(word)
        word_lenghts_arr = []
        graph_dict = defaultdict(list)
        for word_length in len_words_dict:
            for predecessor in len_words_dict[word_length]:
                if word_length + 1 in len_words_dict:
                    for word in len_words_dict[word_length + 1]:
                        if self.isPredecessor(predecessor, word):
                            graph_dict[predecessor].append(word)

        return graph_dict, len_words_dict
    
    def isPredecessor(self, predecessor, word):
        predecessor_index = 0
        word_index = 0
        while predecessor_index < len(predecessor) and word_index < len(word):
            if predecessor[predecessor_index] == word[word_index]:
                predecessor_index += 1
                word_index += 1
            else:
                word_index += 1
        
        if word_index == predecessor_index + 1 or word_index == predecessor_index:
            return True
        else:
            return False


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # sort by decreasing length
        # define vector L of length len(words) such that it's the longest 
        # word chain starting from that word
        # then recuriosn is L_k = 1 if it is not predecessor of any 1 longer words
        # otherwise it is 1 + max L_j for j such that k is predecessor of them
        
        lengths = [len(word) for word in words]
        wordsSorted = [x for _,x in sorted(zip(lengths,words),reverse = True)]
        
        print(wordsSorted)
        L = [1]*len(wordsSorted)
        maxLength = max(lengths)
        for k in range(0, len(L)):
            lenWord = len(wordsSorted[k])
            if  lenWord == maxLength:
                L[k] = 1
            else: 
                for l in range(k):
                    if len(wordsSorted[l]) == lenWord+1 and (set(wordsSorted[l]) & set(wordsSorted[k])) == set(wordsSorted[k]):
                        L[k] = max(L[k], 1 + L[l])
        return(max(L))
                    
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        cnt = [[] for i in range(16)]
        min_l = 16
        for i in words:
            min_l = min([min_l,len(i)])
            cnt[len(i)-1].append(i)
        # print(cnt)
        def match(a,b):
            i = j = 0
            cnt = 0
            while j < len(b):
                if i < len(a) and a[i] == b[j]:
                    i += 1
                    j += 1
                else:
                    j += 1
                    cnt += 1
            return cnt == 1
            
        def topdown(curr_words):
            if len(curr_words) == 16:
                return 1
            if curr_words in dp:
                return dp[curr_words]
            
            dp[curr_words] = 1
            for i in cnt[len(curr_words)]:
                if match(curr_words,i):
                    if not i in dp:
                        dp[i] = topdown(i)+1
                    dp[curr_words] = max([dp[curr_words],dp[i]])
            return dp[curr_words]
        ans = 0
        for i in range(min_l-1,16):
            for j in cnt[i]:
                if j in dp:
                    ans = max([ans,dp[j]])
                else:
                    ans = max([ans,topdown(j)])
                # print(dp,i)
        
        return ans


from collections import deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        return self.run_graph_solution(words)
        # return self.run_dp_solution(words)
    
    def run_dp_solution(self, words):
        pass
        
    def run_graph_solution(self, words):
        graph = {word: [] for word in words}
        lengths = {k: [] for k in range(1, 17)}
        into = {}
        min_len = 17
        max_len = 1
        for word in words:
            lengths[len(word)].append(word)
            min_len = min(len(word), min_len)
            max_len = max(len(word), max_len)
                    
        for k in range(min_len, max_len):
            for word1 in lengths[k]:
                for word2 in lengths[k + 1]:
                    for i in range(len(word2)):
                        if word2[:i] + word2[i+1:] == word1:
                            graph[word1].append(word2)
                            into[word2] = True
        # return graph
        
        sources = [word for word in words if word not in into]
        max_len = 0
        for s in sources:
            max_len = max(max_len, self.run_bfs(s, graph))
        return max_len
                
    def run_bfs(self, s, graph):
        queue = deque(maxlen=len(list(graph.keys())))
        queue.appendleft((s, 1))
        summary = []
        
        while len(queue) > 0:
            current_node, cur_len = queue.pop()
            for word in graph[current_node]:
                queue.appendleft((word, cur_len + 1))
            summary.append(cur_len)
        
        return summary[-1]
    
    
            
            
        
        
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words_len = [[] for _ in range(16)]
        graph = {word: [] for word in words}
        
        for word in words:
            words_len[len(word) - 1].append(word)
    
        def predecessor(word1, word2):
            for i in range(len(word2)):
                if word2[:i] + word2[i+1:] == word1:
                    return True
            
            return False
        
        for i in range(len(words_len) - 1):
            words_1 = words_len[i]
            words_2 = words_len[i + 1]
            
            for word1 in words_1:
                for word2 in words_2:
                    if predecessor(word1, word2):
                        graph[word1].append(word2)
        

        memo = {word: -1 for word in words}
        
        def dfs(word):
            t = 1
            
            if memo[word] == -1:
                for w in graph[word]:
                    t = max(t, 1 + dfs(w))
            
                memo[word] = t
                
            return memo[word]
        
        
        lpl = 0
        
        for words in words_len:
            for word in words:
                lpl = max(lpl, dfs(word))
        
        return lpl
        
        


#Longest increasing sequence.
# Instead, you can store all the words in a hashmap with their indexes. Now, for 
# Every word, try to delete one character and see if it is present in the map. But you need to sort the array first.
from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        chain = defaultdict(list)
        def checkPredecessor(w1, w2):
            for i in range(len(w2)):
                if w2[:i] + w2[i + 1:] == w1:
                    return True
            return False
        for idx, word in enumerate(words):
            chain[len(word)].append((word, idx))
        dp = [0] * len(words)
        
        i = 1
        while(i < 17):
            for word, dp_idx in chain[i]:
                if i - 1 in chain:
                    for checkWord, dp_jdx in chain[i - 1]:
                        if checkPredecessor(checkWord, word):
                            dp[dp_idx] = max(dp[dp_idx], 1 + dp[dp_jdx])
            i += 1
        return max(dp) + 1
            
        


from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        #words.sort(key = lambda x: len(x))
        chain = defaultdict(list)
        def checkPredecessor(w1, w2):
            for i in range(len(w2)):
                if w2[:i] + w2[i + 1:] == w1:
                    return True
            return False
        for idx, word in enumerate(words):
            chain[len(word)].append((word, idx))
        dp = [0] * len(words)
        
        i = 1
        while(i < 17):
            for word, dp_idx in chain[i]:
                if i - 1 in chain:
                    for checkWord, dp_jdx in chain[i - 1]:
                        if checkPredecessor(checkWord, word):
                            dp[dp_idx] = max(dp[dp_idx], 1 + dp[dp_jdx])
            i += 1
        return max(dp) + 1
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def check(i,j):
            w1 = words[i]
            w2 = words[j]
            
            for k in range(len(w2)):
                if w1 == (w2[:k] + w2[k+1:]):
                    return True
            return False
        
        buckets = defaultdict(list)
        for i,word in enumerate(words):
            l = len(word)
            buckets[l] += [i]
            
        g = defaultdict(list)
        for key in buckets:
            if key + 1 in buckets:
                for i in buckets[key]:
                    for j in buckets[key+1]:
                        if check(i,j):
                            g[i] += [j]
                
        ans = 0
        seen = set()
        for key in sorted(buckets.keys()):
            for i in buckets[key]:
                stack = [[1,i]]
                while stack:
                    level, node = stack.pop()
                    ans = max(ans, level)
                    if node in seen:
                        continue
                    seen.add(node)
                    
                    if node in g:
                        for child in g[node]:
                            if child not in seen:
                                stack += [[level+1, child]]
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        result = 1
        for word in sorted(words,key = len):
            dp[word] = 1
            for i in range(len(word)):
                prev = word[:i] + word[i + 1:]
                if prev in dp:
                    dp[word] = max(dp[word],dp[prev] + 1)
                    result = max(result,dp[word])
        return result


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        lengthMap = {}
        for word in words:
            n = len(word)
            if n not in lengthMap:
                lengthMap[n] = []
            lengthMap[n].append(word)
            
        arr = [0 for i in range(len(words))]
        dp = {i : -1 for i in words} 
        res = 1
        
        for i in range(len(words)):
            arr[i] = self.get_longest(dp, lengthMap, words[i])
            res = max(arr[i], res)
            
        print (arr)
        return res
    
    
        # Standard DP solution 
        
        # dp = {i: 0 for i in words}
        # words = sorted(words, key=len)
        # for w in words:
        #     tmp = [0]
        #     for i in range(len(w)):
        #         if w[:i] + w[i+1:] in dp:
        #             tmp.append(dp[w[:i] + w[i+1:]])
        #         dp[w] = max(tmp) + 1
        # return max(dp.values())
    
    
    
    def get_longest(self, dp, lengthMap, word):
        if dp[word] != -1:
            return dp[word]
        
        if len(word) + 1 not in lengthMap:
            return 1
        
        res = 0
        for item in lengthMap[len(word) + 1]:
            if self.only_one_diff(word, item):
                res = max(res, self.get_longest(dp, lengthMap, item))
        
        dp[word] = res + 1
        return res + 1

    
    def only_one_diff(self, word1, word2):
        for i in range(len(word2)):
            if word2[:i] + word2[i + 1:] == word1:
                return True
        return False
            
    

            
            


# DFS | Time: O(n * k) | Space: O(n)
# n: len(words)
# k: length of each word
class Solution:
    
    # @param words: List[str]
    # @return int
    def longestStrChain(self, words):
        if not words:
            return 0

        # build word table
        wordTable = self.buildWordTable(words) # { length: list of words }

        # dfs - search from shortest word to longest word
        maxLen = 0
        visited = set()
        for wordLen in range(min(wordTable), max(wordTable) + 1):
            for word in wordTable[wordLen]:
                if word not in visited:
                    maxLen = max(maxLen, self.dfs(wordTable, word, visited))

        return maxLen

    # Derive word table.
    # Key: word length.
    # Value: list of words that have length of key.
    # @param words: List[str]
    # @return dict({ int: List[str] })
    def buildWordTable(self, words):

        # initialize
        wordTable = {} # { length: list of words }

        for word in words:
            if len(word) not in wordTable:
                wordTable[len(word)] = []
            wordTable[len(word)].append(word)

        return wordTable

    # DFS - search the successors and find the longest chain
    #   - Apply Divide & Conquer coding framework
    # @param wordTable: dict({ int: List[str] })
    # @param word: str
    # @param visited: set(str)
    # @return int
    def dfs(self, wordTable, word, visited):

        # initialize
        maxLen = 0
        visited.add(word)

        # divide
        #   - Find successors and update the max length that subtasks responded
        if len(word) + 1 in wordTable:
            for neighbor in wordTable[len(word) + 1]:
                if self.isAncestor(word, neighbor):
                    maxLen = max(maxLen, self.dfs(wordTable, neighbor, visited))

        # conquer
        #   - Calculate the max length found
        #   - +1 to count itself
        return maxLen + 1

    # @param ancestor: str
    # @param successor: str
    # @return bool
    def isAncestor(self, ancestor, successor):
        if len(ancestor) + 1 != len(successor):
            return False

        # initialize
        i, j = 0, 0

        while i < len(ancestor) and j < len(successor):
            if ancestor[i] != successor[j]:
                j += 1
                continue
            else:
                i += 1
                j += 1

        return i == j or i == j - 1


class Solution:
    
    
    def isChild(self, parent, possible_child):
        for i in range(len(possible_child)):
            if possible_child[:i] + possible_child[i+1:] == parent:
                return True 
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        
        # an intutitve idea is to create a graph of the given words, where every node is a word, and is a predecessor to all of its children. We can then simply DFS from root to node and find the longest path.
        # the DFS would take O(n) where n is the length of words.
        # Challenge: how to construct graph? We can loop once and bucket words by their length, and create a node per word, then starting from the smallest word length i, find all possible children of length i+1 and add them as edges do this until the end. 
        # bucketing and creating graph nodes would take O(n), connecting nodes would also take o(n) since we only check two buckets at a time linearly, and those buckets at most contain n nodes (and in that case other buckets are empty), so its O(n)
        
        
        graph = {}
        # word length buckets 
        word_lens = [[] for _ in range(17)]
        # create nodes and bucket
        for w in words:
            graph[w] = []
            word_lens[len(w)].append(w)
        
        # connect graph
        for i in range(1,16):
            parents = word_lens[i]
            possible_children = word_lens[i+1]
            for p in parents: 
                for c in possible_children:
                    # check if c can possibly be a child of p
                    # it is a possible child if 
                    if self.isChild(p,c):
                        graph[p].append(c)
        # do DFS 
        longest_len = 0
        # word: longest path to it pair. avoids revisiting node if we got to it a longer way before
        visited = {}
        # sorting ensures we visit nodes through the longest path 
        sorted_keys = sorted(graph.keys())
        for node in sorted_keys:
            to_visit = [(node,1)]
            while(len(to_visit) != 0):
                cur_node,depth = to_visit.pop()
                visited[cur_node] = depth
                longest_len = max(depth, longest_len)
                for child in graph[cur_node]: 
                    if child not in visited:
                        to_visit.append((child,depth+1))
                    else: 
                        if depth+1 > visited[child]:
                            to_visit.append((child,depth+1))
        return longest_len

from collections import deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        return self.run_graph_solution(words)
        # return self.run_dp_solution(words)
    
    def run_dp_solution(self, words):
        
        mem = {}
        def inner(w):
            if w in mem or len(w) == 0:
                return
            
            max_per_w = 0
            for j in range(len(w)):
                cur = w[:j] + w[j+1:]
                if cur in mem:
                    val = mem[cur] + 1
                else:
                    val = inner(cur) + 1
                if val > max_per_w:
                    max_per_w = val
            mem[w] = max_per_w
            return
        
        return max(mem.values())
        
    def run_graph_solution(self, words):
        graph = {word: [] for word in words}
        lengths = {k: [] for k in range(1, 17)}
        into = {}
        min_len = 17
        max_len = 1
        for word in words:
            lengths[len(word)].append(word)
            min_len = min(len(word), min_len)
            max_len = max(len(word), max_len)
                    
        for k in range(min_len, max_len):
            for word1 in lengths[k]:
                for word2 in lengths[k + 1]:
                    for i in range(len(word2)):
                        if word2[:i] + word2[i+1:] == word1:
                            graph[word1].append(word2)
                            into[word2] = True     
                            
        sources = [word for word in words if word not in into]
        max_len = 0
        for s in sources:
            max_len = max(max_len, self.run_bfs(s, graph))
        return max_len
                
    def run_bfs(self, s, graph):
        queue = deque(maxlen=len(list(graph.keys())))
        queue.appendleft((s, 1))
        summary = []
        
        while len(queue) > 0:
            current_node, cur_len = queue.pop()
            for word in graph[current_node]:
                queue.appendleft((word, cur_len + 1))
            summary.append(cur_len)
        
        return summary[-1]
    
    
            
            
        
        
        
        
        


class Solution:
    
    
    def isChild(self, parent, possible_child):
        for i in range(len(possible_child)):
            if possible_child[:i] + possible_child[i+1:] == parent:
                return True 
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        
        # an intutitve idea is to create a graph of the given words, where every node is a word, and is a predecessor to all of its children. We can then simply DFS from root to node and find the longest path.
        # the DFS would take O(n) where n is the length of words.
        # Challenge: how to construct graph? We can loop once and bucket words by their length, and create a node per word, then starting from the smallest word length i, find all possible children of length i+1 and add them as edges do this until the end. 
        # bucketing and creating graph nodes would take O(n), connecting nodes would also take o(n) since we only check two buckets at a time linearly, and those buckets at most contain n nodes (and in that case other buckets are empty), so its O(n)
        
        
        graph = {}
        # word length buckets 
        word_lens = [[] for _ in range(17)]
        # create nodes and bucket
        for w in words:
            graph[w] = []
            word_lens[len(w)].append(w)
        
        # connect graph
        for i in range(1,16):
            parents = word_lens[i]
            possible_children = word_lens[i+1]
            for p in parents: 
                for c in possible_children:
                    # check if c can possibly be a child of p
                    # it is a possible child if 
                    if self.isChild(p,c):
                        graph[p].append(c)
       # do DFS 
        longest_len = 0
        for node in graph.keys():
            to_visit = [(node,1)]
            
            while(len(to_visit) != 0):
                cur_node,depth = to_visit.pop()
                longest_len = max(depth, longest_len)
                for child in graph[cur_node]: 
                    to_visit.append((child,depth+1))
            
        return longest_len


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        print(words)
        if not words:
            return 0
            
        max_chain_dp = {}
        max_len = 1
        for i in range(len(words)):
            word_len = len(words[i])
            max_chain_dp[words[i]] = 1
            for j in range(i-1, -1, -1):
                if len(words[j]) == word_len:
                    continue
                elif len(words[j]) + 1 == word_len:
                    if self.isChain(words[j], words[i]):
                        max_chain_dp[words[i]] = max(1 + max_chain_dp[words[j]], max_chain_dp[words[i]])
                        max_len = max(max_chain_dp[words[i]], max_len)
                else:
                    break
        return max_len
                
                
    def isChain(self, word1, word2):
        if len(word2) - len(word1) > 1:
            return False
        added = False
        i, j = 0, 0
        while i < len(word1) or j < len(word2):
            if i < len(word1) and word1[i] == word2[j]:
                i += 1
                j += 1
            else:
                if added:
                    return False
                else:
                    j += 1
                    added = True

        if i == len(word1) and j == len(word2):
            return True


                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        self._len_to_words = collections.defaultdict(list)
        for w in words:
            self._len_to_words[len(w)].append(w)
        if len(self._len_to_words) == 1:
            return 1
        self._cache = {}
            
        self._visited = {}
        max_so_far = 0
        for w in words:
            max_so_far = max(max_so_far, self._find_longest(w))
        return max_so_far
    
    def _find_longest(self, w):
        if w in self._visited:
            return self._visited[w]
        
        len_w = len(w)
        if len_w+1 not in self._len_to_words:
            self._visited[w] = 1
            return 1
        
        max_chain_len = 1
        for cand in self._len_to_words[len_w+1]:
            if self.is_predecessor(w, cand):
                max_chain_len = max(max_chain_len, 1+self._find_longest(cand))
        
        self._visited[w] = max_chain_len
        return max_chain_len
        
    def is_predecessor(self, w1, w2):
        if (w1, w2) in self._cache:
            return self._cache[(w1, w2)]
        
        is_p = True
        if len(w1) + 1 != len(w2):
            is_p = False
        else:
            count = {}
            for c in w1:
                if c in count:
                    count[c] += 1
                else:
                    count[c] = 1
            found_diff = False
            for c in w2:
                if c in count:
                    count[c] -= 1
                    if count[c] == 0:
                        del count[c]
                else:
                    if found_diff:
                        is_p = False
                        break
                    else:
                        found_diff = True
                        count[c] = 1
        if is_p:
            keys = list(count.keys())
            is_p = len(keys) == 1 and count[keys[0]] == 1
        
        self._cache[(w1, w2)] = is_p
        return is_p

class Solution:
    def isPredecessor(self, word1, word2): 
        for i in range(1, len(word2) + 1): 
            if (word2[:i-1] + word2[i:]) == word1: 
                return True
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        # sort by length
        # add smallest set to dictionary that maps the word to the longest length 
        # should be 1 to start with
        # then go bigger looking @ smaller word list, compute longest length for each word
        wordLengths = {}
        for word in words:
            if len(word) in wordLengths: 
                wordLengths[len(word)].append(word)
            else: 
                wordLengths[len(word)] = [word]
        longestStrChains = {}
        totalMaxLength = 1
        for length in sorted(wordLengths.keys()): 
            for word in wordLengths[length]: 
                if len(word) - 1 not in wordLengths: 
                    longestStrChains[word] = 1
                else: 
                    maxLength = 1
                    for smallerWord in wordLengths[len(word) - 1]: 
                        if self.isPredecessor(smallerWord, word): 
                            if maxLength < longestStrChains[smallerWord] + 1: 
                                maxLength = longestStrChains[smallerWord] + 1
                    longestStrChains[word] = maxLength
                    if totalMaxLength < maxLength: 
                        totalMaxLength = maxLength
        print(wordLengths)
        print(longestStrChains)
        return totalMaxLength
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return
        
        dic = {}
        result = 1
        
        for word in sorted(words, key=len):
            dic[word] = 1
            
            for i in range(len(word)):
                currWord = word[:i] + word[i+1:]
                
                if currWord in dic:
                    dic[word] = max(dic[word], dic[currWord] + 1)
                    result = max(result, dic[word])
                    
                    
        return result
                    
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def dfs(w1, size):
            return max([dfs(w2, size + 1) for w2 in graph[w1]], default = size)
        graph = collections.defaultdict(list)
        for w in words:
            graph[len(w)].append(w)
        for w1 in words:
            for w2 in graph[len(w1) + 1]:
                for i in range(len(w2)):
                    if w2[:i] + w2[i + 1:] == w1:
                        graph[w1].append(w2)
        return max(dfs(w, 1) for w in words)

class Solution:
    def isPredecessor(self, word1, word2): 
        for i in range(1, len(word2) + 1): 
            if (word2[:i-1] + word2[i:]) == word1: 
                return True
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        # sort by length
        # add smallest set to dictionary that maps the word to the longest length 
        # should be 1 to start with
        # then go bigger looking @ smaller word list, compute longest length for each word
        wordLengths = {}
        for word in words:
            if len(word) in wordLengths: 
                wordLengths[len(word)].append(word)
            else: 
                wordLengths[len(word)] = [word]
        longestStrChains = {}
        totalMaxLength = 1
        for length in sorted(wordLengths.keys()): 
            for word in wordLengths[length]: 
                if len(word) - 1 not in wordLengths: 
                    longestStrChains[word] = 1
                else: 
                    maxLength = 1
                    for smallerWord in wordLengths[len(word) - 1]: 
                        if self.isPredecessor(smallerWord, word): 
                            if maxLength < longestStrChains[smallerWord] + 1: 
                                maxLength = longestStrChains[smallerWord] + 1
                    longestStrChains[word] = maxLength
                    if totalMaxLength < maxLength: 
                        totalMaxLength = maxLength
        return totalMaxLength
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        graph = collections.defaultdict(list)
        maxlen = float('-inf')
        minlen = float('inf')
        for word in words:
            graph[len(word)].append(word)
            maxlen = max(maxlen, len(word))
            minlen = min(minlen, len(word))
        
        # print(graph, minlen, maxlen)
        
        memo = {}
        def predecessor(word1, word2):
            if not word1:
                return True
            if len(word1)+1 != len(word2):
                return False
            if (word1, word2) in memo:
                return memo[(word1, word2)]
            fill = 1
            left, right = 0 ,0 
            ans = True
            while left < len(word1) and right < len(word2):
                if word1[left] != word2[right]:
                    if fill:
                        right += 1
                        fill -= 1
                        continue
                    else:
                        ans = False
                        break
                left += 1
                right += 1
            # print(word1, word2, True)
            memo[(word1, word2)] = ans
            return ans
            
        def chain(length, s=''):
            if length not in graph:
                return 0
            ans = 0
            for w in graph[length]:
                if predecessor(s, w):
                    ans = max(ans, 1 + chain(length+1, w))
            return ans
        
        return max(chain(l) for l in range(minlen, maxlen+1))


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        dp = [1]*len(words)
        
        possible_pred = [0]*len(words)
        for i in range(len(words)-1, -1, -1):
            cur_word = words[i]
            cur_length = len(cur_word)
            for j in range(i+1, len(words)):
                if len(words[j]) == cur_length+1 and cur_word in possible_pred[j]:
                    dp[i] = max(dp[i], dp[j]+1)
            pred = set()
            for k in range(len(cur_word)):
                pred.add(cur_word[0:k]+cur_word[k+1:])
            possible_pred[i] = pred
        return max(dp)
                    


class Solution:
    def checkPredecessor(self, word_1, word_2):
        find_extra = False
        for i in range(len(word_1)):
            chr_1 = word_1[i]
            if not find_extra:
                chr_2 = word_2[i]
            else:
                chr_2 = word_2[i + 1]
            if chr_1 != chr_2:
                if find_extra:
                    return False
                else:
                    if chr_1 != word_2[i + 1]:
                        return False
                    find_extra = True
        return True
            
    
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        longest_seq = [1] * len(words)
        for i in range(1, len(words)):
            curr_word = words[i]
            for j in range(0, i)[::-1]:
                prev_word = words[j]   
                if len(prev_word) == len(curr_word):
                    continue
                if len(prev_word) + 1 < len(curr_word):
                    break
                if self.checkPredecessor(prev_word, curr_word):
                    longest_seq[i] = max(longest_seq[i], longest_seq[j] + 1)
        return max(longest_seq)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        
        result = 1
        
        
        for word in sorted(words,key=len):
            dp[word] = 1
            
            for i in range(len(word)):
                preword = word[:i]+word[i+1:]
                if preword in dp:
                    dp[word] = max(dp[preword]+1,dp[word])
                    result = max(result, dp[word])
        
        return result

# https://leetcode.com/problems/longest-string-chain/discuss/295008/Python-graph-DFS
import collections
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        self.graph = collections.defaultdict(list)
        
        for word in words:
            self.graph[len(word)].append(word)
            
        for word1 in words:
            for word2 in self.graph[len(word1)+1]:
                for i in range(len(word2)):
                    if word2[:i] + word2[i + 1:] == word1:
                        self.graph[word1].append(word2)
        
    
        return max(self.dfs(w, 1) for w in words)
    
    def dfs(self, word1, size):
        return max([self.dfs(word2, size + 1) for word2 in self.graph[word1]], default = size)

            
        
            
            
                
                    
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(s1,s2):
            add = False
            s1_inx = 0
            s2_inx = 0
            while s1_inx < len(s1) and s2_inx < len(s2):
                if s1[s1_inx] == s2[s2_inx]:
                    s1_inx += 1
                    s2_inx += 1
                elif add == False:
                    s2_inx += 1
                    add = True
                else:
                    return False
            return True
        words = sorted(words, key = lambda x: len(x))
        dp = [1] * len(words)
        res = 1
        for i in range(len(words)):
            for j in range(0,i):
                if len(words[i]) == len(words[j]):
                    break
                elif len(words[i]) - 1 > len(words[j]):
                    continue
                else:
                    if isPredecessor(words[j],words[i]):
                        dp[i] = max(dp[i],dp[j]+1)
                        res = max(res,dp[i])
        return res
#         def isPre(word1,word2):
#             flag=False
#             indx1=0
#             indx2=0
#             while indx1 < len(word1) and indx2 < len(word2):
#                 if word1[indx1] == word2[indx2]:
#                     indx1 +=1
#                     indx2+=1
#                 elif flag == False:
#                     indx2 +=1
#                     flag =True
#                 else:
#                     return False
#             return True
        
#         dp=len(words)*[1]
#         words = sorted(words, key = lambda x: len(x))
#         res=1
#         for i in range(len(words)):
#             for j in range(i):
#                 if len(words[i])==len(words[j]):
#                     break
#                 elif len(words[i]) > len(words[j])+1:
#                     continue
#                 else:
#                     if isPre(words[j],words[i]):
#                         print(i)
#                         dp[i]=max(dp[i],dp[j+1])
#                         res=max(res,dp[i])
       
    


class Solution:
    def plusOne(self, a, b):
        bigger = max([a, b], key=len)
        smaller = min([a, b], key=len)
        for i in range(len(bigger)):
            if bigger[:i] + bigger[i+1:] == smaller:
                return True
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        memo = {}
        sort = [[i for i in words if len(i) == a] for a in set([len(j) for j in words])][::-1]
        print(sort)
        for i, row in enumerate(sort):
            if i == 0:
                for j in row:
                    memo[j] = 1
            else:
                for a in row:
                    values = []
                    for b in sort[i-1]:
                        if self.plusOne(a, b):
                            print((a, b))
                            values.append(1 + memo[b])
                    memo[a] = max(values) if values else 1 
#         for key, value in memo.items(): 
#             print(key, value)
        return max(memo.values())
                        
        
            
            
    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        n = len(words)
        dp = [1] * n
        for i in range(n):
            for j in reversed(list(range(i))):
                if len(words[i]) == len(words[j]):
                    continue
                elif len(words[i]) > len(words[j]) + 1:
                    break
                
                if self.helper(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)
        
        return max(dp)
    
    def helper(self, w1, w2):
        # check if w1 is a predecessor of w2
        i = 0
        while i < len(w1) and w1[i] == w2[i]:
            i += 1
        
        while i < len(w1) and w1[i] == w2[i + 1]:
            i += 1
        
        return i == len(w1)


from collections import defaultdict
from queue import Queue

class Solution:
    def is_pred(self, w1, w2):
        for i in range(len(w2)):
            if w2[:i] + w2[i+1:] == w1:
                return True
        return False
            
    def longestStrChain(self, words: List[str]) -> int:
        graph = defaultdict(list)
        words_by_len = defaultdict(list)
        for word in words:
            n = len(word)
            words_by_len[n].append(word)
        
        roots = set(words)
        for word in words:
            possible_preds = words_by_len[len(word)-1]
            for pred in possible_preds:
                if self.is_pred(pred, word):
                    graph[pred].append(word)
                    if word in roots:
                        roots.remove(word)
        
        max_depth = 1
        for root in roots:
            q = Queue()
            q.put((root, 1))
            while not q.empty():
                curr, depth = q.get()
                max_depth = max(max_depth, depth)
                descs = graph[curr]
                for desc in descs:
                    q.put((desc, depth+1))
        return max_depth
            
            
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def predecessor(word1, word2):
            for i in range(len(word2)):
                if word2[0:i] + word2[i + 1:] == word1:
                    return True
            return False
        words.sort(key = len)
        words_sort = {}
        for i in words:
            if len(i) in words_sort:
                words_sort[len(i)].append(i)
            else:
                words_sort[len(i)] = [i]
        def dfs(words, words_sort):
            max_chain = 1
            stack = [(1, word) for word in words[::-1]]
            while stack:
                current = stack.pop()
                if current[0] > max_chain:
                    max_chain = current[0]
                if len(current[1]) + 1 in words_sort:
                    for word in words_sort[len(current[1]) + 1]:
                        if predecessor(current[1], word):
                            if (1, word) in stack:
                                stack.remove((1, word))
                            stack.append((current[0] + 1, word))
                else:
                    continue
            return max_chain
        max_chain = dfs(words, words_sort)
        return max_chain


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        m = collections.defaultdict(list)
        words = sorted(words, key=len)
        for w in words:
            m[len(w)].append(''.join(sorted(w)))
            
        if len(m) == 1:
            return 1
        
        def equal(x, y):
            for i in range(len(x)):
                if x[i] != y[i]:
                    return x == (y[:i] + y[i+1:])
            return True
        
        def dfs(w):
            k = len(w) + 1
            if k not in m:
                return 0
            return max([dfs(x) + 1 for x in m[k] if equal(w, x)] or [0])
        
        return max(dfs(x) for y in list(m.keys())[:-1] for x in m[y]) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x))                      # words from short to long 
        counter = {}                                            
        minLen = len(words[0])                                  # minimum length of word
        maxLen = 1
        for idx in range(len(words)):
            if len(words[idx]) == minLen:
                counter[idx] = 1                                # base case 
            else:
                ancestor = 1
                j = 0 
                while idx - j >= 0 and len(words[idx-j]) >= len(words[idx]) - 1:
                    j += 1
                    if len(words[idx-j]) == len(words[idx]):    # do not check words with the same length 
                        continue 
                    if self.checker(words[idx-j], words[idx]):       # check if words[idx-j] is an ancestor of words[idx]
                        #print(words[idx-j], words[idx])
                        ancestor = max(ancestor, counter[idx-j] + 1)
                counter[idx] = ancestor 
                maxLen = max(maxLen, ancestor)
        return maxLen
     
    
    def checker(self, short, long):
        for idx in range(len(long)):
            removed = long[0:idx] + long[idx+1:]
            if short == removed:
                return True 
        return False 

class Solution:
    def IsPredecessor(self, w1, w2):
        # return true if w2 is predecessor of w1
        if len(w1)+1 != len(w2):
            return False
        if len(w1)==0:
            return True  

        diff = 0
        for i in range(len(w2)):
            if i-diff==len(w1) or w1[i-diff] != w2[i]:
                diff += 1

        return diff==1
        
    def longestStrChain(self, words: List[str]) -> int:
         
        if len(words)<1:
            return 0
        
        # Use a hashmap to record the length and the index
        length_position = [[] for i in range(17)]
        for i in range(len(words)):
            w = words[i]
            length_position[len(w)].append(i)
                  
        dp = [1]*len(words)
        
        # visit the dictionaty from shorter length to longer length
        for i in range(2, 17):
            if len(length_position[i-1])==0 or len(length_position[i])==0:
                continue
            current = length_position[i]
            previous = length_position[i-1]
            for j in current:
                for k in previous:
                    if self.IsPredecessor(words[k], words[j]):
                        dp[j] = max(dp[j], dp[k]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
    
    
        size = 0
        wordSet = set(words)
        mem = {}
        
        def dfs(s):

            if s in mem:
                return mem[s]
            
            maxpath = 1
            for i in range(len(s)):
                new = s[:i] + s[i+1:]
                if new in wordSet:
                    maxpath = max(maxpath, 1+dfs(new))
            
            mem[s] = maxpath

            return mem[s]
        
        
        for word in words:
            if word not in mem:
                size = max(size, dfs(word))
        print(mem)
        return size
        
        
        
        dic=collections.defaultdict(int)
        words=sorted(words,key=lambda x:len(x))
        for word in words:
            dic[word]=1
            for i in range(len(word)):
                tmp=word[:i]+word[i+1:]
                if tmp in dic:
                    dic[word]=max(dic[word],dic[tmp]+1)
                    
        return max(dic.values())
    
        
        
        
        
        
                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:

        # key is current word, value is current length
        chains = {}
        len_arr = [len(x) for x in words]
        min_len, max_len = min(len_arr), max(len_arr)
        max_len_w = [w for w in words if len(w) == max_len]
        for w in max_len_w:
            chains[w] = 1
        
        for length in range(max_len-1, min_len-1, -1):
            for w in words:
                if len(w) != length:
                    continue
                max_w = 1
                for key in chains:
                    if len(key) == len(w) + 1 and self.is_chain(w, key):
                        max_w = max(max_w, chains[key] + 1)
                chains[w] = max_w
        len_chain = [y for x,y in list(chains.items())]
        return max(len_chain)
    
    def is_chain(self, small_w, large_w):
        for i in range(len(large_w)):
            if large_w[:i] + large_w[i+1:] == small_w:
                return True
        return False
                        
                        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        graph = collections.defaultdict(list)
        maxlen = float('-inf')
        minlen = float('inf')
        for word in words:
            graph[len(word)].append(word)
            maxlen = max(maxlen, len(word))
            minlen = min(minlen, len(word))
        
        # print(graph, minlen, maxlen)
        
        def predecessor(word1, word2):
            if not word1:
                return True
            if len(word1)+1 != len(word2):
                return False
            fill = 1
            left, right = 0 ,0 
            while left < len(word1) and right < len(word2):
                if word1[left] != word2[right]:
                    if fill:
                        right += 1
                        fill -= 1
                        continue
                    else:
                        return False
                left += 1
                right += 1
            # print(word1, word2, True)
            return True
            
        def chain(length, s=''):
            if length not in graph:
                return 0
            ans = 0
            for w in graph[length]:
                if predecessor(s, w):
                    ans = max(ans, 1 + chain(length+1, w))
            return ans
        
        return max(chain(l) for l in range(minlen, maxlen+1))


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        words_dict ={}
        for word in words:
            dp[word] = -1
            if words_dict.get(len(word)):
                words_dict[len(word)].append(word)
            else:
                words_dict[len(word)] = [word]
        result = 0
        for word in words:
            result = max(result, self.longest_chain(word, dp, words_dict))
        return result
            
    def longest_chain(self, word, dp, words_dict):
        if dp[word]!=-1:
            return dp[word]
        if not words_dict.get(len(word)+1):
            return 1
        res = 0
        for word2 in words_dict[len(word)+1]:
            if self.isSucc(word, word2):
                res = max(res, self.longest_chain(word2, dp, words_dict))
        res +=1
        return res
        
    def isSucc(self, word1,word2):
        
        if len(word1)!=len(word2)-1:
            return False
        count=0
        i=0
        j=0
        while i<len(word1):
            if word1[i]!=word2[j]:
                if count==1:
                    return False
                count+=1
                j+=1
            else:
                j+=1
                i+=1
            
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x:len(x))
        chainLen = {w:1 for w in words}
        ret = 1
        for i, word1 in enumerate(words):
            for j in range(i+1, len(words)):
                word2 = words[j]
                if len(word2) == len(word1):
                    continue
                if len(word2) > len(word1) + 1:
                    break
                insert = False
                match = True
                idx1 = 0
                idx2 = 0
                while idx1 < len(word1):
                    if word1[idx1] != word2[idx2]:
                        if insert:
                            match = False
                            break
                        else:
                            insert = True
                            idx2 += 1
                            continue
                    idx1 += 1
                    idx2 += 1
                if match:
                    l = max(chainLen[word2], chainLen[word1] + 1)
                    ret = max(ret, l)
                    chainLen[word2] = l
        return ret


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        table = defaultdict(list)
        for w in words:
            table[len(w)].append(w)
        max_len = max(len(w) for w in words)
            
        
        def backtracking(cur, n):
            if len(cur) == max_len:
                return 0
            if not cur in dp:
                res = 0
                for word in table[n]:
                    for i in range(len(word)):
                        if word[0:i] + word[i + 1:] == cur:
                            res = max(res, backtracking(word, n + 1) + 1)
                            break
                dp[cur] = res
            return dp[cur]
        
        res = 0
        for i, v in table.items():
            for w in v:
                res = max(res, backtracking(w, i + 1) + 1)
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = list(set(words))
        words = sorted(words, key=lambda x: len(x))
        
        # key is current word, value is current length
        chains = {}
        len_arr = [len(x) for x in words]
        min_len, max_len = min(len_arr), max(len_arr)
        max_len_w = [w for w in words if len(w) == max_len]
        for w in max_len_w:
            chains[w] = 1
        
        for length in range(max_len-1, min_len-1, -1):
            for w in words:
                if len(w) != length:
                    continue
                max_w = 1
                for key in chains:
                    if len(key) == len(w) + 1 and self.is_chain(w, key):
                        max_w = max(max_w, chains[key] + 1)
                chains[w] = max_w
                        
        len_chain = [y for x,y in list(chains.items())]
        return max(len_chain)
    
    def is_chain(self, small_w, large_w):
        for i in range(len(large_w)):
            if large_w[:i] + large_w[i+1:] == small_w:
                return True
        return False
                        
                        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        graph = collections.defaultdict(list)
        maxlen = float('-inf')
        minlen = float('inf')
        for word in words:
            graph[len(word)].append(word)
            maxlen = max(maxlen, len(word))
            minlen = min(minlen, len(word))
        
        # print(graph, minlen, maxlen)
        
        def predecessor(word1, word2):
            if not word1:
                return True
            if len(word1)+1 != len(word2):
                return False
            fill = 1
            left, right = 0 ,0 
            while left < len(word1) and right < len(word2):
                if word1[left] != word2[right]:
                    if fill:
                        right += 1
                        fill -= 1
                        continue
                    else:
                        return False
                left += 1
                right += 1
            # print(word1, word2, True)
            return True
            
        def chain(length, s=''):
            if length not in graph:
                return 0
            ans = 0
            for w in graph[length]:
                if predecessor(s, w):
                    ans = max(ans, 1 + chain(length+1, w))
            return ans
        
        ans = 1
        # for l in range(minlen, maxlen+1):
        #     for w in graph[l]:
        #         res = chain(l+1, w) +1 
        #         # print(res)
        #         ans = max(ans, res)
        return max(chain(l) for l in range(minlen, maxlen+1))


class Solution:
    def get_dict(self, word):
        d = {}
        for c in word:
            d[c] = d.get(c, 0) + 1
        return d
    
    def is_close(self, word1, word2):
        dict_word1 = self.get_dict(word1)
        dict_word2 = self.get_dict(word2)
        diff = False
        
        for letter, freq in list(dict_word2.items()):
            if letter in dict_word1:
                if abs(freq - dict_word1[letter]) == 1:
                    if not diff:
                        diff = True
                    else:
                        return False
                elif abs(freq - dict_word1[letter]) > 1:
                    return False
            else:
                if freq > 1:
                    return False
                else:
                    if not diff:
                        diff = True
                    else:
                        return False
        return True
         
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        dict_lens = {}
        dict_words = {}
        
        for word in words:
            dict_lens.setdefault(len(word), []).append(word)
            dict_words[word] = 0
            
        max_res = 0
        sorted_keys = sorted(dict_lens.keys())
        for len_num in sorted_keys:
            if len_num + 1 in dict_lens:
                next_word_list = dict_lens[len_num + 1]
            else:
                continue
            
            for word in dict_lens[len_num]:
                for next_word in next_word_list:
                    if self.is_close(word, next_word):
                        dict_words[next_word] = max(dict_words[next_word], dict_words[word]+1)
                        max_res = max(max_res, dict_words[next_word])
        
        return max_res + 1
                    
                    
                    
                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
    
    
        size = 0
        wordSet = set(words)
        mem = {}
        
        def dfs(s):

            if not s:
                return 1
            
            if s in mem:
                return mem[s]
            
            maxpath = 1
            for i in range(len(s)):
                new = s[:i] + s[i+1:]
                if new in wordSet:
                    maxpath = max(maxpath, 1+dfs(new))
            
            mem[s] = maxpath

            return mem[s]
        
        
        for word in words:
            if word not in mem:
                size = max(size, dfs(word))
        print(mem)
        return size
        
        
        
        dic=collections.defaultdict(int)
        words=sorted(words,key=lambda x:len(x))
        for word in words:
            dic[word]=1
            for i in range(len(word)):
                tmp=word[:i]+word[i+1:]
                if tmp in dic:
                    dic[word]=max(dic[word],dic[tmp]+1)
                    
        return max(dic.values())
    
        
        
        
        
        
                    


class Solution:
    def IsPredecessor(self, w1, w2):
        # return true if w2 is predecessor of w1
        if len(w1)+1 != len(w2):
            return False
        if len(w1)==0:
            return True  

        diff = 0
        for i in range(len(w2)):
            if i-diff==len(w1) or w1[i-diff] != w2[i]:
                diff += 1

        return diff==1
        
    def longestStrChain(self, words: List[str]) -> int:
         
        if len(words)<1:
            return 0
        
        # Use a hashmap to record the length and the index
        length_position = {}
        for i in range(len(words)):
            w = words[i]
            if len(w) in length_position:
                length_position[len(w)].append(i)
            else:
                length_position[len(w)] = [i]
                  
        dp = [1]*len(words)
        # visit the dictionaty from shorter length to longer length
        for i in sorted(length_position):
            if (i-1) not in length_position:
                continue
            current = length_position[i]
            previous = length_position[i-1]
            for j in current:
                for k in previous:
                    if self.IsPredecessor(words[k], words[j]):
                        dp[j] = max(dp[j], dp[k]+1)
                
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        contains = lambda w1, w2: all(l1 in w2 for l1 in w1)
        
        words.sort(key=lambda a: len(a))
        n = len(words)
        chain = [1] * n
        for i in range(n):
            j = i+1
            while j < n and len(words[j]) == len(words[i]):
                j += 1
            while j < n and len(words[j]) == len(words[i]) + 1:
                if contains(words[i], words[j]) and chain[i] + 1 > chain[j]:
                    chain[j] = chain[i] + 1
                j += 1
        
        return max(chain)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        d = {}
        for word in words:
            if len(word) in d:
                d[len(word)].append(word)
            else:
                d[len(word)] = [word]
        visited = {}
        res = 0
        for word in sorted(words):
            res = max(res, self.dfs(word, visited, d))
        return res
        
    def checkPredecessor(self, word1, word2):
        return any(word1 == word2[:i] + word2[i + 1:] for i in range(len(word2)))
    
    def dfs(self, word, visited, d):
        if word in visited:
            return visited[word]
        if len(word) + 1 not in d:
            visited[word] = 1
            return 1
        res = 1
        for w in d[len(word) + 1]:
            if self.checkPredecessor(word, w):
                res = max(res, self.dfs(w, visited, d) + 1)
        visited[word] = res
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        W = defaultdict(list)
        for i,w in enumerate(words):
            W[len(w)].append(i)
        #print(W)
        G = defaultdict(set)
        for i in range(n):
            for j in W[len(words[i])+1]:
                found = False
                for k in range(len(words[j])):
                    if words[i]==words[j][:k]+words[j][k+1:]:
                        found = True
                        break
                if found:
                    G[i].add(j)
        #print(G)
        @lru_cache(None)
        def dfs(i):
            out = 1
            for j in G[i]:
                out = max(out,1+dfs(j))
            return out
        return max(dfs(i) for i in range(n))
    
                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        dicti = collections.defaultdict(set)
        for word in words:
            dicti[len(word)].add(word)
            
        chars = [chr(i) for i in range(ord('a'), ord('z')+1)]
        memo = {}
        
        def dist(word):
            if word in memo:
                return memo[word]
            if len(word)+1 not in dicti:
                memo[word] = 1
                return memo[word]                

            n = len(word)
            ans = 0
            for ch in chars:
                for i in range(n+1):
                    new_word = word[:i] + ch + word[i:]
                    if  new_word in dicti[n+1]:
                        ans = max(ans, dist(new_word))
                        
            memo[word] = ans + 1
            return ans + 1
        
        ans = 0
        for word in words:
            ans = max(ans, dist(word))
        return ans
        
        
                

                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [1 for _ in range(len(words)+1)] ## length of word chain
        dp[0] = 0 
        words = sorted(words, key=lambda x:len(x))
        hmap = collections.defaultdict(list) #{length: words}
        for i, word in enumerate(words):
            hmap[len(word)] += [(word, i)]
        
        for i in range(1, len(words)+1):
            key = len(words[i-1])-1
            for word, idx in hmap[key]:
                ## if word is predecessor of words[i]
                ## , then dp[i] = max(dp[i], dp[idx+1])
                if self.predecessor(word, words[i-1]):
                    dp[i] = max(dp[i], dp[idx+1]+1)
        
        return max(dp)
    
    def predecessor(self, word0, word1):
        cnt0 = collections.Counter(word0)
        cnt1 = collections.Counter(word1)
        for key in cnt0:
            if key not in cnt1:
                return False
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) <= 0:
            return 0
        
        words.sort(key=len)
        numberOfWords = len(words)
        longestLens = [1] * len(words)
        index = numberOfWords - 1
        while index >= 0:
            if index == 0 or len(words[index]) != len (words[index - 1]):
                index -= 1
                break
            index -= 1
        
        while index >= 0:
            maxLen = 1
            index2 = index + 1
            while index2 < numberOfWords and len(words[index]) + 1 >= len(words[index2]):
                if self.isPredecessor(words[index], words[index2]):
                    if maxLen < longestLens[index2] + 1:
                        maxLen = longestLens[index2] + 1
                index2 += 1
            longestLens[index] = maxLen
            index -= 1
        
        maxLen = longestLens[0]
        for longestLen in longestLens:
            if maxLen < longestLen:
                maxLen = longestLen
        return maxLen
                    
    
    def isPredecessor(self, word1:str, word2:str)-> bool:
        if len(word2) - len(word1) != 1:
            return False
        for i, ch in enumerate(word2):
            newWord = word2[:i] + word2[i+1:]
            if newWord == word1:
                return True
        return False
    
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        for w in sorted(words, key=len):
            dp[w] = max(dp.get(w[:i] + w[i + 1:], 0) + 1 for i in range(len(w)))
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n=len(words)
        a=sorted(words,key=len)
        dp=[1]*n
        def helper(a1,b1):
            s=0
           
            n=len(b1)
            i=0
            j=0
            while(i<n and j<n+1):
                if a1[j]==b1[i]:
                    i+=1
                    j+=1
                elif a1[j]!=b1[i] and s==0:
                    s+=1
                    j+=1
                else:
                    return 0
            return 1    
                    
        
        for i in range(1,n):
            for j in range(i-1,-1,-1):
                if len(a[j])==len(a[i]):
                    continue
                elif len(a[j])<len(a[i])-1:
                    break
                x=helper(a[i],a[j])
                if x==1:
                    dp[i]=max(dp[i],dp[j]+1)
        return max(dp)            

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        m = collections.defaultdict(list)
        
        for w in words:
            m[len(w)].append(w)
            
        if len(m) == 1:
            return 1
        
        for k in list(m.keys()):
            m[k] = [''.join(sorted(x)) for x in m[k]]
        
        def equal(x, y):
            for i in range(len(x)):
                if x[i] != y[i]:
                    return x == (y[:i] + y[i+1:])
            return True
        
        def dfs(w):
            k = len(w) + 1
            if k not in m:
                return 0
            return max([dfs(x) + 1 for x in m[k] if equal(w, x)] or [0])
        
        return max(dfs(x) for y in sorted(list(m.keys()))[:-1] for x in m[y]) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # sort by decreasing length
        # define vector L of length len(words) such that it's the longest 
        # word chain starting from that word
        # then recuriosn is L_k = 1 if it is not predecessor of any 1 longer words
        # otherwise it is 1 + max L_j for j such that k is predecessor of them
        
        lengths = [len(word) for word in words]
        wordsSorted = [x for _,x in sorted(zip(lengths,words),reverse = True)]
        
        print(wordsSorted)
        L = [1]*len(wordsSorted)
        maxLength = max(lengths)
        for k in range(0, len(L)):
            lenWord = len(wordsSorted[k])
            for l in range(k):
                if len(wordsSorted[l]) == lenWord+1 and (set(wordsSorted[l]) & set(wordsSorted[k])) == set(wordsSorted[k]):
                    L[k] = max(L[k], 1 + L[l])
        return(max(L))
                    
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        from collections import defaultdict
        
        d = {}
        for word in words:
            if len(word) in d:
                d[len(word)].append(word)
            else:
                d[len(word)] = [word]
        def isPred(w1, w2):
            for i in range(len(w2)):
                if w2[:i] + w2[i+1:] == w1:
                    return True
            return False
        
        followers = defaultdict(list)
        for word in words:
            if len(word) +1 in d:
                for biggerWord in d[len(word)+1]:
                    if isPred(word, biggerWord):
                        followers[word].append(biggerWord)
        
        self.maxDist = 1
        def dfs(start, dist, found = set()):
            found.add(start)
            for neighbor in followers[start]:
                if neighbor not in found:
                    self.maxDist = max(self.maxDist, dist + 1)
                    dfs(neighbor, dist + 1, found)
        
        keys = [key for key in followers]
        for key in keys:
            s = set()
            dfs(key, 1, set())
            
        return self.maxDist


class Solution:
    # sort from smallest to largest length
    # for each element, see if any words of its length - 1 are predescessors
    # for every predecessor, take the max of the word chains and store it + 1
    def isPredecessor(self, prev_word, cur_word):
        if len(prev_word) != len(cur_word) - 1:
            return False
        skipped_char = False
        for i in range(len(prev_word)):
            if not skipped_char and prev_word[i] != cur_word[i]:
                skipped_char = True
            if skipped_char and prev_word[i] != cur_word[i+1]:
                return False
        return True

    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda word: len(word))
        num_words = len(words)
        chain_lengths = [1 for _ in range(num_words)]
        max_chain_length = 0
        for i in range(num_words):
            for j in range(0, i):
                if len(words[j]) == len(words[i]) - 1 and chain_lengths[j] + 1 > chain_lengths[i] and self.isPredecessor(words[j], words[i]):
                    chain_lengths[i] = chain_lengths[j] + 1
            max_chain_length = max(max_chain_length, chain_lengths[i])
        return max_chain_length
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPred(w1,w2):
            i,j = 0, 0
            while i < len(w1):
                if w1[i] != w2[j]:
                    if j != i:
                        return False
                    j += 1
                else:
                    j += 1
                    i += 1
            return True

        words = sorted(words, key=lambda x:len(x))
        dp = [1]*len(words)
        for i in range(1,len(words)):
            for j in range(i):
                if len(words[i])==(len(words[j])+1) and isPred(words[j],words[i]):
                    dp[i] = max(dp[i],dp[j]+1)
         
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n=len(words)
        for ele in words:
            ele=list(ele)
        words = sorted(words, key = lambda x: len(x))
        #print(words)
        arr=[1]*n
        for i in range(1,n):
            for j in range(0,i):
                if len(words[j])==len(words[i])-1:
                    if(all(x in words[i] for x in words[j])): 
                        arr[i]=max(arr[i],arr[j]+1)
                        #print(words[i],words[j])
                #print(words[i],words[j])
        print(arr)
        return max(arr)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        by_length = collections.defaultdict(set)
        for word in words:
            by_length[len(word)].add(word)

        longest = 1
        seen = {*()} # set()
        mx = len(by_length)
        mn = min(by_length)
        # in descending order
        for length in sorted(by_length, reverse=True):
           # if length - mn < longest:
           #     break
            for word in by_length[length]:
                if length - mn < longest:
                    break
                if word in seen:
                    continue
                stk = [(word, length, 1)]
                while stk:
                    word, k, n = stk.pop()
                    seen.add(word)
                    if n > longest:
                        longest = n
                    for i in range(k):
                        pre = word[:i] + word[i+1:]
                        if pre not in seen and pre in by_length[k-1]:
                            stk.append((pre, k-1, n+1))
               # if longest == mx:
               #     return longest

        return longest   

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        for w in sorted(words, key=len):
            # print()
            # print(w)
            temp = [dp.get(w[:i] + w[i + 1:], 0) + 1 for i in range(len(w))]
            # print(temp)
            dp[w] = max(temp)
            # print(dp)
        return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        auxList = [(len(item), item) for item in set(words)]
        auxList.sort()
        auxCntList = [1] * len(auxList)
        ans = 1

        for idx, (wordLen, word) in enumerate(auxList):
            for jdx in range(idx+1, len(auxList)):
                cWordLen, cWord = auxList[jdx]
                if cWordLen > wordLen + 1:
                    break
                elif cWordLen == wordLen:
                    pass
                else:
                    for pivot in range(len(cWord)):
                        if cWord[:pivot] + cWord[pivot+1:] == word:
                            auxCntList[jdx] = auxCntList[idx] + 1
                            ans = max(ans, auxCntList[jdx])

        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        wordsbylen = collections.defaultdict(list)
        for w in words:
            wordsbylen[len(w)].append(w)
        
        # @functools.lru_cache(None)
        def get_longest_derivate(word):
            potential = wordsbylen[len(word)+1]
            possible = [1]
            for p in potential:
                i, j = 0, 0
                while i < len(p) and j < len(word):
                    if p[i] == word[j]:
                        i += 1
                        j += 1
                    elif i != j:
                        break
                    else:
                        i += 1
                if j == len(word):
                    possible.append(1 + get_longest_derivate(p))
            return max(possible)
        
        return max(map(get_longest_derivate, words))

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        auxList = [(len(item), item) for item in set(words)]
        auxList.sort()
        
        auxGraph = collections.defaultdict(set)
        seen = dict()
        
        for idx, (wordLen, word) in enumerate(auxList):
            seen[word] = False
            for jdx in range(idx+1, len(auxList)):
                cWordLen, cWord = auxList[jdx]
                if cWordLen > wordLen + 1:
                    break
                elif cWordLen == wordLen:
                    pass
                else:
                    for pivot in range(len(cWord)):
                        if cWord[:pivot] + cWord[pivot+1:] == word:
                            auxGraph[word].add(cWord)
        # print(auxGraph)
        ans = 0
        auxDeque = collections.deque()
        for _, word in auxList:
            if seen[word]:
                pass
            else:
                seen[word] = True
                # DFS or BFS
                auxDeque.append((1, word))
                
                while len(auxDeque) > 0:
                    cLen, cnode = auxDeque.pop()
                    ans = max(ans, cLen)
                    for eachNeighbor in auxGraph[cnode]:
                        if not seen[eachNeighbor]:
                            seen[eachNeighbor] = True
                            auxDeque.append((cLen + 1, eachNeighbor))
        return ans


from collections import Counter, defaultdict

class Solution:
    def is_one_away(self, c_1, c_2) -> bool:
        # c_1 will be shorter than c_2
        diff = 0
        for k, v in list(c_2.items()):
            diff += v - c_1.get(k, 0)
            if diff > 1:
                return False
        
        for k in c_1:
            if k not in c_2:
                return False
        
        return True
    
    def helper(self, word, words_for_len, counters_for_words):
        w_counter = counters_for_words[word]
        max_chain = 0
        for w in words_for_len[len(word) + 1]:
            if self.is_one_away(w_counter, counters_for_words[w]):
                max_chain = max(max_chain, self.helper(w, words_for_len, counters_for_words))
        return max_chain + 1
    
    def longestStrChain(self, words: List[str]) -> int:
        # Logic:
        # define the longest string chain starting at word w as L[w]
        #   L[w] = 1 + max over all w' in W such that dist(w, w') == 1(L[w'])
        # Compute map of length to word counters to easily find all words 1 letter increased from a given word
        # Given some w,
        #     max_chain := 0
        #     For all w' one letter away
        #          max_chain := max(max_chain, max_chain(w')) + 1
        # return max_chain
        # Time Complexity analysis: ??
        # Space: recursive stack O(n) will be the depth of the tree, hashmap O(n)
        words_for_len = defaultdict(list)
        counters_for_words = {w: Counter(w) for w in words}
        for w in words:
            words_for_len[len(w)].append(w)
        
        max_chain = 0
        for w in words:
            max_chain = max(max_chain, self.helper(w, words_for_len, counters_for_words))
        return max_chain
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            for i, w in enumerate(w1):
                if(w != w2[i]):
                    return w1[i:] == w2[i+1:]
            
            return True
        
        words.sort(key=len, reverse=True)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in reversed(list(range(0, i))):
                len_i, len_j = len(words[i]), len(words[j])
                if(len_i + 1 < len_j):
                    break
                elif(len_i == len_j):
                    continue
                if(combos[i] < combos[j] + 1
                   and is_predecessor(words[i], words[j])):
                    combos[i] = combos[j] + 1
        
        return max(combos) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            for i in range(len(w1)):
                if(w1[i] != w2[i]):
                    return w1[i:] == w2[i+1:]
            
            return True
        
        words.sort(key=len, reverse=True)
        print(words)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in reversed(list(range(0, i))):
                len_i, len_j = len(words[i]), len(words[j])
                if(len_i + 1 < len_j):
                    break
                if(len_i + 1 == len_j
                   and combos[i] < combos[j] + 1
                   and is_predecessor(words[i], words[j])):
                    combos[i] = combos[j] + 1
                    
        # for i in range(len_words):
        #     print(combos[i], end=', ')
        #     print(words[i])
        
        
        return max(combos) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        dp = [1]*len(words)
        m = 1
        for i in range(1, len(words)):
            j = i-1
            cl = len(words[i])
            while(len(words[j]) >= cl-1 and j>-1):
                if(len(words[j]) == cl):
                    j-=1
                    continue
                f = 0
                w1, w2 = words[i], words[j]
                li, lj = 0,0
                while(f<2 and lj < len(w2)):
                    if(w1[li] == w2[lj]):
                        li+=1
                        lj+=1
                    else:
                        f+=1
                        li+=1
                if(f!=2):
                    dp[i] = max(dp[i], 1 + dp[j])
                    m = max(dp[i], m) 
                j-=1
        return m  
                   


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        for w in sorted(words, key=len):
            dp[w] = max(dp.get(w[:i] + w[i + 1:], 0) + 1 for i in range(len(w)))
        return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def compare(w1,w2):
            i,j = 0, 0
            while i < len(w1):
                if w1[i] != w2[j]:
                    if j != i:
                        return False
                    j += 1
                else:
                    j += 1
                    i += 1
            return True
        
        words = sorted(words, key=lambda x:len(x))
        n = len(words)
        dp = [1]*len(words)
        for i in range(1,n):
            for j in range(i):
                if len(words[i])-len(words[j])==1:
                    if compare(words[j],words[i]):
                        dp[i] = max(dp[i],dp[j]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def check(str1, str2):
            for i in range(len(str2)):
                temp = str2[:i] + str2[i+1:]
                if temp == str1:
                    return True
            return False
        words.sort(key=len)
        min_length = len(words[0])
        dp = [1]*len(words)
        
        for i in range(len(words)):
            if len(words[i]) == min_length:
                continue
            for j in range(len(words)):
                if len(words[j]) == len(words[i]) - 1:
                    if check(words[j], words[i]):
                        dp[i] = max(dp[i], dp[j]+1)
                elif len(words[j]) >= len(words[i]):
                    break
        return max(dp)
                
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            for i in range(len(w1)):
                if(w1[i] != w2[i]):
                    return w1[i:] == w2[i+1:]
            
            return True
        
        words.sort(key=len, reverse=True)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in reversed(list(range(0, i))):
                len_i, len_j = len(words[i]), len(words[j])
                if(len_i + 1 < len_j):
                    break
                if(len_i + 1 == len_j
                   and combos[i] < combos[j] + 1
                   and is_predecessor(words[i], words[j])):
                    combos[i] = combos[j] + 1
                    
        # for i in range(len_words):
        #     print(combos[i], end=', ')
        #     print(words[i])
        
        
        return max(combos) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def contains(w1, w2):
            return all(l1 in w2 for l1 in w1)
        
        words.sort(key=lambda a: len(a))
        n = len(words)
        chain = [1] * n
        for i in range(n):
            j = i+1
            while j < n and len(words[j]) == len(words[i]):
                j += 1
            while j < n and len(words[j]) == len(words[i]) + 1:
                if contains(words[i], words[j]) and chain[i] + 1 > chain[j]:
                    chain[j] = chain[i] + 1
                j += 1
        
        return max(chain)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            for i in range(len(w1)):
                if(w1[i] != w2[i]):
                    return w1[i:] == w2[i+1:]
            
            return True
        
        words.sort(key=len, reverse=True)
        combos = [0] * len(words)
        
        for i in range(len(words)):
            for j in reversed(list(range(0, i))):
                len_i, len_j = len(words[i]), len(words[j])
                if(len_i + 1 < len_j):
                    break
                if(len_i + 1 == len_j
                   and combos[i] < combos[j] + 1
                   and is_predecessor(words[i], words[j])):
                    combos[i] = combos[j] + 1

        return max(combos) + 1


    
def is_pre(s1, s2):
    c = 0
    i = 0
    for j in range(len(s2)):
        if i == len(s1) or s1[i] != s2[j]:
            c += 1
        else:
            i += 1
    return c == 1

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key= len)
        n = len(words)
        m = {}
        incount = {}
        q = collections.deque()
        seen = set()
        for i in range(n):
            if i not in incount:
                q.append(i)
            wn = len(words[i])
            for j in range(i+1, n):
                jn = len(words[j])
                if jn > wn + 1:
                    break
                if jn == wn + 1 and is_pre(words[i], words[j]):
                    m.setdefault(i, []).append(j)
                    incount[j] = incount.get(j, 0) + 1
        lev = 0
        while q:
            lev += 1
            for i in range(len(q)):
                u = q.popleft()
                for v in m.get(u, []):
                    if v not in seen:
                        q.append(v)
                        seen.add(v)
        return lev
            
            
            
            
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        chain = {}
        for w in sorted(words, key=len):
            weights = [chain.get(w[:i] + w[i + 1:], 0) + 1 for i in range(len(w))]
            
            chain[w] = max(weights)
        return max(chain.values())


def is_pred(w1, w2):
    skip_count = 0
    if len(w1)+1 != len(w2):
        return False

    for i in range(0, len(w1)):
        if w1[i] == w2[i+skip_count]:
            pass
        elif skip_count == 0 and w1[i] == w2[i+1]:
            skip_count += 1
        else:
            return False

    return True

def tests():
    assert is_pred('a', 'ab')
    assert not is_pred('a', 'a')
    assert not is_pred('a', 'bc')
    assert not is_pred('ab', 'a')
    
class Solution:

        
    def longestStrChain(self, words: List[str]) -> int:
        # tests()
        words.sort(key=lambda w: len(w))
        
        chains = {}
        current_max = 0
        for w in words:
            chains[w] = 1  
            current_max = max(current_max, 1)
            for chain, chain_len in list(chains.items()):
                # if chain_len > chains[w] and is_pred(chain, w):
                #     chains[w] = chain_len + 1
                    
                if is_pred(chain, w):
                    chains[w] = max(chains[w], chain_len+1)
                    current_max = max(current_max, chains[w])
        
        
        return current_max
    

    # [a, ab, abc] abcd


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            for i in range(len(w1)):
                if(w1[i] != w2[i]):
                    return w1[i:] == w2[i+1:]
            
            return True
        
        words.sort(key=len, reverse=True)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in range(0, i):
                if(len(words[i]) + 1 == len(words[j])
                   and combos[i] < combos[j] + 1
                   and is_predecessor(words[i], words[j])):
                    # print(words[i] + ', ' + words[j])
                    combos[i] = combos[j] + 1
                    
        # for i in range(len_words):
        #     print(combos[i], end=', ')
        #     print(words[i])
        
        
        return max(combos) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        dp = [1 for i in range(len(words))]
        for r in range(1, len(words)):
            for l in range(r):
                if len(words[r]) == len(words[l]) + 1:
                    missingLetters = i = 0
                    for j in range(len(words[r])):
                        if i >= len(words[l]) or words[l][i] != words[r][j]:
                            missingLetters += 1
                            if missingLetters > 1:
                                break
                        else:
                            i += 1
                    if missingLetters == 1:
                        dp[r] = max(dp[r], dp[l] + 1)
        return max(dp)
                
                
                
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words=sorted(words, key=len)
        map={}
        DP=[1 for idx in range(len(words))]
       
        
        
        for i in range(len(words)):
            map={}
            for j in range(0,i):
                if len(words[i]) == len(words[j])+1:
                    
                    diff=0
                    
                    for comp in range(len(words[i])):
                        if diff==0 and comp==len(words[i])-1:
                            diff=1
                            continue
                        
                        if words[i][comp]==words[j][comp-diff]:
                            continue
                        else:
                            if diff==1:
                                diff=69
                                break
                            else:
                                diff=1
                    
                    if diff==1:
                        if DP[j]+1>DP[i]:
                            DP[i]=DP[j]+1

        #print (words)
        #print (DP) 
        return max(DP)
                        
                
                
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        graph = collections.defaultdict(list)
        maxlen = float('-inf')
        minlen = float('inf')
        for word in words:
            graph[len(word)].append(word)
            maxlen = max(maxlen, len(word))
            minlen = min(minlen, len(word))
        
        # print(graph, minlen, maxlen)
        
        def predecessor(word1, word2):
            
            if len(word1)+1 != len(word2):
                return False
            if not word1:
                return True
            fill = 1
            left, right = 0 ,0 
            while left < len(word1) and right < len(word2):
                if word1[left] != word2[right]:
                    if fill:
                        right += 1
                        fill -= 1
                        continue
                    else:
                        return False
                left += 1
                right += 1
            # print(word1, word2, True)
            return True
            
        def chain(length, s=''):
            if length not in graph:
                return 0
            ans = 0
            for w in graph[length]:
                if predecessor(s, w):
                    ans = max(ans, 1 + chain(length+1, w))
            return ans
        
        ans = 1
        for l in range(minlen, maxlen+1):
            for w in graph[l]:
                res = chain(l+1, w) +1 
                # print(res)
                ans = max(ans, res)
        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            # if(len(w1) + 1 != len(w2)):
            #     return False
            
            skip = 0
            for i in range(len(w2)):
                j = i - skip
                if(j == len(w1)):
                    break
                if(w1[j] != w2[i]):
                    skip += 1
                    if(skip >= 2):
                        return False
            
            return True
        
        words.sort(key=len, reverse=True)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in range(0, i):
                
                if(len(words[i]) + 1 == len(words[j])and combos[i] < combos[j] + 1 and is_predecessor(words[i], words[j])):
                    # print(words[i] + ', ' + words[j])
                    combos[i] = combos[j] + 1
                    
        for i in range(len_words):
            print(combos[i], end=', ')
            print(words[i])
        
        
        return max(combos) + 1
        

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        if not words:
            return 0
                
        # O(n log n)
        words = sorted(words, key = lambda x: len(x))
                    
        # O(n) space
        dp = [1]*len(words)
        
        # O(n^2)
        for i in range(1, len(words)):
            
            for j in range(i-1, -1, -1):
                                
                
                if len(words[i]) == len(words[j])+1:
                    
                    update = True
                    
                    for c in words[j]:
                        if c not in words[i]:
                            update = False
                    
                    if update:
                        dp[i] = max(dp[i], 1 + dp[j])
                                                                            
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words=sorted(words, key=len)
        
        DP=[1 for idx in range(len(words))]
       
        
        
        for i in range(len(words)):
            
            for j in range(0,i):
                if len(words[i]) == len(words[j])+1:
                    
                    diff=0
                    
                    for comp in range(len(words[i])):
                        
                        if diff==0 and comp==len(words[i])-1:
                            diff=1
                            
                        elif words[i][comp]!=words[j][comp-diff]:
                            if diff==1:
                                diff=None
                                break
                            else:
                                diff=1
                    
                    if diff==1:
                        if DP[j]+1>DP[i]:
                            DP[i]=DP[j]+1


        return max(DP)
                        
                
                
        


class Solution:
    def checkPredecessor(self, word_1, word_2):
        find_extra = False
        for i in range(len(word_1)):
            chr_1 = word_1[i]
            if not find_extra:
                chr_2 = word_2[i]
            else:
                chr_2 = word_2[i + 1]
            if chr_1 != chr_2:
                if find_extra:
                    return False
                else:
                    if chr_1 != word_2[i + 1]:
                        return False
                    find_extra = True
        return True
            
    
    def longestStrChain_old(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        longest_seq = [1] * len(words)
        for i in range(1, len(words)):
            curr_word = words[i]
            for j in range(0, i)[::-1]:
                prev_word = words[j]   
                if len(prev_word) == len(curr_word):
                    continue
                if len(prev_word) + 1 < len(curr_word):
                    break
                if self.checkPredecessor(prev_word, curr_word):
                    longest_seq[i] = max(longest_seq[i], longest_seq[j] + 1)
        return max(longest_seq)
    
    def longestStrChain(self, words):
        words.sort(key=lambda x: len(x))
        longest_chain = {}
        
        for word in words:
            longest_chain[word] = max([longest_chain.get((word[:i] + word[i + 1:]), 0) for i in range(len(word))]) + 1
        
        return max(longest_chain.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words=sorted(words, key=len)
        map={}
        DP=[1 for idx in range(len(words))]
       
        
        
        for i in range(len(words)):
            map={}
            for j in range(0,i):
                if len(words[i]) == len(words[j])+1:
                    
                    diff=0
                    
                    for comp in range(len(words[i])):
                        
                        if diff==0 and comp==len(words[i])-1:
                            diff=1
                            
                        elif words[i][comp]!=words[j][comp-diff]:
                            if diff==1:
                                diff=None
                                break
                            else:
                                diff=1
                    
                    if diff==1:
                        if DP[j]+1>DP[i]:
                            DP[i]=DP[j]+1

        #print (words)
        #print (DP) 
        return max(DP)
                        
                
                
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        n = len(words)
        dp = [1] * n
        for i in range(1, n):
            word = words[i]
            word_len = len(word)
            for j in range(0, i):
                if len(words[j]) == word_len - 1:
                    flag = False
                    for t in range(word_len):
                        if word[:t] + word[t+1:] == words[j]:
                            flag = True
                    if flag:
                        dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
                
            
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def check(s, t):
            m, n = len(s), len(t)
            flag = False
            j = 0
            if n != m + 1:
                return False
            for i in range(n):
                
                if j < m and t[i] == s[j]:
                    j += 1
                else:
                    if not flag:
                        flag = True
                    else:
                        return False
            return True
        words = sorted(words, key = lambda entry: len(entry), reverse = False)        
        table = [1]*len(words)
        for i in range(1, len(table)):
            maxVal = 0
            for j in range(i):
                if (len(words[j]) + 1 == len(words[i])) and check(words[j], words[i]) and (table[i] < table[j] + 1):
                    table[i] = table[j] + 1
        return max(table)
                    
                            
                        
                    
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        words = sorted(words, key=lambda x: len(x))
        print(words)
        def valid(word1, word2):
            i = 0
            j = 0
            flag = False
            while i < len(word1) and j < len(word2):
                if word1[i] == word2[j]:
                    i += 1
                    j += 1
                    continue
                if not flag and word1[i] != word2[j]:
                    flag = True
                    j += 1
                    continue
                if flag and word1[i] != word2[j]:
                    return False
            
            return i == len(word1)
                
        
        res = 1
        dp = [1] * len(words)
        for i in range(1, len(words)):
            for j in range(i):
                if len(words[j]) + 1 == len(words[i]) and valid(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j]+1)
            res = max(res, dp[i])
        # print(dp)
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def ispre(w1, w2):
            i, j = 0, 0
            while i < len(w1) and j < len(w2):
                if w1[i] == w2[j]:
                    i += 1
                    j += 1
                else:
                    j += 1
            return i == len(w1)
        
        words.sort(key=len)
        dp = [0] * len(words)
        
        def dfs(i):
            if dp[i] != 0:
                return dp[i]
            dp[i] = 1
            for j in range(i, len(words)):
                if len(words[j]) <= len(words[i]):
                    continue
                if len(words[j]) > len(words[i]) + 1:
                    break
                if ispre(words[i], words[j]):
                    dp[i] = max(dp[i], 1 + dfs(j))
            return dp[i]
            
        ans = max(dfs(i) for i in range(len(words)))
        return ans

class Solution:
    def is_chainable(self, shorter, longer):
        for i in range(len(longer)):
            if shorter == longer[:i] + longer[i+1:]:
                return True
        return False
    
    def longestStrChain(self, words: List[str]) -> int:
        sw = sorted(words, key=lambda x: len(x))
        n_words = len(sw)
        chains = [1] * n_words
        out = 1
        for i in range(n_words):
            word = sw[i]
            for j in range(i+1, n_words):
                next_word = sw[j]

                if len(word)==len(next_word):
                    continue
                elif len(word)-len(next_word) != -1:
                    break

                if self.is_chainable(word, next_word):
                    chains[j] = chains[i] + 1
                    out = max(out, chains[j])
        return out


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        ans = 1
        dp = [1 for i in range(len(words))]
        for r in range(1, len(words)):
            for l in range(r):
                if len(words[r]) == len(words[l]) + 1:
                    missingLetters = 0
                    i = 0
                    for j in range(len(words[r])):
                        if i >= len(words[l]) or words[l][i] != words[r][j]:
                            missingLetters += 1
                            if missingLetters > 1:
                                break
                        else:
                            i += 1
                    if missingLetters == 1:
                        dp[r] = max(dp[r], dp[l] + 1)
                        ans = max(ans, dp[r])
        return ans
                
                
                
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        n = len(words)
        dp = [1] * n
        for i in range(1, n):
            word = words[i]
            word_len = len(word)
            for j in range(0, i):
                flag = False
                if len(words[j]) == word_len - 1:
                    for t in range(word_len):
                        if word[:t] + word[t+1:] == words[j]:
                            flag = True
                if flag:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
                
            
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        if len(words)==0:
            return 0
        def isRelate(small:str,big:str) -> bool:
            for i in range(len(big)):
                if small==big[:i]+big[i+1:]:
                    return True
            return False
        def lengthList(target:int)-> List[str]:
            if target==0:
                return []
            final=[]
            for i in words:
                if len(i)==target:
                    final.append(i)
            return final
        
        dp=collections.defaultdict(int)
        words.sort(key=lambda x:len(x))
        for word in words:
            compareList=lengthList(len(word)-1)
            maxx=0
            if len(compareList)==0:
                dp[word]=1
            else:
                
                for small in compareList:
                    if isRelate(small,word):
                        maxx=max(maxx,dp[small])
        
                dp[word]=maxx+1
        
        
        return max(dp.values())
                
        


                    
#         def recur(word:str,words:List[str],remain:List[str],length:int):
#             if len(word)==len(max(words)) and len(remain)==0:
#                 return 1
            


#sort words by length, and we will attempt to shrink longer words and jump backwards to calculated length
#store dictionary of all words, with the chain length being 1
#for each word, iteratively remove one letter (O(k), k = len(word))
#check if the smaller word is now in the dictionary, and if it is, increase the current words chain length to be the smaller words chain + 1
#keep track of longest chain

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        word_list = sorted(words, key = lambda x: len(x))
        word_dict = {word:1 for word in word_list}
        
        longest = 0
        for word in word_list:
            for i in range(len(word)):
                test = word[:i] + word[i+1:]
                if test in word_dict:
                    word_dict[word] = word_dict[test] + 1
                longest = max(longest, word_dict[word])
        return longest


from collections import Counter
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def in_c(a: int, b: int):
            for k, v in list(a.items()):
                if k not in b or b[k] < v:
                    return False
            return True

        N = len(words)
        words = sorted(words, key=lambda i:len(i))
        word_c = [Counter(ii) for ii in words]
        dp = [1] * N
        for ii in range(N):
            can = [kk for jj, kk, mm in zip(word_c[:ii], dp[:ii], words[:ii]) if len(words[ii]) == len(mm) + 1 and in_c(jj, word_c[ii])]
            if can:
                dp[ii] = max(can) + 1
        print(words)
        print(dp)
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        words.sort(key = lambda w: len(w))
        
        def predecessor(a: str, b: str):
            if len(b) - len(a) == 1:
                mismatch = 0
                a_idx , b_idx = 0, 0
                
                while a_idx < len(a) and mismatch <=1:
                    
                    if a[a_idx] != b[b_idx]:
                        mismatch +=1
                        b_idx +=1
                    else:
                        a_idx = a_idx+1
                        b_idx = b_idx+1
                         
                if mismatch == 1 or not mismatch: return True
            
            return False
        
        ## max length end at word i
        dp = [1]
        for word in words[1:]:
            max_length = 1
            for i in range(len(dp)):
                if predecessor(words[i], word):
                    max_length = max(max_length, dp[i] + 1)
                    
            dp.append(max_length)
            
        
        return max(dp)
                
            
            
                
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [ 1 for i in range(len(words))]
        words.sort(key=lambda x:len(x))
        for i in range(len(words)):
            for j in range(i):
                if len(words[j]) == len(words[i]):
                    continue
                if len(words[i]) - len(words[j]) > 1:
                    continue
                else:
                    count = 0
                    for ch in words[j]:
                        if ch in words[i]:
                            count += 1
                    
                    if count == len(words[j]):
                        dp[i] = dp[j] + 1
        
        return max(dp)
    
                            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def connected(x, y):
            i, j = 0, 0
            count = 0
            while i < len(y) and j < len(x):
                if y[i] != x[j]:
                    if count == 0:
                        j += 1
                        count += 1
                    else:
                        return False
                else:
                    i += 1
                    j += 1
            return True
                    
        sort_words = sorted(words, key=len) 
        res = [1 for i in range(len(sort_words))]
        
        for i in range(len(sort_words)):
            x = sort_words[i]
            for j in range(i, len(sort_words)):
                y = sort_words[j]
                if len(y) == len(x)+1 and connected(y, x):
                    res[j] = max(res[j], res[i]+1)
                elif len(y) > len(x)+1:
                    break
        return max(res)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPred(w1,w2):
            if len(w1)!=(len(w2)-1):
                return False
            i,j = 0,0
            while i<len(w1):
                if w1[i]!=w2[j]:
                    if i!=j:
                        return False
                    j+=1
                else:
                    i+=1
                    j+=1
            return True
            
        

        words=sorted(words,key=lambda x: len(x))
        dp = [1]*len(words)
        for i in range(len(words)):
            for j in range(i): 
                if isPred(words[j],words[i]):
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if(len(words)==0):
            return False
        words.sort(key=lambda x:len(x))
        def check(a, b):
            for i in range(len(b)-1):
                if(a[i]!=b[i]):
                    return(b[i+1:]==a[i:])
            return True
        ans = 1
        dp = [1]*len(words)
        for i in range(len(words)):
            for j in range(i+1, len(words)):
                if(len(words[j])>1+len(words[i])):
                    break
                if(len(words[j])==1+(len(words[i]))):
                    if(check(words[i], words[j])):
                        dp[j] = max(dp[j], 1+dp[i])
                        ans = max(ans, dp[j])
        return ans

# class Solution:
#     def longestStrChain(self, words: List[str]) -> int:
        
        
#         https://leetcode.com/problems/longest-string-chain/discuss/585044/C%2B%2B-Using-unordered_map-and-DP
# DP

# 1. sort words in increasing order of length
# 2. if len is same

# LIS

# dp[i] = longest word chain including index i
# return max(dp)

# dp[i] = max(dp[i], 1+ dp[j])
# len(words[j]) + 1 = len(words[i])
# ba, bda-> check all chars of j are in j in the same order

# O(n^2*16)





from collections import deque

def getKey(item):
    return len(item)

def isPred(word1, word2):
    word1 = list(word1)
    word2 = list(word2)
    q1 = deque(word1)
    q2 = deque(word2)
    while q1 and q2:
        if q1[0] == q2[0]:
            q1.popleft()
            q2.popleft()
        else:
            q2.popleft()
    if (not q1) and len(q2) <= 1:
        return True
    return False
    

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=getKey)
        dp = [1]*len(words)
        
        for i in range(1, len(dp)):
            for j in reversed(list(range(0, i))):
                if len(words[j]) == len(words[i]):
                    continue
                elif len(words[j]) + 1== len(words[i]):
                    if isPred(words[j], words[i]):
                        dp[i] = max(dp[i], 1+ dp[j])
                else:
                    break
        
        return max(dp)
        
        


# We can also think of thi problem as LIS problem 


# 1. Sort the words according to their length
# Input: [\"a\",\"b\",\"ba\",\"bca\",\"bda\",\"bdca\"]
# When you reach bdca ..check if dca is present..check if bca is present..check if bda is present..check if bdc is present
# 2. So store each word in a hashmap form lookup in O(1)

# 3. For each word in order of length, for each word2 which is word with one character removed, dp[word] = max(dp[word], dp[word2] + 1).
# The maxm element in the dp array is the answer.



# Clean implementation

# https://leetcode.com/problems/longest-string-chain/discuss/376724/C%2B%2B-Simple-clean





# Explaiantion:

# https://leetcode.com/problems/longest-string-chain/discuss/295182/C%2B%2B-Bottom-Up-DP-solution-(with-Explanation)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        words.sort(key=len)
        f = [1] * n
        
        def ispre(w1, w2):
            if len(w1) != len(w2) - 1:
                return False
            i = j = 0
            diff = False
            while i < len(w1) and j < len(w2):
                if w1[i] != w2[j]:
                    if not diff:
                        diff = True
                        j += 1
                        continue
                    else:
                        return False
                i += 1
                j += 1
            return True
        
        res = 1
        for i in range(1, n):
            for j in range(i):
                if ispre(words[j], words[i]):
                    #print(words[j], words[i])
                    f[i] = max(f[i], f[j] + 1)
            res = max(res, f[i])
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) == 0:
            return 0
        
        def findDistance(shorterString, longerString):
            if len(longerString) - len(shorterString) != 1:
                return False
            i, j = 0, 0
            edits = 0
            while j < len(shorterString):
                if longerString[i] != shorterString[j]:
                    if edits == 0:
                        i+=1
                        edits += 1
                        continue
                    else:
                        return False
                i += 1
                j += 1
            return True
        
        dp = [1]*len(words)
        words = sorted(words, key=lambda x: len(x))
        maxAnswer = 0
        for i in range(len(words)):
            temp = 1
            for j in range(i+1):
                if findDistance(words[j], words[i]):
                    temp = max(temp, dp[j]+1)
            dp[i] = temp
            maxAnswer = max(maxAnswer, dp[i])
        # print(dp)
        return maxAnswer

def find(s1,s2):
    i = 0
    j = 0
    count = 0
    while i < len(s1) and j < len(s2):
        if s1[i] == s2[j]:
            i += 1
            j += 1
        else:
            j += 1
            count += 1
    
    if i == len(s1):
        return True
    if count == 1:
        return True
    
    return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        new_words = []
        for w in words:
            new_words.append((len(w),w))
        
        new_words.sort()
        dp = [1]*len(words)
        for i in range(len(words)):
            for j in range(i+1,len(words)):
                l1,w1 = new_words[i]
                l2,w2 = new_words[j]
                if l2-l1 > 1:
                    break
                if l2-l1 == 1:
                    diff = find(w1,w2)
                    if diff == 1:
                        dp[j] = max(dp[j],dp[i]+1)
        
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        dp = collections.defaultdict(int)
        for word in words:
            dp[word] = max([dp[word[:i] + word[i + 1:]] for i in range(len(word))]) + 1
        return max(dp.values()) 

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def if_pred(a, b):
            if len(a) + 1 != len(b): 
                return False 
            i = 0 
            j = 0 
            while i < len(a): 
                if a[i] == b[j]: 
                    i += 1 
                j += 1 
                
                if j - i > 1: 
                    return False                 
            else: 
                return True 
                
        words.sort(key=lambda x: len(x))        
        dp = [1 for _ in range(len(words))]
        
        for i in range(len(words)): 
            for j in range(i-1, -1, -1):
                if if_pred(words[j], words[i]): 
                    dp[i] = max(dp[i], dp[j] + 1)

                        
        print(dp)
        return max(dp)



class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words_sort = [(len(word), word) for word in words]
        words_sort.sort()
        words = [word for w_len, word in words_sort]
        
        
        def isPredecessor(pre:str, succ:str) -> bool:
            
            if len(pre) != len(succ)-1:
                return False
            
            pre = pre + '.'
            d = 0
            for i in range(len(succ)):
                if pre[i-d] != succ[i]:
                    d += 1
                    if d>=2:
                        return False
            return True
        
        
        chain = [1] * len(words)
        ans = 1
        for i in range(len(words)):
            for j in range(0, i):
                if isPredecessor(words[j], words[i]):
                    chain[i] = max(chain[i], chain[j]+1)
                    ans = max(ans, chain[i])
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def if_pred(a, b):
            if len(a) + 1 != len(b): 
                return False 
            i = 0 
            j = 0 
            while i < len(a): 
                if a[i] == b[j]: 
                    i += 1 
                j += 1 
                
                if j - i > 1: 
                    return False                 
            else: 
                return True 
                
        words.sort(key=lambda x: len(x))        
        dp = [1 for _ in range(len(words))]
        
        for i in range(len(words)): 
            for j in range(i-1, -1, -1):
                if if_pred(words[j], words[i]): 
                    if dp[i] < dp[j] + 1: 
                        dp[i] =  dp[j] + 1

                        
        print(dp)
        return max(dp)



class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def determine(s1, s2):
            if len(s2) != len(s1) + 1:
                return False
            
            insert = False
            j = 0
            for i in range(len(s1)):
                while s2[j] != s1[i]:
                    # print(s2[j], s1[i])
                    if insert: return False
                    j = j + 1
                    insert = True
                j = j + 1
            return True
        
        d = {}
        words = sorted(words, key = lambda x: len(x))
        d[words[-1]] = 1
        for i in reversed(list(range(len(words)-1))):
            m = 0
            d[words[i]] = 1
            for j in range(i, len(words)):
                # print(words[i], words[j], determine(words[i], words[j]))
                if determine(words[i], words[j]):
                    if d[words[j]] > m:
                        d[words[i]] = d[words[j]] + 1
                        m = d[words[i]]
                        
        # print(d)
                        
        out = 0
        for k in d:
            if d[k] > out:
                out = d[k]
        
        return out
                    
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPred(w1,w2):
            if len(w1)!=(len(w2)-1):
                return False
            i,j = 0,0
            while i<len(w1):
                if w1[i]!=w2[j]:
                    if i!=j:
                        return False
                    j+=1
                else:
                    i+=1
                    j+=1
            return True
           
        words=sorted(words,key=lambda x: len(x))
        dp = [1]*len(words)
        for i in range(len(words)):
            for j in range(i): 
                if isPred(words[j],words[i]):
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
            


def is_pred(w1, w2):
    skip_count = 0
    if len(w1)+1 != len(w2):
        return False

    for i in range(0, len(w1)):
        if w1[i] == w2[i+skip_count]:
            pass
        elif skip_count == 0 and w1[i] == w2[i+1]:
            skip_count += 1
        else:
            return False

    return True

def tests():
    assert is_pred('a', 'ab')
    assert not is_pred('a', 'a')
    assert not is_pred('a', 'bc')
    assert not is_pred('ab', 'a')
    
class Solution:

        
    def longestStrChain(self, words: List[str]) -> int:
        # tests()
        words.sort(key=lambda w: len(w))
        
        chains = {}
        current_max = 0
        for w in words:
            chains[w] = 1  
            current_max = max(current_max, 1)
            word_max = 1
            for chain, chain_len in list(chains.items()):
                # if chain_len > chains[w] and is_pred(chain, w):
                #     chains[w] = chain_len + 1
                    
                if is_pred(chain, w) and word_max < chain_len+1:
                    # chains[w] = max(chains[w], chain_len+1)
                    chains[w] = chain_len+1
                    word_max = chain_len+1
                    current_max = max(current_max, word_max)
        
        
        return current_max
    

    # [a, ab, abc] abcd


from collections import Counter
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [1] * len(words)
        words.sort(key=len)
        for i in range(1, len(words)):
            d1 = Counter(words[i])
            for j in range(i):
                if len(words[i]) - len(words[j]) == 1:
                    d2 = Counter(words[j])
                    check = 0
                    for k in d1:
                        if k not in d2:
                            check += 1
                        elif d2[k] != d1[k]:
                            check += 1
                        if check == 2:
                            break
                    if check == 1:
                        dp[i] = max(dp[i], dp[j]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_pred(short, long):
            if len(short) + 1 != len(long):
                return False
            short +=  ' '
            new = None
            for i in range(len(long)):
                if short[i] != long[i]:
                    new = i
                    break
            if short[:-1] == long[:new] + long[(new+1):]:
                return True
            return False
        words.sort(key = lambda x : len(x)) 
        dp = [1]*len(words)
        for i in range(1, len(words)):
            for j in range(i):
                if is_pred(words[j], words[i]):
                    if dp[j] >= dp[i]:
                        dp[i] = dp[j] + 1
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        self.res = 0
        
        def compare(w1, w2):
            if len(w1) > len(w2):
                w1, w2 = w2, w1
                
            if len(w2) - len(w1) != 1:
                return False
            
            i, j = 0, 0
            
            flag = False
            while i < len(w1) or j < len(w2):
                if i < len(w1) and w1[i] == w2[j]:
                    i += 1
                    j += 1
                
                elif not flag:
                    flag = True
                    j += 1
                    
                else:
                    return False
                
            return True
        
        @lru_cache(maxsize=None)
        def dfs(w):
            length = 0
            for word in words:
                if len(word) - len(w) == 1 and compare(w, word):
                    length = max(dfs(word), length)
                    
            ret = length + 1
            self.res = max(ret, self.res)
            return ret
        
        for w in words:
            dfs(w)
            
        return self.res
        
                
            
        


def is_pred(w1, w2):
    skip_count = 0
    if len(w1)+1 != len(w2):
        return False

    for i in range(0, len(w1)):
        if w1[i] == w2[i+skip_count]:
            pass
        elif skip_count == 0 and w1[i] == w2[i+1]:
            skip_count += 1
        else:
            return False

    return True

def tests():
    assert is_pred('a', 'ab')
    assert not is_pred('a', 'a')
    assert not is_pred('a', 'bc')
    assert not is_pred('ab', 'a')
    
class Solution:

        
    def longestStrChain(self, words: List[str]) -> int:
        tests()
        words.sort(key=lambda w: len(w))
        
        chains = {}
        current_max = 0
        for w in words:
            chains[w] = 1  
            current_max = max(current_max, 1)
            for chain, chain_len in list(chains.items()):
                if is_pred(chain, w):
                    if w in chains:
                        chains[w] = max(chains[w], chain_len+1)
                    else: 
                        chains[w] = chain_len + 1
                    current_max = max(current_max, chains[w])
        
        
        return current_max
    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        words.sort(key = lambda x: len(x))
        for w in words:
            maxLen = 0
            for i in range(len(w)):
                predecessor = w[:i] + w[i+1:]
                maxLen = max(maxLen, dp.get(predecessor, 0) + 1)
            dp[w] = maxLen
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def check(w1, w2):
            if len(w1)!=len(w2)-1:
                return False
            cnt = 0
            idx1 = 0
            idx2 = 0
            while idx1<len(w1) and idx2<len(w2) and cnt<=1:
                if w1[idx1]==w2[idx2]:
                    idx1 += 1
                    idx2 += 1
                else:
                    cnt += 1
                    idx2 += 1
            if cnt==0 and idx1==len(w1) and idx2==len(w2)-1:
                return True
            elif cnt==1 and idx1==len(w1) and idx2==len(w2):
                return True
            else:
                return False
        
        # print (check(words[0], words[1]))
        # return
        words.sort(key=len)
        dp = [1 for _ in range(len(words))]
        for j in range(len(words)):
            for i in range(j):
                if check(words[i], words[j]):
                    # print (i, j)
                    dp[j] = max(dp[j], dp[i]+1)
        res = max(dp)
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def isPre(st1,st2):
            if len(st2)!=len(st1)+1:
                return False
            j=0
            diff=0
            for i in range(len(st2)):
                if diff>1:
                    return False
                elif j<len(st1) and st1[j]==st2[i]:
                    j+=1
                else:
                    diff+=1
            return diff<=1

        words.sort(key=lambda x: len(x))
        n=len(words)
        dp=[1]*n
        #print(words)
        for i in range(1,n):
            for j in range(i):
                if isPre(words[j],words[i]):
                    dp[i]=max(dp[i],dp[j]+1)
        #print([(words[i],dp[i]) for i in range(n)])
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        '''
        
        abc dabc
        
        max_val = max(i->(0,len(a)) f(n,i-1))
        
        '''
        def is_predessor(word1, word2):
            
            if(abs(len(word1) - len(word2)) > 1):
                return False
            else:
                count = 0
                word1_cnt = 0
                word2_cnt = 0
                
                while(word1 and word2):
                    if(word1[0] == word2[0]):
                        word1 = word1[1:]
                        word2 = word2[1:]
                    else:
                        count+=1
                        word1 = word1[1:]
                    
                    if(count > 1):
                        return False
                
                
                return True
        
            
        words = sorted(words, key = lambda x:len(x))
        
        M = [0]*len(words)
        M[0] = 1
        overall_max = float('-inf')
        for i in range(1, len(M)):
            max_val = float('-inf')
            for j in range(i):
                if((len(words[i]) != len(words[j])) and is_predessor(words[i], words[j])):
                    max_val = max(max_val, 1 + M[j])
                
            M[i] = max(1, max_val)
            overall_max = max(overall_max, M[i])
        
        return overall_max
                
            


class Solution:
    def predecessor(self, larger, smaller):
        largerCount = Counter(larger)
        for c in smaller:
            if c in largerCount:
                largerCount[c] -= 1
                if largerCount[c] == 0:
                    del largerCount[c]
        mc = largerCount.most_common()
        return len(mc) == 1 and mc[0][1] == 1
    
    def longestStrChain(self, words: List[str]) -> int:
        lengthToWords = defaultdict(list)
        for word in words:
            lengthToWords[len(word)].append(word)
        wordToCL = defaultdict(lambda: 1)
        res = 1
        
        for wordLength in range(16, 1, -1):
            if wordLength - 1 not in lengthToWords:
                continue
            for larger in lengthToWords[wordLength]:
                for smaller in lengthToWords[wordLength-1]:
                    if self.predecessor(larger, smaller):
                        wordToCL[smaller] = max(wordToCL[smaller], wordToCL[larger] + 1)
                        res = max(res, wordToCL[smaller])
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def is_predecessor(word_short, word_long) -> bool:
            if len(word_short)+1 != len(word_long):
                return False
            i_s = 0
            can_skip = True
            for i_l in range(len(word_long)):
                if i_s > len(word_short)-1:
                    return True
                c_s = word_short[i_s]
                c_l = word_long[i_l]
                if c_s == c_l:
                    i_s += 1
                else:
                    if can_skip:
                        # i_s += 1
                        can_skip = False
                        pass
                    else:
                        return False
            return True
        
        if not words:
            return 0
        
        words.sort(key=lambda x: len(x))
        # print(words)
        dp = [1] * len(words)

        for r in range(1, len(words)):
            # print(r)
            for l in range(r):
                if is_predecessor(words[l], words[r]):
                    # print('ok',words[l], words[r], l, dp[l])
                    dp[r] = max(dp[r], dp[l]+1)
                
        # print(dp)
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(w1, l1, w2, l2):
            if l2 == l1 + 1:
                for i in w1:
                    if i not in w2:
                        return False
                return True
            return False
        
        words.sort(key = lambda x: len(x))
        L = [len(i) for i in words]
        DP = [1 for i in range(len(L))]
        
        ans = 1
        for i in range(1, len(L)):
            for j in range(0,i):
                if isPredecessor(words[j], L[j], words[i], L[i]):
                    DP[i] = max(DP[j]+1, DP[i])
            ans = max(ans, DP[i])
        
        return ans

def isPredecessor(src, dst):
    nsrc = len(src)
    ndst = len(dst)
    if nsrc+1 != ndst:
        return False
    
    i = 0
    while i < nsrc:
        if src[i] == dst[i]:
            i += 1
            continue
        else:
            return src[i:] == dst[i+1:]
    
    return True
        
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        
        mem_len = [-1 for _ in range(len(words))]
        
        def search(i):
            if mem_len[i] != -1:
                return 
            
            ans = 1
            for j in range(i+1, len(words)):    
                if isPredecessor(words[i], words[j]):
                    search(j)
                    ans = max(ans, mem_len[j]+1)
                    
            mem_len[i] = ans
            
        for i in range(len(words)):
            search(i)

        return max(mem_len)

from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def isPartOfChain(longer,shorter):
            if len(longer) - len(shorter) != 1:
                return False 
            
            short_ptr = 0
            long_ptr = 0
            
            num_differences = 0
            while long_ptr <= len(longer):
                
                
                if num_differences > 1:
                    return False

                if short_ptr > len(shorter)-1:
                    break
                
                if shorter[short_ptr] == longer[long_ptr]:
                    short_ptr += 1
                    long_ptr += 1
                
                else:
                    long_ptr += 1
                    num_differences += 1
            
            return True 
            
    
        def longestHelp(words):
            
            
            if len(words) <= 1:
                return 1
            
            
            dp = [1 for _ in range(len(words))]
            
            #dp[i] = longest chain at words[i] 
            
            # max_length is always 1 for each word
            max_length = 1
            for word in range(0,len(dp)):
                for j in range(word-1,-1,-1):
                    if isPartOfChain(words[word], words[j]):
                        dp[word] = max(dp[word],1+dp[j])
                        max_length = max(max_length, dp[word])


            return max_length
        
        words.sort(key = len)
        return longestHelp(words)
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        lis = [1 for _ in range(n)]
        def cmp(w1, w2):
            for k in range(len(w1)):
                if (w1[0:k] + w1[k+1:]) == w2:
                    return True
            return False
        
        words = sorted(words, key=len)
        # print(words)
        for i in range(1, n):
            for j in range(i):
                l1, l2 = len(words[j]), len(words[i])
                if l1 == (l2 - 1):
                    if cmp(words[i], words[j]):
                        if lis[i] < lis[j] + 1:
                            lis[i] = lis[j] + 1
        
        
        # print(lis)
        return max(lis)

class Solution:
    def _is_pred(self, word1: str, word2: str) -> bool:
        if(not (len(word1) == len(word2)-1)):
            return False
        
        for c in word1:
            if c not in word2:
                return False
        
        return True
        
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        n = len(words)
        dp = [1 for i in range(n)]
        
        for i in range(n):
            for j in range(i):
                if(self._is_pred(words[j], words[i])):
                    dp[i] = max(dp[i], dp[j] + 1)
        
        return (max(dp))

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key=lambda w: len(w))
        
        seenCache = {}
        max_len = 0
        
        for cur_word in words:
            # print(cur_word)
            cur_len = 0
            for i in range(len(cur_word)):
                check_word = cur_word[:i] + cur_word[i+1:]
                cur_len = max(cur_len, seenCache.get(check_word, 0) + 1)
                    
            seenCache[cur_word] = cur_len
            max_len = max(max_len, cur_len)
        
        return max_len

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        if not words:
            return 0
        n = len(words)
        dp = [1] * n
        for i in range(n):
            for j in range(i):
                if self.valid(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)

        return max(dp)

    def valid(self, str1, str2):
        if len(str1)+1 != len(str2):
            return False
        cnt = 0
        n = len(str1)
        l1, l2 = 0, 0
        while l1 < n and l2 < n + 1:
            if cnt > 1:
                return False

            if str1[l1] != str2[l2]:
                l2 += 1
                cnt += 1
            else:
                l1 += 1
                l2 += 1

        return cnt <= 1 


                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        words.sort(key = lambda x: len(x))

        #start with any word in words, find the chain.
        #if a word is chained in other word chain, it cannot be a beginning word
        visited = set()
        res = 1
        for i in range(len(words)):
            if words[i] in visited:
                continue
            visited.add(words[i])
            res = max(res, self.dfs(words, words[i], i + 1, visited))
        
        return res
    
    def dfs(self, words, cur, start, visited):
        if start >= len(words):
            return 1
        ma = 1
        for i in range(start, len(words)):
            if len(words[i]) > len(cur) + 1:
                break
            if len(words[i]) == len(cur):
                continue
            if not self.isPredecessor(cur, words[i]):
                continue
            visited.add(words[i])
            ma = max(ma, 1 + self.dfs(words, words[i], i + 1, visited))
        
        return ma
    
    def isPredecessor(self, w1, w2):
        #print(w1, w2)
        for i in range(len(w1)):
            if w1[:i] == w2[:i] and w1[i:] == w2[i + 1:]:
                #print(\"True\")
                return True
        #不要忘记检查一下这种情况，没被包含在上面
        if w1 == w2[:-1]:
            return True
        return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPred(w1,w2):
            i,j = 0, 0
            if len(w1)!=(len(w2)-1):
                return False
            while i < len(w1):
                if w1[i] != w2[j]:
                    if j != i:
                        return False
                    j += 1
                else:
                    j += 1
                    i += 1
            return True

        
        words=sorted(words,key=lambda x: len(x))
        dp = [1]*len(words)
        for i in range(len(words)):
            for j in range(i): 
                if isPred(words[j],words[i]):
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
            


class Solution:
    
    def can_move(self, a, b):
        b_letters = Counter(b)
        
        for char in a:
            b_letters[char] -= 1
        
            if b_letters[char] < 0:
                return False
            elif b_letters[char] == 0:
                b_letters.pop(char)
        
        return b_letters.popitem()[1] == 1


    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda w: len(w))
        
        depths = {}
        def recur(index):
            if index not in depths:
                s = words[index]

                mx = 0
                for i in range(index + 1, len(words)):
                    if len(words[i]) - len(s) == 1 and self.can_move(s, words[i]):
                        mx = max(recur(i), mx)
                        
                depths[index] = mx + 1
                        
            return depths[index]
        
        for i in range(len(words)):
            recur(i)
        
        return max(depths.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # O(NNS) Solution
        predecessors = dict()
        words = sorted(words, key=len)
        for i in range(len(words)):
            predecessors[words[i]] = []
            for j in range(i):
                if self.isPredecessor(words[i], words[j]):
                    predecessors[words[i]].append((words[j],j))
        #print(words)
        #print(predecessors)
        T = [0] * len(words)
        T[0] = 1
        for i in range(1, len(words)):
            if predecessors[words[i]]:
                T[i] = max([T[j[1]] for j in predecessors[words[i]]]) + 1
            else:
                T[i] = 1
        return max(T)
            
    def isPredecessor(self, word, candidate):
        if len(candidate) + 1 != len(word):
            return False
        i, j = 0, 0
        wildcard = True
        while i < len(word) and j < len(candidate):
            if word[i] == candidate[j]:
                i += 1
                j += 1
            else:
                if wildcard:
                    i += 1
                    wildcard = False
                else:
                    return False
        return i+1 == len(word) or i == len(word) if wildcard else i == len(word)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        words.sort(key=lambda x : len(x))
        f = [1] * n
        
        def ispre(w1, w2):
            if len(w1) != len(w2) - 1:
                return False
            l1, l2 = sorted(w1), sorted(w2)
            i = j = 0
            diff = False
            while i < len(l1) and j < len(l2):
                if l1[i] != l2[j]:
                    if not diff:
                        diff = True
                        j += 1
                        continue
                    else:
                        return False
                i += 1
                j += 1
            return True
        
        res = 1
        for i in range(1, n):
            for j in range(i):
                if ispre(words[j], words[i]):
                    #print(words[j], words[i])
                    f[i] = max(f[i], f[j] + 1)
            res = max(res, f[i])
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # O(NNS) Solution
        predecessors = dict()
        words = sorted(words, key=len)
        for i in range(len(words)):
            predecessors[words[i]] = []
            for j in range(i):
                if self.isPredecessor(words[i], words[j]):
                    predecessors[words[i]].append((words[j],j))
        print(words)
        print(predecessors)
        T = [0] * len(words)
        T[0] = 1
        for i in range(1, len(words)):
            if predecessors[words[i]]:
                T[i] = max([T[j[1]] for j in predecessors[words[i]]]) + 1
            else:
                T[i] = 1
        return max(T)
            
    def isPredecessor(self, word, candidate):
        if len(candidate) + 1 != len(word):
            return False
        i, j = 0, 0
        wildcard = True
        while i < len(word) and j < len(candidate):
            if word[i] == candidate[j]:
                i += 1
                j += 1
            else:
                if wildcard:
                    i += 1
                    wildcard = False
                else:
                    return False
        return i+1 == len(word) or i == len(word) if wildcard else i == len(word)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda word: (len(word)))
        lengths = [1] * len(words)
        for i in range(len(words)):
            for j in range(i):
                if self.checkifSuccessor(words[j],words[i]):
                    lengths[i] = max(lengths[i], lengths[j] + 1)
        return max(lengths)
                    
                
    def checkifSuccessor(self, word1, word2):
        if len(word1) + 1 !=  len(word2):
            return False
        else:
            indexOfword1 = 0
            differentReached = False
            for a in range(len(word2)):
                if indexOfword1 == len(word1):
                    return True
                if word2[a] == word1[indexOfword1]:
                    indexOfword1 += 1
                else:
                    if differentReached == True:
                        return False
                    else:
                        differentReached = True
            return True
            
                    


from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = defaultdict(int)
        words.sort(key = lambda x: len(x))
        for w in words:
            maxLen = 0
            for i in range(len(w)):
                predecessor = w[:i] + w[i+1:]
                maxLen = max(maxLen, dp[predecessor] + 1)
            dp[w] = maxLen
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        N = len(words)
        
        def is_predecessor(word1: str, word2: str) -> bool:
            if (len(word1) + 1 == len(word2)):
                diffCount = 0
                i, j = 0, 0
                while (i < len(word1) and j < len(word2)):
                    if (word1[i] == word2[j]):
                        i += 1
                        j += 1
                    elif (diffCount == 0):
                        j += 1
                        diffCount = 1
                    else:
                        return False
                return True
            return False
        
        def backtracking(start: int, count: int) -> int:
            if (start in memo):
                return memo[start]
            
            sublongest = count
            for i in range(start + 1, N):
                if (is_predecessor(words[start], words[i])):
                    sublongest = max(sublongest, backtracking(i, count + 1))
            memo[start] = sublongest
            return sublongest
        
        words.sort(key = lambda x: (len(x), x))
        
        memo = dict()
        longest = 0
        for i in range(N):
            longest = max(longest, backtracking(i, 1))
        return longest

class Solution:
    def checkPredecessor(self, word_1, word_2):
        find_extra = False
        if len(word_1) + 1 != len(word_2):
            return False
        for i in range(len(word_1)):
            chr_1 = word_1[i]
            if not find_extra:
                chr_2 = word_2[i]
            else:
                chr_2 = word_2[i + 1]
            if chr_1 != chr_2:
                if find_extra:
                    return False
                else:
                    if chr_1 != word_2[i + 1]:
                        return False
                    find_extra = True
        return True
            
    
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        longest_seq = [1] * len(words)
        for i in range(1, len(words)):
            curr_word = words[i]
            for j in range(0, i):
                prev_word = words[j]
                if self.checkPredecessor(prev_word, curr_word):
                    longest_seq[i] = max(longest_seq[i], longest_seq[j] + 1)
        return max(longest_seq)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda word: (len(word)))
        lengths = [1] * len(words)
        for i in range(len(words)):
            for j in range(i):
                if self.checkifSuccessor(words[j],words[i]):
                    lengths[i] = max(lengths[i], lengths[j] + 1)
        return max(lengths)
                    
                
    def checkifSuccessor(self, word1, word2):
        if len(word1) + 1 !=  len(word2):
            return False
        else:
            indexOfword1 = 0
            differentReached = False
            for a in range(len(word2)):
                if indexOfword1 == len(word1):
                    break
                if word2[a] == word1[indexOfword1]:
                    indexOfword1 += 1
                else:
                    if differentReached == True:
                        return False
                    else:
                        differentReached = True
            return True
            
                    


class Solution:
    def isPredecessor(self, predecessorCandidate, word):
        if len(predecessorCandidate) != len(word)-1:
            return False
        pWhere = 0
        wWhere = 0
        hasSkipped = False
        while pWhere < len(predecessorCandidate) and wWhere < len(word):
            if predecessorCandidate[pWhere] != word[wWhere]:
                if hasSkipped: return False
                hasSkipped = True
                wWhere += 1
            else:
                wWhere+= 1
                pWhere += 1
        return True
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        predecessorDict = collections.defaultdict(list)
        chainCountDict = collections.defaultdict(lambda: 1)
        currentMax = 1
        for i in range(0, len(words)):
            for j in range(i+1, len(words)):
                if self.isPredecessor(words[i], words[j]):
                    predecessorDict[words[j]].append(words[i])
                    chainCountDict[words[j]] = max(chainCountDict[words[j]], 1+chainCountDict[words[i]])
                    currentMax = max(currentMax, chainCountDict[words[j]])
        return currentMax


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        dp = [1] * n
        words.sort(key=len)
        for i in range(1, n):
            for j in range(i):
                if self.is_predecessor(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
    
    def is_predecessor(self, word1, word2):
        if len(word1) - len(word2) != 1:
            return False
        i1 = 0
        i2 = 0
        diff = 0
        while i1 < len(word1) and i2 < len(word2):
            if word1[i1] == word2[i2]:
                i1 += 1
                i2 += 1
            else:
                i1 += 1
                diff += 1
                if diff > 1:
                    return False
        return True


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        if not words:
            return 0
        n = len(words)
        dp = [1] * n
        for i in range(n):
            for j in range(i):
                if self.is_predecessor(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)

        return max(dp)

    def is_predecessor(self, str1, str2):
        if len(str1)+1 != len(str2):
            return False
        cnt = 0
        n = len(str1)
        l1, l2 = 0, 0
        while l1 < n and l2 < n + 1:
            if cnt > 1:
                return False

            if str1[l1] != str2[l2]:
                l2 += 1
                cnt += 1
            else:
                l1 += 1
                l2 += 1

        return l1 == l2 or cnt == 1

                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        words.sort(key=len)
        f = [1] * n
        
        def ispre(w1, w2):
            if len(w1) != len(w2) - 1:
                return False
            
            it = iter(w2)
            return all(c in it for c in w1)
        
        res = 1
        for i in range(1, n):
            for j in range(i):
                if ispre(words[j], words[i]):
                    #print(words[j], words[i])
                    f[i] = max(f[i], f[j] + 1)
            res = max(res, f[i])
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        
        def helper(a,b):
            i,j = 0,0
            diff = 0
            while(i<len(a) and j<len(b)):
                
                if a[i] == b[j]:
                    i+=1
                    j+=1
                else:
                    i+=1
                    diff+=1
            if i<len(a):
                diff+=(len(a)-i)
            if j<len(b):
                diff+=(len(b)-j)
                            
            if diff>1:
                return False
            else:
                return True
                    
        dp = [1]*(len(words))
        words = sorted(words,key=len)
        
        for i in range(len(words)):
            for j in range(i):
                if len(words[i])-len(words[j]) == 1 and helper(words[i],words[j]):
                    dp[i] = max(dp[i],dp[j]+1)
        print(dp)     
        return max(dp)
                    
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # Sorting words by length is necessary
        words.sort(key = lambda x: len(x))
        max_len = [1] * len(words)
        res = 1
        for i in range(len(words) - 1):
            for j in range(i, len(words)):
                if self.isPredecessor(words[i], words[j]):
                    max_len[j] = max(max_len[i] + 1, max_len[j])
                    res = max(res, max_len[j])
        return res
    
    # Use this function to check whether s1 is a predecessor of s2
    def isPredecessor(self, s1, s2):
        # Corner case:
        if len(s2) - len(s1) != 1:
            return False
        # Use flag to record whether difference between s1 and s2 is found
        flag = 0
        i, j = 0, 0
        while i < len(s1) and j < len(s2):
            if s1[i] == s2[j]:
                i += 1
                j += 1
            elif flag == 0:
                flag = 1
                j += 1
            else:
                return False
        return True
    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def chain(w1, w2):                         # check if w1 is a chain to w2
            m, n = len(w1), len(w2)
            if abs(m-n) != 1: return False
            i, j, one = 0, 0, 1
            while i < m and j < n:
                if w1[i] == w2[j]: i, j = i+1, j+1
                elif one: one, i = 0, i+1
                else: return False    
            return True
                    
        if not words: return 0
        words.sort(key=lambda x: len(x))
        n = len(words)
        dp = [1] * n
        ans = 1
        for i in range(n):
            for j in range(i): # visited all previous words[j] to check if dp[i] can reach a longer chain
                if chain(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j] + 1)
            ans = max(ans, dp[i])
        return ans        
#https://blog.csdn.net/JamieLuo/article/details/106486186?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-2.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-2.add_param_isCf
#先把所有的words按照长度进行sort
#dp[i]：包含第i个单词在内的最长链
#dp[i] = max(dp[j]) + 1，其中j<i且words[j]是words[i]的前缀
#另外，在判断是否是前缀时：（1)pre一定是word的长度-1；（2)遍历两者，两者应该只有一个字母不同


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        words.sort(key=len)
        f = [1] * n
        
        def ispre(w1, w2):
            if len(w1) != len(w2) - 1:
                return False
            l1, l2 = sorted(w1), sorted(w2)
            i = j = 0
            diff = False
            while i < len(l1) and j < len(l2):
                if l1[i] != l2[j]:
                    if not diff:
                        diff = True
                        j += 1
                        continue
                    else:
                        return False
                i += 1
                j += 1
            return True
        
        res = 1
        for i in range(1, n):
            for j in range(i):
                if ispre(words[j], words[i]):
                    #print(words[j], words[i])
                    f[i] = max(f[i], f[j] + 1)
            res = max(res, f[i])
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words: return 0
        def chain(w1, w2):
            m, n = len(w1), len(w2)
            if abs(m-n) != 1: return False
            i, j, one = 0, 0, 1
            while i < m and j < n:
                if w1[i] == w2[j]: i, j = i+1, j+1
                elif one: one, i = 0, i+1
                else: return False    
            return True
                    
        words.sort(key=lambda x: len(x))
        n = len(words)
        dp = [1] * n
        ans = 1
        for i in range(n):
            for j in range(i):
                if chain(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j] + 1)
            ans = max(ans, dp[i])
        return ans        

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        ##do DP, DP[i] = length of longest possible word chain ending at words[i]
        
        words = sorted(words, key= lambda x: len(x))
        # print(words)
        DP = [0 for i in words]
        
        max_len = 0
        for i in range(len(words)):
            length = []
            for j in range(1,i+1):
                if len(words[i-j])< len(words[i])-1:
                    break
                else:
                    if len(words[i-j]) == len(words[i])-1:
                        if self.check(words[i-j], words[i]) == True:
                            length.append(DP[i-j])
            if length:
                DP[i] = max(length)+1
            else:
                DP[i] = 1
        # print(DP)     
        return max(DP)
    
    def check(self, word1, word2):
        ##if word1 + 1 character = word2 return true else return false
        val = 0
        # print(word1, word2)
        for i in range(len(word1)):
            if word1[i]!=word2[i]:
                if val==0:
                    word2 = word2[:i] + word2[i+1:]
                    # print(i)
                    # print(word1, word2)
                    # if word1[i]!=word2[i]:
                    #     return False
                    return word1 == word2
                    
                if val==1:
                    return False
                continue
        return True

import collections

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def chain(w1, w2):                         # check if w1 is a chain to w2
            m, n = len(w1), len(w2)
            if abs(m-n) != 1: return False
            i, j, one = 0, 0, 1
            while i < m and j < n:
                if w1[i] == w2[j]: i, j = i+1, j+1
                elif one: one, i = 0, i+1
                else: return False    
            return True
                    
        if not words: return 0
        words.sort(key=lambda x: len(x))
        n = len(words)
        dp = [1] * n
        ans = 1
        for i in range(n):
            for j in range(i):                     # visited all previous words[j] to check if dp[i] can reach a longer chain
                if chain(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j] + 1)
            ans = max(ans, dp[i])
        return ans   

class Solution:
    def longer_by_one(self,word1,word2):
        if len(word2) - len(word1) != 1:
            return False
        j=0
        count = 0
        for i in range(len(word2)):
            if i < len(word2) and j<len(word1) and word2[i]!=word1[j]:
                count+=1
                if count>1:
                    return False
                continue
            j+=1 
            
        if i == len(word2) -1 and j== len(word1) and count ==1:
            return True
        elif i == len(word2) - 1 and j>= len(word1) and count == 0:
            return True
        else:
            return False
            
    
    def longestStrChain(self, words: List[str]) -> int:
        dp =  [1] * len(words)
        words = sorted(words, key = lambda x: len(x))
        for i in range(1,len(words)):
            for j in range(0,i):
                if self.longer_by_one(words[j],words[i]) == True:
                    dp[i] = max(dp[i],dp[j]+1)
        for i in range(len(dp)):
            print((words[i],dp[i]))
        return max(dp)


from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words=sorted(words,key=lambda x:len(x))
        
        # hashMap=defaultdict(list)
        # for i in range(len(words)-1,-1,-1):
        #     for j in range(i):
        #         if self.isPred(words[j],words[i]):
        #             hashMap[i].append(j)
                    
        # dp=[1 for _ in range(len(words))]
        # res=1
        # for k in range(1,len(words)):
        #     for index in hashMap[k]:
        #         dp[k]=max(dp[k],dp[index]+1)
        #     res=max(res,dp[k])
        
        dp=[1 for _ in range(len(words))]
        res=1
        
        for i in range(1,len(words)):  
            for j in range(i):
                if len(words[j])+1==len(words[i]) and self.isPred(words[j],words[i]):
                    dp[i]=max(dp[i],dp[j]+1)
            res=max(res,dp[i])   
            
        return res
            
 
    
    def isPred(self,word1,word2):
        if len(word1)+1!=len(word2):
            return False
        
        p1=0
        p2=0
        degree=0
        
        while p1<len(word1) and p2<len(word2):
            if word1[p1]==word2[p2]:
                p1+=1
                p2+=1
                continue
            else:
                degree+=1
                p2+=1
               
        return degree<2

                
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        result = 1

        for word in sorted(words, key=lambda x: -len(x)):
            dp[word] = 1

            for i in range(len(word)+1):
                for c in 'abcdefghijklmnopqrstuvwxyz':
                    next_word = word[:i] + c + word[i:]

                    if next_word in dp:
                        dp[word] = max(dp[word], dp[next_word] + 1)
                        result = max(result, dp[word])

        return result

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        by_length = collections.defaultdict(set)
        for word in words:
            by_length[len(word)].add(word)

        longest = 1
        seen = {*()} # set()
        mx = len(by_length)
        mn = min(by_length)
        
        # in descending order
        for length in sorted(by_length, reverse=True):
            if length == mn: return 1
            if length - mn < longest:
                break
            for word in by_length[length]:
                if length - mn < longest:
                    break
                if word in seen:
                    continue
                stk = [(word, length, 1)]
                while stk:
                    word, k, n = stk.pop()
                    seen.add(word)
                    if n > longest:
                        longest = n
                    for i in range(k):
                        pre = word[:i] + word[i+1:]
                        if pre not in seen and pre in by_length[k-1]:
                            stk.append((pre, k-1, n+1))
                if longest == mx:
                    return longest

        return longest   

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        longestchain = defaultdict(int)
        words.sort(key=len, reverse=True)
        for word in words:
            for i in range(len(word)):
                pred = word[:i] + word[i + 1:]
                longestchain[pred] = max(longestchain[pred], 1 + longestchain[word])
        return max(longestchain.values()) 


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # Note: order does not matter for this problem
        words = sorted(words, key=lambda word: len(word))
        # Once we've sorted words by length, we need an efficient way to
        # check if a longer word can be generated from a shorter word
        # We can simply do a char by char comparison which is O(W), where W 
        # is the maximum length of a word
        # You have to do this for all strings of length 1 less than current string's length
        # If you find a match, then increment the dp value
        n = len(words)
        if n == 1:
            return 1
        
        ans = 1
        # dp[i] is the length of the longest chain ending at i
        dp = [1]*n
        
        # Check if string at i can be generated from string at j
        def match(i, j):
            # This function assumes that len(words[i]) - len(words[j]) == 1
            idx_i = 0
            idx_j = 0
            skipped = False
            while idx_j < len(words[j]):
                if words[i][idx_i] != words[j][idx_j]:
                    if skipped:
                        return False
                    skipped = True
                    idx_i += 1
                else:
                    idx_i += 1
                    idx_j += 1
            return True
        
        for i in range(1, n):
            for j in range(i-1, -1, -1):
                if len(words[i]) - len(words[j]) > 1:
                    break
                elif len(words[i]) - len(words[j]) == 1:
                    if match(i, j):
                        dp[i] = max(dp[i], dp[j] + 1)
                        ans = max(ans, dp[i])
                else:
                    pass
            
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key=lambda w: len(w))
        dp = [None] * len(words)
        dp[0] = 1
        for i in range(1, len(words)):
            maxi = 1
            for j in list(range(0,i))[::-1]:
                # print(j)
                if(self.check(words[j], words[i]) and dp[j]+1 > maxi):
                    maxi = dp[j] +1
            dp[i] = maxi
        # print(dp)
        return max(dp)
    def check(self, chain, word):
        if(len(chain)+1 != len(word)):
            return False
        i=0
        j=0
        Flag = False
        while(i<len(chain) and j<len(word)):
            if(chain[i] == word[j]):
                i+=1
                j+=1
            elif(chain[i]!=word[j] and not Flag):
                j+=1
                Flag = True
            else:
                return False
        return True


def chain(w1, w2):                         # check if w1 is a chain to w2
    m, n = len(w1), len(w2)
    if abs(m-n) != 1: return False
    i, j, one = 0, 0, 1
    while i < m and j < n:
        if w1[i] == w2[j]: i, j = i+1, j+1
        elif one: one, i = 0, i+1
        else: return False    
    return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [1]*(len(words))
        dp[0] = 1
        words.sort(key=lambda x: len(x))
        for i in range(1, len(words)):
            for j in range(0,i):
                if chain(words[i], words[j]):
                    # print(dp[i],dp[j]+1)
                    dp[i] = max(dp[i], dp[j]+1)
        # print(dp)
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        # self.memo = defaultdict(bool)
        def isPred(p,q,words):
            # return if words[p] is a predecessor of words[q]
            # if (p,q) in self.memo:
                # return self.memo[(p,q)]
            w1 = words[p]
            w2 = words[q]
            n2 = len(w2)
            res = False
            for i in range(n2):
                if w1 == w2[0:i]+w2[i+1:]:
                    res = True
            return res
        
        n = len(words)
        
        # dpA = [1]*n# dp[i] is the longest length of word chain consisting of dp[i]
        # for i in range(n):
        #     cur = 1
        #     for j in range(i):
        #         if isPred(j,i,words):
        #            cur = max(cur,dpA[j]+1)
        #     dpA[i] = cur
        # print(dpA)
        
        dpR = [1]*n
        wR = sorted(words,key=len)
        # print(wR)
        for i in range(n):
            cur = 1
            for j in range(i):
                n1 = len(wR[j])
                n2 = len(wR[i])
                if n2-n1!=1:
                    continue
                if isPred(j,i, wR):
                    cur = max(cur,dpR[j]+1)
            dpR[i] = cur
        # print(dpR)
        
        
        
        return max(dpR)
            
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        words.sort(key = len)
        print(words)
        solutions = []
        
        numWords = len(words)
        
        for i in range(numWords):   
            self.getSequences(words[i+1:], len(words[i]) + 1, [words[i]], words[i], solutions)
        
        maxSize = 0
        
        for s in solutions:
            maxSize = max(maxSize, len(s))
        
        return maxSize
        
    def getSequences(self, words, targetLength, currSequence, lastWord, solutions):
        
        numWords = len(words)
        for i in range(numWords):
            if len(words[i]) == targetLength:
                if self.isPredecessor(lastWord, words[i]):
                    newCurrSequence = currSequence[:]
                    newCurrSequence.append(words[i])
                    self.getSequences(words[i+1:], targetLength + 1, newCurrSequence, words[i], solutions)
                    
            elif len(words[i]) > targetLength:
                break
                
        if len(solutions) > 0:
            if len(solutions[0]) < len(currSequence):
                solutions[0] = currSequence
        else:
            solutions.append(currSequence)
        return
        
    def isPredecessor(self, word1, word2):
        
        numLetters1 = len(word1)
        numLetters2 = len(word2)
        
        if numLetters2 - numLetters1 != 1:
            return False
        
        p1 = 0
        p2 = 0
        count = 0
        
        while p1 < numLetters1:
            if word1[p1] == word2[p2]:
                p1 += 1
                p2 += 1
            elif count == 1:
                return False
            else:
                count += 1
                p2 += 1
                
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def check(w1, w2):
            idx = 0
            
            for i in range(len(w2)):
                if w2[i]==w1[idx]:
                    idx += 1
                
                if idx==len(w1):
                    return True
            
            return False
        
        n = len(words)
        words.sort(key=lambda w: len(w))
        G = [[] for _ in range(n)]
        
        for i in range(n):
            for j in range(i+1, n):
                if len(words[j])-len(words[i])>1:
                    break
                
                if len(words[j])-len(words[i])==1 and check(words[i], words[j]):
                    G[i].append(j)
                
        def rec(v):
            if memo[v]!=-1:
                return memo[v]
            
            res = 1
            
            for nv in G[v]:
                res = max(res, rec(nv)+1)
            
            memo[v] = res
            return res
    
        memo = [-1]*n
        ans = 0
        
        for i in range(n):
            ans = max(ans, rec(i))
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        words.sort(key = len)
        print(words)
        solutions = []
        
        numWords = len(words)
        
        for i in range(numWords):   
            self.getSequences(words[i+1:], len(words[i]) + 1, [words[i]], words[i], solutions)
        
        maxSize = 0
        
        for s in solutions:
            maxSize = max(maxSize, len(s))
        
        return maxSize
        
    def getSequences(self, words, targetLength, currSequence, lastWord, solutions):
        
        numWords = len(words)
        for i in range(numWords):
            if len(words[i]) == targetLength:
                if self.isPredecessor(lastWord, words[i]):
                    newCurrSequence = currSequence[:]
                    newCurrSequence.append(words[i])
                    self.getSequences(words[i+1:], targetLength + 1, newCurrSequence, words[i], solutions)
                    
            elif len(words[i]) > targetLength:
                break
        solutions.append(currSequence)
        return
        
    def isPredecessor(self, word1, word2):
        
        numLetters1 = len(word1)
        numLetters2 = len(word2)
        
        if numLetters2 - numLetters1 != 1:
            return False
        
        p1 = 0
        p2 = 0
        count = 0
        
        while p1 < numLetters1:
            if word1[p1] == word2[p2]:
                p1 += 1
                p2 += 1
            elif count == 1:
                return False
            else:
                count += 1
                p2 += 1
                
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        longestchain = defaultdict(int)
        for word in sorted(words, key=len, reverse=True):
            for i in range(len(word)):
                pred = word[:i] + word[i + 1:]
                longestchain[pred] = max(longestchain[pred], 1 + longestchain[word])
        return max(longestchain.values()) 


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        words.sort(key = len)
        print(words)
        solutions = []
        
        numWords = len(words)
        
        for i in range(numWords):   
            self.getSequences(words[i+1:], len(words[i]) + 1, [words[i]], words[i], solutions)
        
            
        if len(solutions) > 0:
            return len(solutions[0])
        else:
            return 0
        
    def getSequences(self, words, targetLength, currSequence, lastWord, solutions):
        
        numWords = len(words)
        for i in range(numWords):
            if len(words[i]) == targetLength:
                if self.isPredecessor(lastWord, words[i]):
                    newCurrSequence = currSequence[:]
                    newCurrSequence.append(words[i])
                    self.getSequences(words[i+1:], targetLength + 1, newCurrSequence, words[i], solutions)
                    
            elif len(words[i]) > targetLength:
                break
                
        if len(solutions) > 0:
            if len(solutions[0]) < len(currSequence):
                solutions[0] = currSequence
        else:
            solutions.append(currSequence)
        return
        
    def isPredecessor(self, word1, word2):
        
        numLetters1 = len(word1)
        numLetters2 = len(word2)
        
        if numLetters2 - numLetters1 != 1:
            return False
        
        p1 = 0
        p2 = 0
        count = 0
        
        while p1 < numLetters1:
            if word1[p1] == word2[p2]:
                p1 += 1
                p2 += 1
            elif count == 1:
                return False
            else:
                count += 1
                p2 += 1
                
        return True

class Solution:
    def check(self, word1, word2) -> bool:
        if len(word1)+1 != len(word2):
            return False
        for i in range(len(word1)+1):
            if i < len(word1) and word1[i] == word2[i]:
                continue
            else:
                word2 = word2[:i] + word2[i+1:]
                return word1 == word2
            
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        # print(words)
        output = [0 for i in range(len(words))]
        for j in range(-1,-len(words)-1,-1):
            # print(f\"{j} and {output}\")
            temp_len = 1
            k = j+1
            while k <= -1:
                if temp_len < output[k]+1:
                    # print(f\"{words[j]} and {words[k]} and {output}\")
                    if self.check(words[j], words[k]):
                        temp_len = output[k]+1
                k += 1
            output[j] = temp_len
        return max(output)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        li = {word:1 for word in words} 
        words.sort(key=len) 
        ans = 1
        for word in words:
            for  i in range(len(word)):
                new  = word[:i] + word[i+1:]
                
                if new in words:
                    #print(f\"{new} is in {word}\")
                    #print(f\" {li[word]}  and for new is {li[new] + 1 }\")
                    if li[word] < li[new] + 1:
                       # print(f\" {li[word]}  and for new is {li[new]}\")
                        li[word] = li[new] + 1
                        if li[word] > ans:
                                ans = li[word]
        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        longestchain = defaultdict(int)
        for word in sorted(words, key=len):
            for i in range(len(word)):
                longestchain[word] = max(longestchain[word[:i] + word[i+1:]] + 1, longestchain[word])
        return max(longestchain.values()) 


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [0]*len(words)
        
        words.sort(key=lambda x: len(x))
        
        
        def predecessor(a, b):
            if len(a) == len(b):
                return False
            diff = 0
            i = 0
            j = 0
            while i <len(a) and j<len(b):
                if a[i]!=b[j]:
                    diff += 1
                    if diff>=2:
                        return False
                    i+=1
                else:
                    i+=1
                    j+=1
            return True
        for i, word in enumerate(words):
            dp[i] = 1
            for j in range(i-1, -1,-1):
                if len(words[i]) - len(words[j])>=2:
                    break
                if predecessor(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_seq(word1, word2):
            if len(word2)!=len(word1)+1:
                return False
            L=len(word2)
            for idx in range(L):
                if word1==word2[:idx]+word2[idx+1:]:
                    return True
            return False
        
        L=len(words)
        dp=[1 for w in words]
        words=sorted(words, key=lambda x:len(x))
        for idx in range(L):
            word2=words[idx]
            for j in range(idx):
                word1=words[j]
                if is_seq(word1, word2):
                    dp[idx]=max(dp[idx], dp[j]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dp = [1 for _ in words]
        for start in range(len(words)):
            for end in range(start-1, -1, -1):
                if len(words[end]) < len(words[start])-1:
                    break
                if predecessor(words[start], words[end]):
                    dp[start] = max(dp[start], 1 + dp[end])
        return max(dp)
    
def predecessor(word, candidate):
    if len(word) != len(candidate)+1:
        return False
    j = 0
    for i, c in enumerate(word):
        if i > j + 1 or j == len(candidate):
            break
        if c == candidate[j]:
            j += 1
    return j == len(candidate)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words: return 0
        def chain(w1, w2):
            m, n = len(w1), len(w2)
            if abs(m-n) != 1: return False
            i, j, one = 0, 0, 1
            while i < m and j < n:
                if w1[i] == w2[j]: i, j = i+1, j+1
                elif one: one, i = 0, i+1
                else: return False    
            return True
                    
        words.sort(key=lambda x: len(x))
        n = len(words)
        dp = [1] * n
        ans = 1
        for i in range(n):
            for j in range(i):
                if chain(words[i], words[j]):
                    dp[i] = max(dp[i], dp[j] + 1)
                if dp[i] > ans: break
            ans = max(ans, dp[i])
        return ans        

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        self.memo = defaultdict(bool)
        def isPred(p,q,words):
            # return if words[p] is a predecessor of words[q]
            if (p,q) in self.memo:
                return self.memo[(p,q)]
            w1 = words[p]
            w2 = words[q]
            n2 = len(w2)
            res = False
            for i in range(n2):
                if w1 == w2[0:i]+w2[i+1:]:
                    res = True
            self.memo[(p,q)] = res
            return res
        
        n = len(words)
        
        # dpA = [1]*n# dp[i] is the longest length of word chain consisting of dp[i]
        # for i in range(n):
        #     cur = 1
        #     for j in range(i):
        #         if isPred(j,i,words):
        #            cur = max(cur,dpA[j]+1)
        #     dpA[i] = cur
        # print(dpA)
        
        dpR = [1]*n
        wR = sorted(words,key=len)
        # print(wR)
        for i in range(n):
            cur = 1
            for j in range(i):
                n1 = len(wR[j])
                n2 = len(wR[i])
                if n2-n1!=1:
                    continue
                if isPred(j,i, wR):
                    cur = max(cur,dpR[j]+1)
            dpR[i] = cur
        # print(dpR)
        
        
        
        return max(dpR)
            
            


def predecessor(word, candidate):
    if len(word) != len(candidate)+1:
        return False
    j = 0
    for i, c in enumerate(word):
        if i > j + 1 or j == len(candidate):
            break
        if c == candidate[j]:
            j += 1
    return j == len(candidate)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dp = [1 for _ in words]
        for start in range(len(words)):
            for end in range(start-1, -1, -1):
                if len(words[end]) < len(words[start])-1:
                    break
                if predecessor(words[start], words[end]):
                    dp[start] = max(dp[start], 1 + dp[end])
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words=sorted(words,key=lambda x:len(x))
        def helper(w1,w2):
            if len(w2)-len(w1)!=1:return False
            i,j=0,0
            while i<len(w1) and j<len(w2):
                if w1[i]==w2[j]:
                    i+=1
                j+=1
            return i==len(w1)
        n=len(words)
        dp=[1 for i in range(n)]
        for i in range(n):
            for j in range(i):
                if helper(words[j],words[i]):
                    dp[i]=max(dp[i],dp[j]+1)
        return max(dp)
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        # words=sorted(words,key=lambda x:len(x))
        # def ccmp(a,b):
        #     if len(b)-len(a)!=1:
        #         return False
        #     i,j=0,0
        #     while i<len(a) and j <len(b):
        #         if a[i]==b[j]:
        #             i+=1
        #             j+=1
        #         else:
        #             j+=1
        #     return i==len(a)
        # n=len(words)
        # dp=[1 for i in range(n)]
        # for i in range(1,n):
        #     for j in range(i):
        #         if len(words[i])-len(words[j])==1:
        #             if ccmp(words[j],words[i]): 
        #                 dp[i]=max(dp[i],dp[j]+1)
        # ans=0
        # for i in range(n):
        #     ans=max(ans,dp[i])
        # return ans
                    
                
                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def pred(a, b):
            if len(a) == len(b) or len(a) - len(b) > 1:
                return False
            
            diff = 0
            i = 0
            j = 0
            while i < len(a):
                if a[i] == b[j]:
                    i+=1
                    j+=1
                else:
                    diff+=1
                    if diff > 1:
                        return False
                    j+=1
                    
            return True
        
        if not words:
            return 0
        
        dp = [0 for w in words]
        ret = 0
        
        words.sort(key=len)
        
        for i in range(len(words)):
            dp[i] = 1
            
            for j in range(i):
                if ((len(words[i]) - len(words[j]) <= 1) and pred(words[j], words[i])):
                    dp[i] = max(dp[i], dp[j]+1)
            ret = max(ret, dp[i])
        
        return ret

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        from collections import defaultdict, deque
        hash_map = defaultdict(list)
        visited = {}
        max_len = 0
        q = deque()
        for char in words:
            if len(char) >1:
                neighbors = [char[:i] + char[i+1:] for i in range(len(char))]
                for n in neighbors:
                    hash_map[n].append(char)
            hash_map[char].append(char)

        words.sort(key=len)
        for word in words:
            q.appendleft((word, 1))


        while q:
            char, count = q.pop()
            if char not in visited and char in hash_map:
                max_len = max(max_len, count)
                for n in hash_map[char]:
                    q.append((n, count + 1))

            visited[char] = 1

        return max_len
                    



class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(a, b):
            if len(a) != len(b) -1 or len(b) == 1:
                return False
            for i in range(len(b)):
                tmp = b[:i] + b[i+1:]
                if a == tmp:
                    return True
            return False
        words.sort(key=len)
        dp = [1] * (len(words))
        for i in range(len(dp)):
            for j in range(i-1, -1, -1):
                if isPredecessor(words[j], words[i]):
                    dp[i] = max(dp[j] +1, dp[i])
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # longest path in a specially constructed DAG
        dp = [0 for i in range(len(words))]
        ans = 0
        words = sorted(words, key=lambda x: len(x))
        for i in range(len(words)):
            for j in range(i):
                if self.prev(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)
                    ans = max(ans, dp[i])
        
        return ans + 1
    
    def prev(self, word_1, word_2):
        len_1, len_2 = len(word_1), len(word_2)
        if len_1 + 1 != len_2:
            return False
        
        i = j = 0
        while i < len_1 and j < len_2:
            if word_1[i] == word_2[j]:
                i += 1
            j += 1
        if i == len_1:
            return True
        else:
            return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:

        words.sort(key=len)
        print(words)
        sequence = collections.defaultdict(list)
        dp = [1]*len(words)
        
        for i in range(len(words)):
            for j in range(0, i):
                predecessor = self.checkPredecessor(words[j], words[i])
                if predecessor:
                    sequence[words[i]].append(words[j])
                    dp[i] = max(dp[j] + 1, dp[i])
                    
        print(sequence)
        print(list(zip(words, dp)))
        return(max(dp))
                
    def checkPredecessor(self, word1, word2):
        if len(word2)-len(word1) > 1:
            return False
        elif len(word1) == len(word2):
            return False
        
        i = j = 0
        diff = False
        while i < len(word1):
        
            if word1[i] == word2[j]:
                i += 1
                j += 1
            elif not diff:
                diff = True
                j += 1
            else:
                return False
        
        if i == len(word1) and j < len(word2) and diff:
            return False
        
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key=len)
        def onediff(word1, word2):
            if len(word1) + 1 != len(word2) : return False
            for i in range(len(word2)):
                if word2[:i] + word2[i+1:] == word1 : 
                    return True
            return False
        dp = [1] * len(words)
        for i in range(len(words)):
            for j in range(i):
                if onediff(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j]+1)
        return max(dp)


from collections import Counter

class Solution:
    def is_one_away(self, str_1, str_2):
        c1 = Counter(str_1)
        c2 = Counter(str_2)
        diff = 0
        for k, v in c2.items():
            diff += v - c1.get(k, 0)
            if diff > 1:
                return False
        
        for k in c1:
            if k not in c2:
                return False
        
        return True
    
    def longestStrChain(self, words: List[str]) -> int:
        sorted_words = sorted([''.join(sorted(w)) for w in words], key=lambda x: len(x))
        dp = [1] * len(words)
        
        for i in range(len(sorted_words)-1,-1,-1):
            for j in range(i-1, -1, -1):
                if len(sorted_words[i]) - len(sorted_words[j]) == 0:
                    continue
                
                if len(sorted_words[i]) - len(sorted_words[j]) > 1:
                    break
                    
                if self.is_one_away(sorted_words[j], sorted_words[i]):
                    dp[j] = max(1+dp[i], dp[j])
                    
                
            
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(x, y):
            if len(x) != len(y) - 1:
                return False
            
            i = 0
            j = 0
            while i < len(x) and j < len(y):
                if x[i] == y[j]:
                    i += 1
                    j += 1
                else:
                    j += 1
                    
            if i == len(x):
                return True
            return False
        
        words = sorted(words, key=lambda x: len(x))
        n = len(words)
        dp = [1] * n
        for i in range(1, n):
            for j in range(i):
                if is_predecessor(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)
        
        return max(dp)
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda w: len(w))
        
        word_chains = {w: 1 for w in words}
        
        # intentionally min length to max length
        for word in words:
            for i in range(len(word)):
                sub_word = word[:i] + word[i+1:]
                if sub_word in words:
                    word_chains[word] = max(word_chains[sub_word] + 1, word_chains[word])
        
        return max(word_chains.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        res = 1
        for word in sorted(words,key = len):
            for i in range(len(word)):
                # print(word[:i])
                if word[:i] + word[i+1:] in words:
                    dp[word] = dp.get(word[:i]+word[i+1:],1) + 1
                    res = max(res,dp[word])
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:

        if not words:
            return 0 

        words.sort(key = lambda x: len(x)) #O(nlogn)
        n = len(words)
        dp = [1 for i in range(n)]

        for i in range(1, n): # O(n^2)
            for j in range(i):
                if self.is_predecessor(words[j], words[i]): # O(k)
                    dp[i] = max(dp[i], dp[j] + 1)

        return max(dp)
    
    def is_predecessor(self, word1, word2):

        if len(word1) + 1 != len(word2):
            return False

        i, j = 0, 0
        while i < len(word1) and j < len(word2):
            if word1[i] == word2[j]:
                i += 1
                j += 1
            else:
                j += 1

        return i == len(word1)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        words = [(len(word), word) for word in words]
        words.sort()
        words = [word[1] for word in words]
        dp = [1 for i in range(n)]
        
        for i in range(n):
            for j in reversed(list(range(i))):
                if len(words[j]) < len(words[i]) - 1:
                    break
                
                if len(words[j]) == len(words[i]):
                    continue

                if self.check(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j] + 1)
    
        return max(dp)
    
    def check(self, s1, s2):
        i, j = 0, 0
        count = 0
        while i < len(s1) and j < len(s2):
            if s1[i] == s2[j]:
                i += 1
                j += 1
            else:
                j += 1
                count += 1
            
        return i == len(s1)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def pre(word2, word):
            if len(word2) + 1 == len(word):
                for i in range(len(word)):
                    if word[:i]+word[i+1:] == word2:
                        return True
            return False
   
        words = sorted(words, key=lambda x: -len(x))
        best = 0
        
        n = len(words)
        length = [0]*n
        
        for i in range(n):
            for j in range(i+1,n):
                if pre(words[j], words[i]):
                    # print('pre', word2, word)
                    length[j] = max(length[j], length[i] + 1)
                    best = max(best, length[j])    
                    
        # print(length)
        
        return best+1

def isPredecessor(src, dst):
    nsrc = len(src)
    ndst = len(dst)
    if nsrc+1 != ndst:
        return False
    
    i = 0
    while i < nsrc:
        if src[i] == dst[i]:
            i += 1
            continue
        else:
            return src[i:] == dst[i+1:]
    
    return True
        
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        mem_len = [-1 for _ in range(len(words))]
        
        def search(i):
            if mem_len[i] != -1:
                return mem_len[i]
            
            ans = 1
            for j in range(i+1, len(words)):    
                if isPredecessor(words[i], words[j]):
                    mem_len[j] = search(j)
                    ans = max(ans, mem_len[j]+1)
                    
            mem_len[i] = ans
            return ans
            
        for i in range(len(words)):
            search(i)

        return max(mem_len)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        l = [1 for i in range(len(words))]
        words.sort(key = lambda x:len(x))
        res = [[i] for i in words]
        # print(l,res)
        for i in range(1,len(words)):
            for j in range(i):
                if len(words[j])+1==len(words[i]):
                    x = {}
                    for a in words[j]:
                        if a in x:
                            x[a]+=1
                        else:
                            x[a]=1
                    ctr = 0
                    for a in words[i]:
                        if a not in x:
                            ctr+=1
                        elif x[a]==0:
                            ctr+=1
                        else:
                            x[a]-=1
                    if ctr==1 and l[i]<(l[j]+1):
                        res[i] = [i for i in res[j]]
                        res[i].append([words[i]])
                        l[i] = l[j]+1
        return max(l)
                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key=lambda w: len(w))
        total = len(words)
        count = [1 for _ in range(total)]
        for i in range(1, total):
            for j in range(i):
                if self.predecessor(words[j], words[i]) and count[i] < count[j] + 1:
                    count[i] = count[j] + 1
        # print(count)
        return max(count)
    
    def predecessor(self, word1, word2):
        if len(word2) != len(word1) + 1:
            return False
        for i in range(len(word2)):
            subword = word2[:i] + word2[i+1:]
            if subword == word1:
                return True
        return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x))
        N = len(words)
        self.longest = [None]*N
        max_len = 1
        for i in range(N):
            max_len = max(max_len, self.__findLongestAfter(words, i))
            
        return max_len
                                    
                
    def __isPredecessor(self, w1, w2):
        if len(w2) != len(w1)+1:
            return False
        
        for i in range(len(w2)):
            if (w2[:i]+w2[i+1:]) == w1:
                return True
        return False
    
    def __findLongestAfter(self, words: List[str], i:int) -> int:
        if self.longest[i] is not None:
            return self.longest[i]
        
        max_len = 1
        for j in range(i+1, len(words)):
            if self.__isPredecessor(words[i], words[j]):
                max_len = max(max_len, self.__findLongestAfter(words, j)+1)
                
        self.longest[i] = max_len
        return max_len

class Solution:

    def predecessor(self, w1, w2):
        
        i = 0

        diffs = 0

        while i<len(w1):
            if w1[i] != w2[i]:
                diffs = 1
                break

            i+=1

        if diffs==0:
            return True

        return w1==w2[:i]+w2[i+1:]

    def longestStrChain(self, words):

        words = sorted(words, key=lambda x: len(x))

        #print(words)

        words_by_len = {}

        for w in words:
            if len(w) in words_by_len:
                words_by_len[len(w)].append(w)
            else:
                words_by_len[len(w)] = [w]

        #print(words_by_len)

        max_lens = {}

        for w in words:
            max_lens[w] = 1

        for l in words_by_len:

            if l-1 in words_by_len:
            
                for w1 in words_by_len[l]:
                    for w2 in words_by_len[l-1]:
                        #print(w1, w2)

                        if self.predecessor(w2, w1):
                            max_lens[w1] = max(max_lens[w1], 1+max_lens[w2])

        max_len = 0

        for w in max_lens:
            max_len = max(max_len, max_lens[w])

        return max_len



class Solution:
    # def predecessor(self, long, short):
    #     # print(long, short)
    #     for i in range(len(long)):
    #         if long[0:i] + long[i+1:] == short:
    #             return True
    #     return False
    def predecessor(self, word, candidate):
        if len(word) != len(candidate)+1:
            return False
        j = 0
        for i, c in enumerate(word):
            if i > j + 1 or j == len(candidate):
                break
            if c == candidate[j]:
                j += 1
        return j == len(candidate)
    
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dp = [1 for _ in words]
        for i in range(len(words)):
            for j in range(i, -1, -1):
                if len(words[i]) - len(words[j]) > 1:
                    break
                if self.predecessor(words[i], words[j]):
                    # print(\"hi\")
                    dp[i] = max(dp[i], dp[j]+1)
        return max(dp)

class Solution:
    def longestStrChain(self, wordlist: List[str]) -> int:
        wl = set(wordlist)
        def dfs(w):
            # base
            if len(w) == 1:
                return 1
            if w in memo:
                return memo[w]
            # recursive
            l = len(w)
            depth = 0
            for i in range(l):
                next = w[0:i] + w[i+1:l] # substring
                if next in wordlist:
                    depth = max(depth, dfs(next))
            memo[w] = depth + 1
            return depth + 1

        memo = {}
        longest = 1
        for word in wordlist:
            longest = max(longest, dfs(word))
        return longest


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = [x for x in sorted(words, key=lambda w: len(w))]
        total = len(words)
        count = [1 for _ in range(total)]
        for i in range(1, total):
            for j in range(i):
                if self.predecessor(words[j], words[i]) and count[i] < count[j] + 1:
                    count[i] = count[j] + 1
        # print(count)
        return max(count)
    
    def predecessor(self, word1, word2):
        if len(word2) != len(word1) + 1:
            return False
        for i in range(len(word2)):
            subword = word2[:i] + word2[i+1:]
            if subword == word1:
                return True
        return False

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def helper(word_pre, word_cur):
            if len(word_pre) - len(word_cur) != -1: return False
            count, pre, cur = 0, 0, 0
            while pre <= len(word_pre) and cur < len(word_cur):
                if pre < len(word_pre) and word_cur[cur] == word_pre[pre]:
                    pre += 1
                else:
                    count += 1
                cur += 1
            return True if count == 1 else False

        dp = [1] * len(words)
        words = sorted(words, key=len)
        for i in range(len(words)):
            for j in range(i):
                if helper(words[j], words[i]) and dp[j] > dp[i] - 1:
                    dp[i] = dp[j] + 1
        return max(dp)



class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x))
        N = len(words)
        self.longest = [None]*N
        max_len = 1
        for i in range(N):
            max_len = max(max_len, self.__findLongestAfter(words, i))
            
        return max_len
                                    
                
    def __isPredecessor(self, w1, w2):
        if len(w2) != len(w1)+1:
            return False
        
        i=j=0
        while i < len(w1) and j < len(w2):
            if w1[i] == w2[j]:
                i+=1
            j+=1
            
        return (i == len(w1))
    
    def __findLongestAfter(self, words: List[str], i:int) -> int:
        if self.longest[i] is not None:
            return self.longest[i]
        
        self.longest[i] = 1
        len_i = len(words[i])
        for j in range(i+1, len(words)):
            if len(words[j]) - len_i > 1:
                break
                
            if self.__isPredecessor(words[i], words[j]):
                self.longest[i] = max(self.longest[i], self.__findLongestAfter(words, j)+1)
                
        return self.longest[i]

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        d = {w : 1 for w in words}
        words.sort(key = lambda x: len(x))
        def check(w1, w2):
            if len(w1) <= len(w2):
                return False
            if len(w1) - len(w2) == 1:
                '''for i in range(len(w1)):
                    temp = w1[:i] + w1[i + 1:]
                    if temp == w2:
                        return True'''
                i = 0
                j = 0
                count = 0
                while i < len(w1) and j < len(w2):
                    if w1[i] == w2[j]:
                        i += 1
                        j += 1
                    else:
                        i += 1
                        count += 1
                        
                if count > 1:
                    return False
                else:
                    return True
                    
            return False
                    
            
            
        
        
        dp = [0 for _ in range(len(words))]
        
        dp[0] = 1
        res = 1
        for i in range(1, len(words)):
            curr_max = 0
            for j in range(i):
                if check(words[i], words[j]): 
                    curr_max = max(curr_max, dp[j])
                    
            dp[i] = curr_max + 1
            res = max(res, dp[i])

            
        return res
            


class Solution:
    def isPredecessor(self, word1, word2):
        if len(word1)+1 == len(word2):
            i = 0
            j = 0
            count = 0
            while i < len(word1) and j < len(word2):
                if word1[i] != word2[j]:
                    count += 1
                else:
                    i += 1
                j += 1
            if count > 1:
                return False
            elif count == 0:
                if len(word2) == j + 1:
                    return True
            else:
                return True
        else:
            return False
        
    def memoization(self, i, words, memo):
        if i >= len(words):
            return 0
        if memo[i] is not None:
            return memo[i]
        length = 0
        for j in range(i+1, len(words)):
            # if i==14:
            #     print(words[i], words[j], self.isPredecessor(words[i], words[j]))
            if self.isPredecessor(words[i], words[j]):
                length = max(length, self.memoization(j, words, memo))
        memo[i] = 1 + length
        return memo[i]

    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key=len)
        memo = [None]*len(words)
        length = 0
        for i in range(len(words)):
            length = max(length, self.memoization(i, words, memo))
        return length

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        ## return a list of lists
        #words.sort(cmp=lambda x,y:len(x) - len(y))
        words = sorted(words, key = lambda x: len(x))
        #print(words)
        dp = [1]*len(words)
        res = 1
        for i in range(len(words)):
            for j in range(0,i):
                ## compare word[j] -> word[i]
                #print(self.isPrecessor(words[j],words[i]))
                if len(words[i])>len(words[j]) and self.isPrecessor(words[j],words[i]):
                    dp[i] = max(dp[i],dp[j]+1)
                    res = max(res,dp[i])
        #print(dp)            
        return res
    
    def isPrecessor(self,word1,word2):
        #print('------------')
        #print(word1)
        #print(word2)
        ind1 = 0
        ind2 = 0
        flag = 0
        
        while ind1<len(word1) and ind2<len(word2):
            if word1[ind1]==word2[ind2]:
                ind1 += 1
                ind2 += 1
            else: 
                if flag==1:
                    return False
                ind2 += 1
                flag = 1
                
        return (ind1==len(word1) and ind2==len(word2)) or (ind1==len(word1) and flag==0 and (ind2+1)==len(word2))


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        sort = [[] for _ in range(max(map(len, words)))]
        for el in words:
            sort[len(el) - 1].append(el)
        d = collections.defaultdict(int)
        for row in sort:
            for w in row:
                for i in range(len(w)):
                    l = w[:i] + w[i + 1:]
                    t = d[l] + 1
                    d[w] = max(d[w], t)
        return max(d.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        N = len(words)
        self.longest = [None]*N
        max_len = 1
        for i in range(N):
            max_len = max(max_len, self.__findLongestAfter(words, i))
            
        return max_len
                                    
                
    def __isPredecessor(self, w1, w2):
        if len(w2) != len(w1)+1:
            return False
        
        i=j=0
        while i < len(w1) and j < len(w2):
            if w1[i] == w2[j]:
                i+=1
            j+=1
            
        return (i == len(w1))
    
    def __findLongestAfter(self, words: List[str], i:int) -> int:
        if self.longest[i] is not None:
            return self.longest[i]
        
        self.longest[i] = 1
        len_i = len(words[i])
        for j in range(i+1, len(words)):
            if len(words[j]) > len_i+1:
                break
                
            if self.__isPredecessor(words[i], words[j]):
                self.longest[i] = max(self.longest[i], self.__findLongestAfter(words, j)+1)
                
        return self.longest[i]

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        '''
        [0, 2, 4, 5, 3, 1, 56, 0] -> input
        return the length of the longest subsequence of ascending numbers
        [1, 2, 0, 0, 0, 0, 0, 0]
        return max(dp)
        '''
        
        words.sort(key = lambda x : len(x))
        dp  = [1] * len(words)
        for i in range(len(words)):
            curr = words[i]
            for j in range(i):
                prev = words[j]
                if self.isPredecessor(curr, prev):
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
    
    def isPredecessor(self, curr, prev):
        if len(curr) - len(prev) != 1: return False
        i = j = 0
        diff = False
        while i < len(curr) and j < len(prev):
            if curr[i] == prev[j]:
                i += 1
                j += 1
            else:
                if diff: return False
                diff = True
                i += 1
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        #backtrack/ bfs/dfs?
        # back -> longest to smallest?
        
        words.sort(key = lambda x:len(x))
        print(words)
        n_s=len(words[0])
        
        dic = collections.defaultdict(lambda:0)
        longest_chain = 1
        
        def search(word):
            if len(word)==n_s:
                dic[word] = 1
                return 1
            N = 1
            for i in range(0,len(word)):
                sub = word[0:i] + word[i+1:]
                
                if sub in words:
                    n = search(sub)
                    
                    N =max(N,n+1)
            dic[word]=N
            nonlocal longest_chain
            longest_chain = max(longest_chain,N)
            #print(N,longest_chain,word)
            return N
        
        for i in range(len(words)-1,-1,-1):
            if dic[words[i]] in dic:
                continue
            word = words[i]
            re = search(word)
            longest_chain = max (longest_chain,re)
        return longest_chain


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        #string : length of longest chain ends with string
        wset= set(words)
        
        #return the longest chain begin with word
        memo = {}
        def dp(word):
            if word in memo:
                return memo[word]
            #contruct all the possible successor from word
            ans = 1
            for i in range(len(word) + 1):
                for j in range(26):
                    newword = word[:i] + chr(ord('a') + j) + word[i:]
                    if newword in wset:
                        ans = max(ans, 1 + dp(newword))
            memo[word] = ans
            return ans
        
        ret = 0
        for word in words:
            if not word in memo:
                ret = max(ret, dp(word))
        
        return ret

class Solution:
    def isSubString(self, s1, s2):
        diff = 0;
        l = len(s1);
        ll = len(s2);
        if(ll - l !=1):
            return False
        i = 0;
        j = 0;
        diff =0;
        while(i < l and diff < 2):
            if(s1[i] == s2[j]):
                i += 1;
                j += 1;
            else:
                j += 1;
                diff += 1;
        if(diff > 1):
            return False
        return True;
                
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words);
        words = sorted(words, key = len)
        dp = [1]*(n);
        for i in range(n):
            j = i-1
            while(j >= 0 and (len(words[i]) - len(words[j]) <= 1)):
                if(self.isSubString(words[j], words[i])):
                    dp[i] = max(dp[i], dp[j] + 1);
                j -= 1;
        return max(dp)
                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x))
        N = len(words)
        self.longest = [None]*N
        max_len = 1
        for i in range(N):
            max_len = max(max_len, self.__findLongestAfter(words, i))
            
        return max_len
                                    
                
    def __isPredecessor(self, w1, w2):
        if len(w2) != len(w1)+1:
            return False
        
        for i in range(len(w2)):
            if (w2[:i]+w2[i+1:]) == w1:
                return True
        return False
    
    def __findLongestAfter(self, words: List[str], i:int) -> int:
        if self.longest[i] is not None:
            return self.longest[i]
        
        max_len = 1
        len_i = len(words[i])
        for j in range(i+1, len(words)):
            if len(words[j]) - len_i > 1:
                break
                
            if self.__isPredecessor(words[i], words[j]):
                max_len = max(max_len, self.__findLongestAfter(words, j)+1)
                
        self.longest[i] = max_len
        return max_len

from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words=sorted(words,key=lambda x:len(x))
        
        # hashMap=defaultdict(list)
        # for i in range(len(words)-1,-1,-1):
        #     for j in range(i):
        #         if self.isPred(words[j],words[i]):
        #             hashMap[i].append(j)
                    
        # dp=[1 for _ in range(len(words))]
        # res=1
        # for k in range(1,len(words)):
        #     for index in hashMap[k]:
        #         dp[k]=max(dp[k],dp[index]+1)
        #     res=max(res,dp[k])
        
        dp=[1 for _ in range(len(words))]
        res=1
        
        for i in range(1,len(words)):  
            for j in range(i):
                if self.isPred(words[j],words[i]):
                    dp[i]=max(dp[i],dp[j]+1)
            res=max(res,dp[i])   
            
        return res
            
 
    
    def isPred(self,word1,word2):
        if len(word1)+1!=len(word2):
            return False
        
        p1=0
        p2=0
        degree=0
        
        while p1<len(word1) and p2<len(word2):
            if word1[p1]==word2[p2]:
                p1+=1
                p2+=1
                continue
            else:
                degree+=1
                p2+=1
               
        return degree<2

                
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        di = defaultdict(list)
        for w in words:
            di[len(w)].append(w)
        L, U = min(di.keys()), max(di.keys())
        dp = [1] * len(di[L])
        ans = 1
        
        def ispred(w1, w2):
            skip = False
            for i in range(len(w1)):
                while w1[i] != w2[i + skip]:
                    if skip:
                        return False
                    skip = True
            return True
        
        for l in range(L+1, U+1):
            old = dp
            dp = [1] * len(di[l])
            for j, w2 in enumerate(di[l]):
                tmp = [old[i] for i, w1 in enumerate(di[l-1]) if ispred(w1, w2)]
                if tmp: dp[j] = max(tmp) + 1
            ans = max(ans, max(dp))
            
        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n= len(words)
        def extraChar(s1, s2):
            l1, l2 = sorted(s1), sorted(s2)
            
            diff =0
            i,j = 0,0
            if len(s1) == len(s2) + 1: 
                while i<len(s1) and j<len(s2):
                    if l1[i] != l2[j]: 
                        i+=1
                        diff+=1
                     
                    else:
                        i+=1
                        j+=1
                    
                if i==len(s1) and j== len(s2): return diff==1
                elif i==len(s1)-1: return True
                
            return False
                    
        
        mem = {}
        words.sort(key = lambda x: len(x))
        
        
        def dp(i):
            if i>=n: return 0
            if i in mem: return mem[i]
            mem[i] = 1
            for j in range(i+1, n):
                
                if len(words[j]) == len(words[i])+1 and extraChar(words[j], words[i]):
                    #print('words[j]: ', words[j], ' words[j]: ', words[i])
                    mem[i] =  max(mem[i], dp(j)+1)
                    
                elif len(words[j]) > len(words[i])+1: break
        
            return mem[i]
        
        
        return max([dp(i) for i in range(n)])

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        mark = {}
        queue = []
        for index, word in enumerate(words):
            mark[word] = collections.Counter(word)
            queue.append([[word],index, len(word)])
        ans = 0
        while queue:
            s, index, l = queue.pop(0)
            flag = True
            for i in range(index+1, len(words)):
                if len(words[i]) > l+1:
                    break
                if len(words[i]) == l+1:
                    add = True
                    sum1 = 0
                    sum2 = 0
                    for key in list(mark[s[-1]].keys()):
                        if key not in mark[words[i]]:
                            add = False
                            break
                        sum1 += mark[s[-1]][key]
                        sum2 += mark[words[i]][key]
                    if add and (sum1 == sum2 or sum2-sum1 == 1):
                        queue.append([s+[words[i]], i, l+1])
                        flag = False
            if flag:
                ans = max(ans, len(s))       
        return ans
                        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        '''
        [0, 2, 4, 5, 3, 1, 56, 0] -> input
        return the length of the longest subsequence of ascending numbers
        [1, 2, 0, 0, 0, 0, 0, 0]
        return max(dp)
        '''
        
        words.sort(key = lambda x : len(x))
        dp  = [1] * len(words)
        ans = 0
        for i in range(len(words)):
            curr = words[i]
            for j in range(i):
                prev = words[j]
                if self.isPredecessor(curr, prev):
                    dp[i] = max(dp[i], dp[j] + 1)
            ans = max(ans, dp[i])
        return ans
    
    def isPredecessor(self, curr, prev):
        if len(curr) - len(prev) != 1: return False
        i = j = 0
        diff = False
        while i < len(curr) and j < len(prev):
            if curr[i] == prev[j]:
                i += 1
                j += 1
            else:
                if diff: return False
                diff = True
                i += 1
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words: return 0
        words.sort(key=lambda x: len(x))
        dp = [1] * len(words)
        for i in range(len(words)):
            curr = words[i]
            for j in range(i):
                prev = words[j]
                if (self.checkPredecessor(curr, prev)):
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
                    
                    
    def checkPredecessor(self, curr, prev):
        if len(curr) - len(prev) != 1: return False
        i = j = 0
        diff = False
        while i < len(curr) and j < len(prev):
            if curr[i] == prev[j]:
                i += 1
                j += 1
            else:
                if diff: return False
                diff = True
                i += 1
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        hashmap = collections.defaultdict(list)
        for index, word in enumerate(words):
            hashmap[len(word)].append(index)
        n = len(words)
        graph = [[False for _ in range(n)] for _ in range(n)]
        
        def predecessor(a, b):
            mistake = 0
            for i in range(len(a)):
                if a[i] != b[i + mistake]:
                    mistake += 1
                    if mistake > 1 or a[i] != b[i + mistake]:
                        return False
            return True
        
        for index, word in enumerate(words):
            for candidate in hashmap[len(word)+1]:
                if predecessor(word, words[candidate]):
                    graph[index][candidate] = True
        dp = [-1 for _ in range(n)]
        
        def dfs(i):
            if dp[i] != -1:
                return dp[i]
            answer = 0
            for j in range(n):
                if graph[i][j]:
                    answer = max(answer, dfs(j))
            dp[i] = answer + 1
            #print(f'i = {i}, dp[i]={dp[i]}')
            return answer + 1
        
        answer = 0
        for i in range(n):
            answer = max(answer, dfs(i))
        return answer

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_edit(a, b):
            if abs(len(a) - len(b)) > 1:
                return False
            if len(a) == len(b):
                return False

            for i in range(min(len(a), len(b))):
                if a[i] != b[i]:
                    return a == b[:i] + b[i + 1:] or b == a[:i] + a[i + 1:]
            return True

        words.sort(key=lambda x: len(x))
        n = len(words)
        memo = [1 for _ in range(n)]
        ans = 1
        for i in range(n - 1, -1, -1):
            max_ = 0
            for j in range(i + 1, n):
                if is_edit(words[i], words[j]):
                    max_ = max(max_, memo[j])
            memo[i] = 1 + max_
            ans = max(memo[i], ans)
        return ans


from functools import lru_cache
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        #string : length of longest chain ends with string
        wset= set(words)
        
        #return the longest chain begin with word
        memo = {}
        def dp(word):
            if word in memo:
                return memo[word]
            #contruct all the possible successor from word
            ans = 1
            for i in range(len(word) + 1):
                for j in range(26):
                    newword = word[:i] + chr(ord('a') + j) + word[i:]
                    if newword in wset:
                        ans = max(ans, 1 + dp(newword))
            memo[word] = ans
            return ans
        
        ret = 0
        for word in words:
            if not word in memo:
                ret = max(ret, dp(word))
        
        return ret

from collections import Counter

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = [1]
        res = 0
        words = sorted(words, key=lambda x:len(x))
        for i in range(1,len(words)):
            j = i-1
            val = 1
            while(j >= 0):
                if len(words[j]) != len(words[i]) - 1:
                    j -= 1
                    continue
                counter_j, counter_i = Counter(words[j]), Counter(words[i])
                used = False
                
                for key, value_j in counter_j.items():
                    value_i = counter_i.get(key)
                    if not value_i or value_i > value_j+1 or value_i < value_j:
                        break
                    
                    if value_i == value_j+1 and used:
                        break
                    
                    if value_i == value_j+1 and not used:
                        used = True    
                else:
                    val = max(val, dp[j]+1)
                j-=1
            dp.append(val)
            res = max(val, res)
        
        print(dp)
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
                # contruct a graph linking predecessor
        def isPred(word1, word2):
            # len(word2) > len(word1)
            for i in range(len(word2)):
                if word2[0:i] + word2[i+1:] == word1:
                    return True
            return False
        graph = {}
        for i in range(len(words) - 1):
            for j in range(i + 1, len(words)):
                w1, w2 = words[i], words[j]
                if len(w2) - len(w1) == 1 and isPred(w1, w2):
                    if w1 in graph:
                        graph[w1].append(w2)
                    else:
                        graph[w1] = [w2]
                elif len(w2) - len(w1) == -1 and isPred(w2, w1):
                    if w2 in graph:
                        graph[w2].append(w1)
                    else:
                        graph[w2] = [w1]
        self.res = 0
        def dfs(word, lev):
            # no need to mark visited here as len goes up
            self.res = max(self.res, lev)
            if word not in graph:
                return
            for i in graph[word]:
                dfs(i, lev+1)
        for i_word in words:
            dfs(i_word, 1)
        return self.res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        st = set(words)

        def ls(word):
            res = 1
            for j in range(len(word)):
                substr = word[:j] + word[(j+1):]
                if substr not in st:
                    continue
                res = 1 + ls(substr)
            return res
           
        res = 1
        for word in words:
            res = max(res, ls(word))
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        N = len(words)
        dp = [1]*N
        words.sort(key=len)
        print(words)
        if N == 1:
            return 1
        def check(word1, word2):
            if len(word2) - len(word1) != 1:
                return False
            n = len(word1)
            for i in range(n):
                # print(word2[:i] + word2[i+1:])
                if word2[:i] + word2[i+1:] == word1:
                    return True
                if word2[:-1] == word1:
                    return True
            return False
                
        for i in range(N):
            for j in range(i):
                if check(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j]+1)
                    
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def f(x, y):
            if len(x) != len(y) - 1:
                return False
            p = len(x)
            for i in range(len(x)):
                if x[i] != y[i]:
                    p = i
                    break
            
            for i in range(p, len(x)):
                if x[i] != y[i + 1]:
                    return False
            return True

        words.sort(key=lambda x:len(x))
        
        a = []
        for _ in range(len(words)):
            a.append([False] * len(words))
            
        
        for i in range(len(words)):
            for j in range(i + 1, len(words)):
                a[i][j] = f(words[i], words[j])
                
        # print(a)
                
        dp = [1] * len(words)
        for i in range(1, len(words)):
            for j in range(i):
                if a[j][i]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        word_dict= {}
        words= [(len(w), w) for w in words]
        words = sorted(words)

        for _,w in words:
            word_dict[w] = 1
            
        state_t = set() #word and chainlen
        
        max_chain = 1
        for _,word in words:

            chain_len = word_dict[word]
            max_chain = max(max_chain, chain_len)
            for i in range(len(word)+1):
                for c in range(97,123):
                    
                    new_word = word[:i] + chr(c)  + word[i:]
                    
                    

                    if word_dict.get(new_word, False) and new_word not in state_t:
                        
                        state_t.add(new_word)
                        word_dict[new_word] = chain_len+1
                        
        return max_chain
        
        
        
        
        
            
        
            
            


from collections import defaultdict

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        length = defaultdict(list)
        
        for s in words:
            length[len(s)].append(s)
        
        record = sorted(list(length.items()), reverse=True)
        result = defaultdict(int)
        max_length = 1
        for item in record:
            for word in item[1]:
                if result[word] == 0:
                    result[word] += 1
                for i in range(len(word)):
                    new_string = word[0:i]+word[i+1:] 
                    if new_string in words:
                        result[new_string] = max(result[new_string], result[word]+1)
                        max_length = max(max_length, result[new_string])
        
        return max_length
                        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dic = defaultdict(list)
        words.sort(key = len, reverse = True)
        for w in words:
            for i in range(len(w)):
                if w[:i] + w[i+1:] in words:
                    dic[w].append(w[:i] + w[i+1:])
        def bt(x):
            return [[x]] if x not in dic else [[x] + rest for y in dic[x] for rest in bt(y)]
        ct = 1
        for word in words:
            ct = max(ct, len(max(bt(word), key = len)))
        return ct
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        dp = [1 for _ in words]
        largest = 1
        words.sort(key=lambda s:len(s))
        print(words)
        def predecessor(big_word, small_word):
            b = 0
            s = 0
            mismatch = False
            if len(small_word) + 1 != len(big_word):
                return False
            while b < len(big_word) and s < len(small_word):
                if big_word[b] != small_word[s] and not mismatch:
                    mismatch = True
                    b += 1
                    continue
                elif big_word[b] == small_word[s]:
                    s += 1
                    b += 1
                    continue
                else:
                    return False
            return True

        left = 0
        right = 0
        while right < len(words):
            for k in range(left, right):
                if len(words[k]) == words[right]:
                    left = right
                    break
                if predecessor(words[right], words[k]):
                    dp[right] = max(dp[right], dp[k] + 1)
                    largest = max(dp[right], largest)
            right += 1
        return largest

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        if not words:return 0
        
        words.sort(key=lambda x:len(x))
        
        counter=[collections.Counter(word) for word in words]
        
        dp=[1]*len(words)
        maxlen=0
        for i in range(len(dp)):
            for j in range(i):
                if len(words[i])-len(words[j])==1 and len((counter[i]-counter[j]))==1:
                    dp[i]=max(dp[i],1+dp[j])
                    
            maxlen=max(maxlen,dp[i])
            
            
        return maxlen
                


class Solution:
    def __init__(self):
        self.cache = collections.defaultdict(int)
    
    def longestEndWith(self, word, words):
        if word in self.cache:
            return self.cache[word]
        if len(word) == 1:
            self.cache[word] = 1
            return 1
        else:
            for i in range(len(word)):
                s = word[:i] + word[i+1:]
                if s in words:
                    self.cache[word] = max(self.cache[word], self.longestEndWith(s, words))
            self.cache[word] += 1
            return self.cache[word]
        
    def longestStrChain(self, words: List[str]) -> int:
        ans = 0
        
        for word in words:
            ans = max(ans, self.longestEndWith(word, words))
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        from collections import defaultdict, deque
        hash_map = defaultdict(list)

        max_len = 0
        q = deque()
        for char in words:
            if len(char) >1:
                neighbors = [char[:i] + char[i+1:] for i in range(len(char))]
                for n in neighbors:
                    hash_map[n].append(char)
            hash_map[char].append(char)

        for c in words:
            visited = {}
            q.append((c, 1))
            while q:
                char, count = q.pop()
                if char not in visited and char in hash_map:
                    max_len = max(max_len, count)
                    for n in hash_map[char]:
                        q.append((n, count + 1))

                visited[char] = 1
        return max_len
                    



class Solution:
    def __init__(self):
        self.cache = collections.defaultdict(int)
    
    def longestEndWith(self, word, words):
        if word in self.cache:
            return self.cache[word]
        if len(word) == 1:
            self.cache[word] = 1
            return 1
        else:
            for i in range(len(word)):
                s = word[:i] + word[i+1:]
                if s in words:
                    self.cache[word] = max(self.cache[word], self.longestEndWith(s, words))
            self.cache[word] += 1
            return self.cache[word]
        
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x)) 
        ans = 0
        
        for word in words[::-1]:
            if word not in self.cache:
                ans = max(ans, self.longestEndWith(word, words))
        
        return ans

class Solution:
    def __init__(self):
        self.cache = collections.defaultdict(int)
    
    def longestEndWith(self, word, words):
        if word in self.cache:
            return self.cache[word]
        if len(word) == 1:
            self.cache[word] = 1
            return 1
        else:
            for i in range(len(word)):
                s = word[:i] + word[i+1:]
                if s in words:
                    self.cache[word] = max(self.cache[word], self.longestEndWith(s, words))
            self.cache[word] += 1
            return self.cache[word]
        
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda x: len(x)) 
        ans = 0
        
        for word in words[::-1]:
            if word not in self.cache:
                ans = max(ans, self.longestEndWith(word, words))
                if ans == len(words):
                    break
        
        return ans

from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        ends_at = defaultdict(int)
        words = sorted(words, key = lambda x: len(x))
        ans = 0
        for w in words:
            ends_at[w] = 1
            for i, ch in enumerate(w):
                prev = w[:i] + w[i+1:]
                if prev in words:
                    ends_at[w] = max(ends_at[w], ends_at[prev] + 1)
            ans = max(ans, ends_at[w])
        return ans


class Solution:
    def __init__(self):
        self.cache = collections.defaultdict(int)
    
    def longestEndWith(self, word, words):
        if word in self.cache:
            return self.cache[word]
        if len(word) == 1:
            self.cache[word] = 1
            return 1
        else:
            for i in range(len(word)):
                s = word[:i] + word[i+1:]
                if s in words:
                    self.cache[word] = max(self.cache[word], self.longestEndWith(s, words))
            self.cache[word] += 1
            return self.cache[word]
        
    def longestStrChain(self, words: List[str]) -> int:
        ans = 0
        
        for word in words[::-1]:
            if word not in self.cache:
                ans = max(ans, self.longestEndWith(word, words))
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        '''
        DFS with memo
        '''
        # words = set(words)
        dp = {}
        def chainEndingAt(word):
            if word not in words:
                return 0
            if word not in dp:
                chain_len = 1
                for i in range(len(word)):
                    pred = word[:i] + word[i+1:]
                    chain_len = max(chain_len, chainEndingAt(pred) + 1)
                dp[word] = chain_len
            return dp[word]
        
        ans = 0
        for word in words:
            ans = max(ans, chainEndingAt(word))
        return ans
        
        '''
        DP
        dp function:
        chain[word2] = max(chain[word1]+1 for all word1 if word1 is predecessor of word2)
        '''
        # dp = {}
        # for w in sorted(words, key=len):
        #     dp[w] = 1
        #     for i in range(len(w)):
        #         dp[w] = max(dp[w], dp.get(w[:i] + w[i+1:],0) + 1)
        # return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = sorted(words, key = len)
        n = len(words)
        def match(l, r):
            a = words[l]
            b = words[r]
            if (len(a) + 1 != len(b)):
                return False
            i,j = 0,0
            while i < len(a) and j < len(b):
                if (a[i] == b[j]):
                    i += 1
                j += 1
            return i == len(a)
        
        dp = [0 for _ in range(n)]
        for i in range(n-2, -1, -1):
            for j in range(i+1, n):
                if (len(words[j]) > len(words[i])+1):
                    break
                if match(i, j):
                    dp[i] = max(dp[i], 1 + dp[j])
        return 1+max(dp)

class Solution:
    def __init__(self):
        self.cache = collections.defaultdict(int)
    
    def longestEndWith(self, word, words):
        if word in self.cache:
            return self.cache[word]
        if len(word) == 1:
            self.cache[word] = 1
            return 1
        else:
            for i in range(len(word)):
                s = word[:i] + word[i+1:]
                if s in words:
                    self.cache[word] = max(self.cache[word], self.longestEndWith(s, words))
            self.cache[word] += 1
            return self.cache[word]
        
    def longestStrChain(self, words: List[str]) -> int:
        ans = 0
        
        for word in words:
            if word not in self.cache:
                ans = max(ans, self.longestEndWith(word, words))
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        by_length = collections.defaultdict(set)
        for word in words:
            by_length[len(word)].add(word)

        longest = 1
        seen = {*()} # set()
        mx = len(by_length)

        # in descending order
        for length in sorted(by_length, reverse=True):
            for word in by_length[length]:
                if longest == mx:
                    break
                if word in seen:
                    continue
                stk = [(word, length, 1)]
                while stk:
                    word, k, n = stk.pop()
                    seen.add(word)
                    if n > longest:
                        longest = n
                    for i in range(k):
                        pre = word[:i] + word[i+1:]
                        if pre not in seen and pre in by_length[k-1]:
                            stk.append((pre, k-1, n+1))

        return longest   

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        dictByLen = collections.defaultdict(list)
        wordCounter = {}
        
        for i,w in enumerate(words):
            dictByLen[len(w)].append(w)
            wordCounter[w] = collections.Counter(w)
            
        def isOneDiff(s1, s2):
            diff = 0
            j = 0
            for c in s2:
                if c != s1[j]:
                    if diff == 1:
                        return False
                    diff += 1
                else:
                    j += 1
                    if j == len(s1):
                        return True
            return True
                    
        sortedLens = sorted(dictByLen.keys())  
        longestLen = 1
        
        
        prevLen = -1
        
        for l in sortedLens:
            if prevLen == -1 or l != (prevLen + 1):
                prevArr = [(s,1) for s in dictByLen[l]]
            else:
                newArr = []
                
                for s2 in dictByLen[l]:
                    diff = 1
                    for s1, lsf in prevArr:
                        if isOneDiff(s1, s2):
                            diff = max(diff, lsf + 1)
                            
                    newArr.append((s2, diff))
                    longestLen = max(diff, longestLen)
                prevArr = newArr
            prevLen = l
        
        return longestLen
                
            
        

                
                                
                        
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = lambda w: len(w))
        graph = {}
        indegree = {}
        for w in words:
            graph[w] = []
            indegree[w] = 0
        
        def is_pre(wd_sml, wd_lrg):
            for idx in range(len(wd_lrg)):
                if wd_lrg[:idx] + wd_lrg[idx+1:] == wd_sml:
                    return True
            return False
        
        for idx in range(1, len(words)):
            for idx2 in range(idx):
                if len(words[idx]) != len(words[idx2])+1:
                    continue
                if is_pre(words[idx2], words[idx]):
                    graph[words[idx2]].append(words[idx])
                    indegree[words[idx]] += 1
        
        memo = {}
        from collections import deque
        def dfs(wd):
            if wd in memo:
                return memo[wd]
            res = 1
            for nei in graph[wd]:
                res = max(res, 1+dfs(nei))
            memo[wd] = res
            return memo[wd]
            
        
        st_arr = [w for w,indeg in list(indegree.items()) if not indeg]
        
        max_len = 0
        
        for elt in st_arr:
            res_len = dfs(elt)
            max_len = max(res_len, max_len)
        
        return max_len
                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def edit_dist(w1, w2):
            n = len(w1)
            m = len(w2)
            if n + 1 != m:
                return False
            mismatch = 0
            i = 0
            j = 0
            while i < n and j < m:
                if w1[i] != w2[j]:
                    mismatch += 1
                    j += 1
                else:
                    i += 1
                    j += 1
            return mismatch <= 1
        
        def recurse(word, length):
            nonlocal max_l
            max_l = max(max_l, length)
            n = len(word) + 1
            if n in dic:
                for next_word in dic[n]:
                    if edit_dist(word, next_word):
                        recurse(next_word, length+1)
                        
        
        dic = collections.defaultdict(list)
        
        for word in words:
            dic[len(word)].append(word)
        max_l = 0
        
        for word in words:
            recurse(word, 1)
        return max_l
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        word_graph = {}
        for word in words:
            for second_word in words:
                if len(second_word) == len(word) + 1:
                    is_predecessor = True
                    second_word_counter = Counter(second_word)
                    first_word_counter = Counter(word)
                    for key, value in list(first_word_counter.items()):
                        if second_word_counter[key] < value:
                            is_predecessor = False
                            break
                    if is_predecessor:
                        if word in word_graph:
                            word_graph[word].append(second_word)
                        else:
                            word_graph[word] = [second_word]
        max_level = 1
        for key, value in list(word_graph.items()):
            queue = deque()
            queue.append((key, 1))
            visted = set()
            while queue:
                curr, level = queue.popleft()
                if level > max_level:
                    max_level = level
                if curr in word_graph:
                    for next_word in word_graph[curr]:
                        queue.append((next_word, level + 1))

        return max_level


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        explored = set()
        stack = [(word, 1) for word in sorted(words, key = len, reverse = True)]
        words = set(words)
        best = 1
        while stack:
            word, chain_length = stack.pop()
            best = max(best, chain_length)
            for candidate_word in [word[:i] + c + word[i:] for c in 'abcdefghijklmnopqrstuvwxyz' for i in range(len(word) + 1)]:
                if candidate_word in words and candidate_word not in explored:
                    stack.append((candidate_word, chain_length + 1))
                    explored.add(candidate_word)
        return best


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def dfs(str):
            dep = 1
            for longer_str in words:
                if len(longer_str) == len(str)+1:
                    for i in range(len(str)+1):
                        new_str = longer_str[:i] + longer_str[i+1:] 
                        if new_str == str:
                            dep = max(dep,dfs(longer_str)+1)
                            seen.append(longer_str)
            return dep
        
        seen = []
        depth = []
        words = sorted(words, key=len)
        for str in words:
            if str not in seen:
                depth.append(dfs(str))
                
        return max(depth)

from collections import Counter
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        def helper(s1, s2):
            if len(s1) != len(s2) - 1:
                return False
            s1 = Counter(s1)
            s2 = Counter(s2)
            diff = 0
            for j in s2:
                if j not in s1:
                    diff += 1
                elif s2[j] != s1[j]:
                    diff += 1
                if diff >= 2:
                    return False
            return diff == 1
    
        dps = [1 for _ in words]
        for j in range(len(words)):
            _max = 1
            for i in range(j):
                if helper(words[i], words[j]):
                    _max = max(_max, dps[i] + 1)
            dps[j] = _max
        return max(dps)
            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        

        # general: create arrays of strings of each length, 
        
        # minimum is one, max is length of the longest word 
        
        # sort and split by length
        
        # BFS by length and at each 
        
        words.sort(key=lambda x: len(x))

        wordLevels = {}
        
        for word in words:
            if len(word) in wordLevels:
                wordLevels[len(word)].append(word)
            else:
                wordLevels[len(word)] = [word]
                
        result = 0
        words.reverse()
        for word in words:
            result = max(result, self.getChain(word, wordLevels, 1))
            
        return result
    
    
    
    def getChain(self, word, wordLevels, length):
        # dfs here
        
        level = len(word)
        for i in range(len(word)):
            if level-1 in wordLevels:
                if word[:i]+word[i+1:] in wordLevels[level-1]:
                    return self.getChain(word[:i]+word[i+1:], wordLevels, length+1)
 
        return length
        
        


class Solution:
    
    def longestStrChain(self, words: List[str]) -> int:
        x=lambda a:len(a)
        words.sort(key=x)
        print(words)
        chain_len=[None]*len(words)
        chain_len[0]=1
        
        
        
        
        for i in range(1,len(words)):
            temp=words[i]
            chain_len[i]=1
            for j in range(len(temp)):
                temp2=temp[:j]+temp[j+1:]
                if (temp2 not in words) :
                    continue
                for k in range(i-1,-1,-1):
                    if (len(words[k])<len(words[i])-1):
                        break        
                    else:
                          if (len(words[k])==len(words[i])-1):
                                  if words[k]==temp2:
                                        chain_len[i]=max(chain_len[i],chain_len[k]+1)
                                        break
                        
        return max(chain_len)

from collections import deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        # return self.run_graph_solution(words)
        return self.run_dp_solution(words)
    
    def run_dp_solution(self, words):
        
        mem = {'': 0}
        
        def inner(w):
            if w in mem or w not in words:
                return
            
            max_per_w = 1
            for j in range(len(w)):
                cur = w[:j] + w[j+1:]
                if cur in mem:
                    val = mem[cur] + 1
                else:
                    inner(cur)
                    if cur not in mem:
                        val = 1
                    else:
                        val = mem[cur] + 1
                if val > max_per_w:
                    max_per_w = val
            mem[w] = max_per_w
            return
        
        for w in words:
            inner(w)
            
        return max(mem.values())
        
    def run_graph_solution(self, words):
        graph = {word: [] for word in words}
        lengths = {k: [] for k in range(1, 17)}
        into = {}
        min_len = 17
        max_len = 1
        for word in words:
            lengths[len(word)].append(word)
            min_len = min(len(word), min_len)
            max_len = max(len(word), max_len)
                    
        for k in range(min_len, max_len):
            for word1 in lengths[k]:
                for word2 in lengths[k + 1]:
                    for i in range(len(word2)):
                        if word2[:i] + word2[i+1:] == word1:
                            graph[word1].append(word2)
                            into[word2] = True     
                            
        sources = [word for word in words if word not in into]
        max_len = 0
        for s in sources:
            max_len = max(max_len, self.run_bfs(s, graph))
        return max_len
                
    def run_bfs(self, s, graph):
        queue = deque(maxlen=len(list(graph.keys())))
        queue.appendleft((s, 1))
        summary = []
        
        while len(queue) > 0:
            current_node, cur_len = queue.pop()
            for word in graph[current_node]:
                queue.appendleft((word, cur_len + 1))
            summary.append(cur_len)
        
        return summary[-1]
    
    
            
            
        
        
        
        
        


from collections import deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        # return self.run_graph_solution(words)
        return self.run_dp_solution(words)
    
    def run_dp_solution(self, words):
        
        mem = {'': 0}
        
        def inner(w):
            if w in mem or w not in words:
                return
            
            max_per_w = 1
            for j in range(len(w)):
                cur = w[:j] + w[j+1:]
                if cur in mem:
                    val = mem[cur] + 1
                elif cur not in words:
                    val = 1
                else:
                    inner(cur)
                    val = mem[cur] + 1
                if val > max_per_w:
                    max_per_w = val
            mem[w] = max_per_w
            return
        
        for w in words:
            inner(w)
            
        return max(mem.values())
        
    def run_graph_solution(self, words):
        graph = {word: [] for word in words}
        lengths = {k: [] for k in range(1, 17)}
        into = {}
        min_len = 17
        max_len = 1
        for word in words:
            lengths[len(word)].append(word)
            min_len = min(len(word), min_len)
            max_len = max(len(word), max_len)
                    
        for k in range(min_len, max_len):
            for word1 in lengths[k]:
                for word2 in lengths[k + 1]:
                    for i in range(len(word2)):
                        if word2[:i] + word2[i+1:] == word1:
                            graph[word1].append(word2)
                            into[word2] = True     
                            
        sources = [word for word in words if word not in into]
        max_len = 0
        for s in sources:
            max_len = max(max_len, self.run_bfs(s, graph))
        return max_len
                
    def run_bfs(self, s, graph):
        queue = deque(maxlen=len(list(graph.keys())))
        queue.appendleft((s, 1))
        summary = []
        
        while len(queue) > 0:
            current_node, cur_len = queue.pop()
            for word in graph[current_node]:
                queue.appendleft((word, cur_len + 1))
            summary.append(cur_len)
        
        return summary[-1]
    
    
            
            
        
        
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        # pred  = [1  , 1 , 1  , 1   , 1+ba, 1+bda]
        # d = {}
        # d[length] = [list of words]
        # d[1] = [\"a\", \"b\"] , ...
        # 26*C
        
        
        # words = [\"a\",\"b\",\"ba\",\"bca\",\"bda\",\"bdca\"]
        # pred  = [1  , 1 , 2  , 3   , 3   , 4    ]
        # solution 1:
        # sort words by their length, O(N log N)
        # O(N^2) time to update each pred based on its last values,
        # find the maximum value in pred.
        # O(N^2*C^2) time, O(N) space,
        
        words = sorted(words, key = lambda x:len(x))
        
        #build an array of length words with default 1,
        pred  = [1]*len(words)
        
        def diff1(w1, w2):
            for i in range(len(w2)):
                if w2[:i]+w2[i+1:] == w1:
                    return True
            return False
        def diff2(w1, w2):
            i = 0
            while i<len(w1) and w1[i]==w2[i]:
                i+=1
            j=len(w1)-1
            while j>=i and w1[j]==w2[j+1]:
                j -= 1
            if j==i-1:
                return True
            return False
        
        # compare each word with words with length-1, 
        for i in range(len(words)):
            j = i-1
            while j>=0 and abs(len(words[j]) - len(words[i])) <2:
                if len(words[j])==len(words[i])-1 and diff2(words[j], words[i]):
                    pred[i] = max(pred[i], pred[j]+1)
                j -= 1
        
        return max(pred)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        
        words.sort(key = len)
        print(words)
        def stringmatch(s1,s2):
            onemismatchallowed = True
            
            if(len(s1)>len(s2)):
                s2,s1 = s1,s2
                
            # print(\"s1\",\"s2\",s1,s2)
    
            if(len(s1)+1!=len(s2)):
                return False
            i,j=0,0
            while(i<len(s1)):
                if(s1[i]!=s2[j]):
                    if(onemismatchallowed==True):
                        onemismatchallowed = False
                    else:
                        return False
                    j+=1
                else:
                    i+=1
                    j+=1
                    
            return True
        
        dp = [1 for _ in range(len(words))]
        
        # dp[0] = 1
        ans = 1
        for i in range(1,len(words)):
            j = i-1
            while(j>=0 and len(words[j])+1>=len(words[i])):
                
                val = stringmatch(words[j],words[i])
                if(val):
                    print(words[j],words[i])
                    print(dp[i])
                    dp[i] = max(dp[j]+1,dp[i])
                    ans = max(dp[i],ans)
                j-=1
        print(dp)
                    
        return ans

class Solution:
    def __init__(self):
        self.results = []
        self.explored = dict()
        
    def traverse(self, string, listt, buffer):
        flag = True
        for i in range(len(string)):
            if string[:i]+string[i+1:] in listt:
                flag = False
                self.traverse(string[:i]+string[i+1:], listt, buffer+[string[:i]+string[i+1:]])
        if flag:
            self.results.append(buffer)
            for item in buffer:
                self.explored[item] = True
        return
        
        
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len, reverse=True)
        for word in words:
            if word not in self.explored:
                self.traverse(word, words, [word])
        return len(max(self.results, key=len))

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(w1, w2):
            if(len(w1) + 1 != len(w2)):
                return False
            
            skip = 0
            for i in range(len(w2)):
                j = i - skip
                if(j == len(w1)):
                    break
                if(w1[j] != w2[i]):
                    skip += 1
                    if(skip >= 2):
                        return False
            
            return True
        
        words.sort(key=len, reverse=True)
        len_words = len(words)
        combos = [0] * len_words
        
        for i in range(len_words):
            for j in range(0, i):
                if(combos[i] < combos[j] + 1 and is_predecessor(words[i], words[j])):
                    # print(words[i] + ', ' + words[j])
                    combos[i] = combos[j] + 1
                    
        # for i in range(len_words):
        #     print(combos[i], end=', ')
        #     print(words[i])
        
        
        return max(combos) + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(str1, str2):
            if len(str1) + 1 != len(str2):
                return False
            else:
                for i in range(len(str2)):
                    if str1 == str2[:i] + str2[i+1:]:
                        return True
                return False
        l = len(words)
        words.sort(key=len)
        mat = [[0 for j in range(l)] for i in range(l)]
        mat_max = 0
        for i in range(l):
            for j in range(i, l):
                if is_predecessor(words[i], words[j]):
                    mat[i][j] = mat[i][i] + 1
                    mat[j][j] = max(mat[j][j], mat[i][j])
                    mat_max = max(mat_max, mat[i][j])
        return mat_max + 1

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        st = set(words)

        def ls(word):
            res = 1
            for j in range(len(word)):
                substr = word[:j] + word[(j+1):]
                if substr not in st:
                    continue
                res = max(res, 1 + ls(substr))
            return res
           
        res = 1
        for word in words:
            res = max(res, ls(word))
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        words_len = len(words)
        
        def predecessor(word, candidate):
            if len(word) != len(candidate)+1:
                return False
            j = 0
            for i, c in enumerate(word):
                if i > j + 1 or j == len(candidate):
                    break
                if c == candidate[j]:
                    j += 1
            return j == len(candidate)
        
        
        dp = [1] * words_len
        for i in range(words_len):
            # cur_possibles = makePossibles(words[i])
            curLen = len(words[i])
            for j in range(i - 1, -1, -1):
                if len(words[j]) < curLen-1:
                    break
                if predecessor(words[i], words[j]):
                    dp[i] = max(dp[i], 1 + dp[j])
                
            
        
        return max(dp)
        
        # makePossibles(words[2])
        # getAdjacents(words[2], 2)
        # print(words)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def pred(origin,dest):
            if len(dest) - len(origin) != 1:
                return False
            misWord = False
            oIndex = 0
            for index in range(len(dest)):
                if oIndex == len(origin):
                    if misWord == False:
                        return True
                    return False
                
                if dest[index] != origin[oIndex]:
                    if misWord:
                        return False
                    misWord = True
                    continue
                oIndex += 1
            return True
        table = {}
        def helper(node):
            longest = 0
            if node in table:
                return table[node]
            for word in words:
                if pred(node,word):
                    longest = max(helper(word),longest)
            table[node] = longest + 1
            return longest + 1
        maxPos = 0
        for word in words:
            maxPos = max(maxPos,helper(word))
        return maxPos


class Solution:
    def _check_word(self, wordlong, word2) -> bool:
        if len(wordlong) - len(word2) > 1 or len(wordlong) - len(word2) == 0:
            return False
        for i in range(0, len(wordlong)):
            newstr = wordlong[:i] + wordlong[i+1:]
            if newstr == word2:
                return True
            
        return False
            
        
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) == 1:
            return words[0]
        
        words = sorted(words, key=len, reverse=True)
        
        m = [1] * len(words)
        for i in range(1, len(words)):
            for j in range(0, i):
                if self._check_word(words[j], words[i]) and m[j] + 1 > m[i]:
                    m[i] = m[j] + 1
        return max(m)
                


from collections import defaultdict, deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        lookup_neighbors = defaultdict(list)
        for word in words:
            word_length: int = len(word)
            lookup_neighbors[word_length].append(word)
        
        def is_next_word_valid(current_word: str, next_word: str) -> bool:
            current_word_length = len(current_word)
            
            for index in range(current_word_length):
                if next_word == current_word[0:index] + current_word[index + 1:]:
                    return True
                
            return False
        
        maximum_chain = 0
        
        for word in reversed(words):
            frontier = deque([(word, 1)])
            
            while frontier:
                next_frontier = deque()
                while frontier:
                    current_word, distance = frontier.popleft()
                    current_word_length = len(current_word)
                    maximum_chain = max(maximum_chain, distance)
                    
                    for neighbor in lookup_neighbors[current_word_length - 1]:
                        if is_next_word_valid(current_word, neighbor):
                            next_frontier.append((neighbor, distance + 1))
                
                frontier = next_frontier
            
        
        return maximum_chain

from collections import Counter, defaultdict

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        length_to_words = defaultdict(list)

        for word in words:
            length_to_words[len(word)].append(word)
            
        lengths = sorted(length_to_words.keys(), reverse=True)
        
        successors = defaultdict(list)
        
        for length in lengths:
            for word in length_to_words[length]:
                
                for predecessor_candidate in length_to_words[length - 1]:
                    if predecessor(predecessor_candidate, word):
                        successors[predecessor_candidate].append(word)
        
        max_lengths = {}
        
        for length in lengths:
            for word in length_to_words[length]:
                if word in successors:
                    max_lengths[word] = 1 + max([max_lengths[s] for s in successors[word]])
                else:
                    max_lengths[word] = 1
        return max(max_lengths.values())
                
                
def predecessor(word1, word2):
    word1_counts = Counter(word1)
    word2_counts = Counter(word2)
    
    letters = set(word1 + word2)
    
    diff = [abs(word1_counts[l] - word2_counts[l]) for l in letters]
    nonzero_diff = [d for d in diff if d > 0]
    if len(nonzero_diff) != 1:
        return False
    if nonzero_diff[0] != 1:
        return False
    return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        wordsListLength = len(words)
        if wordsListLength <= 1:
            return wordsListLength
        def isPredecessor(word1, word2): #is word1 predecessor of word2
            for i in range(len(word2)):
                if word2[:i] + word2[i+1:] == word1:
                    return True
            return False
        words.sort(key = lambda x:len(x))
        result = 1
        dp = [1 for _ in range(wordsListLength)]
        for i in range(wordsListLength):
            for j in range(i + 1, wordsListLength):
                if len(words[i]) + 1 == len(words[j]) and isPredecessor(words[i], words[j]):
                    dp[j] = dp[i] + 1
                    result = max(result, dp[j])
        return result

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(str1, str2):
            if len(str1) + 1 != len(str2):
                return False
            else:
                for i in range(len(str2)):
                    if str1 == str2[:i] + str2[i+1:]:
                        return True
                return False
        l = len(words)
        words.sort(key=len)
        mat = [[0 for j in range(l)] for i in range(l)]
        mat_max = 0
        for i in range(l):
            for j in range(i, l):
                str1, str2 = words[i], words[j]
                if is_predecessor(str1, str2):
                    mat[i][j] = mat[i][i] + 1
                    mat[j][j] = max(mat[j][j], mat[i][j])
                    mat_max = max(mat_max, mat[i][j])
        return mat_max + 1


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key = len)
        wordCounter = [collections.Counter(x) for x in words]
        dp = [1 for _ in range(len(words))]
        for i in range(len(words)-2, -1, -1):
            for j in range(i+1, len(words)):
                if len(words[i]) == len(words[j]): continue
                if len(words[j])-len(words[i]) > 1: break
                if len(wordCounter[j] - wordCounter[i]) == 1:
                    dp[i] = max(dp[i], dp[j]+1)
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dic = collections.Counter()
        res = 0
        for word in words:
            for i in range(len(word)):
                pre = word[:i] + word[i + 1:]
                dic[word] = max(dic[word], dic[pre] + 1)
            res = max(res, dic[word])
        return res


class Solution:
    def one_off(self, a, b):
        if abs(len(a)-len(b)) != 1:
            return False
        one_off = False
        i = 0
        j = 0
        while i < len(a) and j < len(b):
            if a[i]!=b[j]:
                if one_off:
                    return False
                one_off = True
                if len(a) > len(b):
                    j-=1
                else:
                    i-=1
            i+=1
            j+=1
        
        return True
    
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        words.sort(key=lambda word: -len(word))
        # print(words)
        dp = {}
        # prev = {}
        best = 1
        for i, word in enumerate(words):
            for other in words[i+1:]:
                if len(word) - 1 > len(other):
                    break
                if self.one_off(word, other):
                    dp[other] = max(dp.get(other, 0), dp.get(word, 1)+1)
                    # if dp[other] > best:
                    #     prev[other] = word
                    best = max(best, dp[other])
        # print(prev)           
        return best

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def is_predecessor(str1, str2):
            if len(str1) + 1 != len(str2):
                return False
            else:
                for i in range(len(str2)):
                    if str1 == str2[:i] + str2[i+1:]:
                        return True
                return False        
        #words = [\"sgtnz\",\"sgtz\",\"sgz\",\"ikrcyoglz\",\"ajelpkpx\",\\
                #\"ajelpkpxm\",\"srqgtnz\",\"srqgotnz\",\"srgtnz\",\"ijkrcyoglz\"]
        l = len(words)
        words.sort(key=len)
        mat = [[0 for j in range(l)] for i in range(l)]
        mat_max = 0
        for i in range(l):
            for j in range(i, l):
                str1, str2 = words[i], words[j]
                if is_predecessor(str1, str2):
                    mat[i][j] = mat[i][i] + 1
                    mat[j][j] = max(mat[j][j], mat[i][j])
                    mat_max = max(mat_max, mat[i][j])
        return mat_max + 1

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) <= 0:
            return 0
        
        words.sort(key=len)
        numberOfWords = len(words)
        longestLens = [1] * len(words)
        index = numberOfWords - 1    
        ret = 0
        while index >= 0:
            maxLen = 1
            index2 = index + 1
            while index2 < numberOfWords and len(words[index]) + 1 >= len(words[index2]):
                if self.isPredecessor(words[index], words[index2]):
                    if maxLen < longestLens[index2] + 1:
                        maxLen = longestLens[index2] + 1
                index2 += 1
            longestLens[index] = maxLen
            if ret < maxLen:
                ret = maxLen
            index -= 1
        
        return ret
                    
    
    def isPredecessor(self, word1:str, word2:str)-> bool:
        if len(word2) - len(word1) != 1:
            return False
        for i, ch in enumerate(word2):
            newWord = word2[:i] + word2[i+1:]
            if newWord == word1:
                return True
        return False
    
        


from collections import Counter
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda x: len(x))
        def check_predecessor(s1, s2):
            if len(s1) != len(s2) - 1:
                return False
            for i in range(len(s2)):
                if s1 == (s2[:i] + s2[i+1:]):
                    return True
            return False
        
        dp = [1] * len(words)
        res = 1
        
        for i in range(len(words)):
            for j in range(i):
                if check_predecessor(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j]+1)
                    res = max(res, dp[i])
                    
        return res
                    
                    


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
    
        # build graph out of all pairs of words
        graph = defaultdict(list)
        in_degrees = defaultdict(int)
        for w1 in words:
            for w2 in words:
                if self.isPredecessor(w1, w2):
                    # w1 have more words -> w2->w1
                    graph[w2].append(w1)
                    in_degrees[w1] += 1
        
        max_len = 1
        # topological sort to get the maximum length ending with each word
        queue = deque([(w, 1) for w in graph if in_degrees[w] == 0])
        while queue:
            word, path_len = queue.popleft()
            for child in graph[word]:
                in_degrees[child] -= 1
                if in_degrees[child] == 0:
                    queue.append((child, path_len + 1))
            
            max_len = path_len
        return max_len
        

    def isPredecessor(self, w1, w2):
        if len(w1) != len(w2) + 1:
            return False
        
        i1 = 0
        i2 = 0
        added = False
        while i1 < len(w1) and i2 < len(w2):
            if w1[i1] != w2[i2] and added:
                return False
            elif w1[i1] != w2[i2]:
                i1 += 1
                added = True
            else:
                i1 += 1
                i2 += 1

        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) <= 0:
            return 0
        
        words.sort(key=len)
        numberOfWords = len(words)
        longestLens = [1] * len(words)
        index = numberOfWords - 1
        
        while index >= 0:
            maxLen = 1
            index2 = index + 1
            while index2 < numberOfWords and len(words[index]) + 1 >= len(words[index2]):
                if self.isPredecessor(words[index], words[index2]):
                    if maxLen < longestLens[index2] + 1:
                        maxLen = longestLens[index2] + 1
                index2 += 1
            longestLens[index] = maxLen
            index -= 1
        
        maxLen = longestLens[0]
        for longestLen in longestLens:
            if maxLen < longestLen:
                maxLen = longestLen
        return maxLen
                    
    
    def isPredecessor(self, word1:str, word2:str)-> bool:
        if len(word2) - len(word1) != 1:
            return False
        for i, ch in enumerate(word2):
            newWord = word2[:i] + word2[i+1:]
            if newWord == word1:
                return True
        return False
    
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        longestchain = defaultdict(int)
        for word in sorted(words, key=len, reverse=True):
            for i in range(len(word)):
                longestchain[word[:i] + word[i + 1:]] = max(longestchain[word[:i] + word[i + 1:]], 1 + longestchain[word])
        return max(longestchain.values()) 


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        sorted(words, key = len, reverse = True)
        depth = [1] * len(words)
        def dfs(word):
            depth = 1
            for i in range(len(word)):
                new_word = word[:i]+word[i+1:]
                if new_word in words:
                    depth = max(dfs(new_word)+1, depth)
            seen.append(word)
            return depth
        
        count = []
        seen = []
        for word in words:
            if word not in seen:
                count.append(dfs(word))
        return max(count)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        l = [1 for i in range(len(words))]
        words = list(set(words))
        words.sort(key = lambda x:len(x))
        res = [[i] for i in words]
        # print(l,res)
        for i in range(1,len(words)):
            for j in range(i):
                if len(words[j])+1==len(words[i]):
                    x = {}
                    for a in words[j]:
                        if a in x:
                            x[a]+=1
                        else:
                            x[a]=1
                    ctr = 0
                    for a in words[i]:
                        if a not in x:
                            ctr+=1
                        elif x[a]==0:
                            ctr+=1
                        else:
                            x[a]-=1
                    if ctr==1 and l[i]<(l[j]+1):
                        res[i] = [i for i in res[j]]
                        res[i].append([words[i]])
                        l[i] = l[j]+1
        return max(l)
                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        preToPost = collections.defaultdict(set)
        for word1 in words:
            for word2 in words:
                if self.isPredecessor(word1, word2):
                    preToPost[word1].add(word2)
        
        ans = 0
        memo = {}
        for word in words:
            ans = max(ans, self.dfs(word, preToPost, memo))
        return ans
        
        
        
    
    def dfs(self, word, preToPost, memo):
        if word in memo:
            return memo[word]
        
        # if word not in preToPost:
        #     memo[word] = 1
        #     return memo[word]
        
        ans = 1
        for post in preToPost[word]:
            ans = max(ans, 1 + self.dfs(post, preToPost, memo))
        
        memo[word] = ans
        return memo[word]
    
    
    def isPredecessor(self, word1, word2):
        if not len(word1) + 1 == len(word2):
            return False 
        
        missed = False
        i = 0
        j = 0
        while i < len(word1) and j < len(word2) and word1[i] == word2[j]:
            i += 1
            j += 1
        
        j += 1
        
        while i < len(word1) and j < len(word2) and word1[i] == word2[j]:
            i += 1
            j += 1
        
        return i == len(word1) and j == len(word2)


class Solution:
    def longestStrChain(self, words) -> int:
        len_words = len(words)
        dp = [1] * len_words
        res = 0
        words.sort(key = len)
        for i in range(len_words):
            max_value = 0
            for j in range(i):
                if not self.checkChild(words[j], words[i]):
                    continue
                max_value = max(max_value, dp[j])
            dp[i] = max_value + 1
            res = max(res, dp[i])
        return res

    def checkChild(self, word1, word2):
        if len(word1) != len(word2) - 1:
            return False
        from collections import Counter
        counter1 = Counter(word1)
        count = 0
        for ch in word2:
            if ch in counter1:
                counter1[ch] -= 1
                if counter1[ch] == 0:
                    counter1.pop(ch)
            else:
                count += 1
            if count > 1:
                return False
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(word1, word2):
            if len(word1) + 1 != len(word2):
                return False
            i, j, count = 0, 0, 0
            while i < len(word1):
                if word1[i] != word2[j]:
                    count += 1
                    j += 1
                    if count > 1:
                        return False
                    continue
                i += 1
                j += 1
            return True
        
        # build graph
        numPredecessor = [0] * len(words)
        
        graph = defaultdict(lambda: [])
        for i in range(len(words)):
            for j in range(i + 1, len(words)):
                if isPredecessor(words[i], words[j]):
                    graph[i].append(j)
                    numPredecessor[j] += 1
                elif isPredecessor(words[j], words[i]):
                    graph[j].append(i)
                    numPredecessor[i] += 1
        
        # dfs for each node without predecessor
        def dfs(node, path):
            nonlocal out
            
            path.append(node)
            out = max(out, len(path))
            for nei in graph[node]:
                dfs(nei,path)
            path.pop()
            
        
        out = 0
        for node in [i for i in range(len(words)) if not numPredecessor[i]]:
            dfs(node,[])
        return out
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        children = collections.defaultdict(set)
        for i in range(len(words)):
            for j in range(len(words)):
                if len(words[i]) == len(words[j]) + 1:
                    for k in range(len(words[i])):
                        if words[i][:k] + words[i][k+1:] == words[j]:
                            children[words[j]].add(words[i])
        print(children)
        def dfs(w, count):
            visited.add(w)
            if not children[w]:
                self.res = max(self.res, count)
            else:
                for v in children[w]:
                    dfs(v, count + 1)
        visited = set()
        self.res = 0
        for u in words:
            if u not in visited:
                dfs(u, 1)
        return self.res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(word1, word2):
            if len(word1) + 1 != len(word2):
                return False
            i, j, count = 0, 0, 0
            while i < len(word1):
                if word1[i] != word2[j]:
                    count += 1
                    j += 1
                    if count > 1:
                        return False
                    continue
                i += 1
                j += 1
            return True
        
        # build graph
        numPredecessor = [0] * len(words)
        
        graph = defaultdict(lambda: [])
        for i in range(len(words)):
            for j in range(i + 1, len(words)):
                if isPredecessor(words[i], words[j]):
                    graph[i].append(j)
                    numPredecessor[j] += 1
                elif isPredecessor(words[j], words[i]):
                    graph[j].append(i)
                    numPredecessor[i] += 1
        
        # dfs for each node without predecessor
        def dfs(node, path):
            nonlocal out
            
            path.append(node)
            out = max(out, len(path))
            for nei in graph[node]:
                dfs(nei,path)
            path.pop()
            
        
        out = 0
        for node in [i for i in range(len(words)) if not numPredecessor[i]]:
            if out == 16:
                break
            dfs(node,[])
        return out
        
        
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        word_map = collections.defaultdict(int)
        word_list = collections.defaultdict(list)
        ans = 1
        #没有任何先导单词的话，is_predecessor 永远返回false, 这样长度至少为1
        for word in words:
            length = len(word)
            word_list[length].append(word)
            word_map[word] = 1
        
        # print(word_map)
        for word_len in range(1, 17):
            if word_len not in word_list:
                continue
            for word in word_list[word_len]:
                pre_len = word_len - 1
                if pre_len not in word_list:
                    break
                for pre_word in word_list[pre_len]:
                    if self.is_predecessor(pre_word, word):
                        word_map[word] = max(word_map[word], word_map[pre_word] + 1)
                        ans = max(ans, word_map[word])
        
        return ans
    
    def is_predecessor(self, w1, w2):
        idx1, idx2 = 0, 0
        once = False
        while idx1 < len(w1) and idx2 < len(w2):
            if w1[idx1] == w2[idx2]:
                idx1 += 1
                idx2 += 1
            elif once:
                return False
            else:
                once = True
                idx2 += 1
        
        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def isPredecessor(word1, word2):
            if len(word1) + 1 != len(word2):
                return False
            i, j = 0, 0
            while i < len(word1):
                if word1[i] != word2[j]:
                    j += 1
                    if j-i > 1:
                        return False
                    continue
                i += 1
                j += 1
            return True
        
        # build graph
        numPredecessor = [0] * len(words)
        
        graph = defaultdict(lambda: [])
        for i in range(len(words)):
            for j in range(i + 1, len(words)):
                if isPredecessor(words[i], words[j]):
                    graph[i].append(j)
                    numPredecessor[j] += 1
                elif isPredecessor(words[j], words[i]):
                    graph[j].append(i)
                    numPredecessor[i] += 1
        
        # dfs for each node without predecessor
        def dfs(node, path):
            nonlocal out
            
            path.append(node)
            out = max(out, len(path))
            for nei in graph[node]:
                dfs(nei,path)
            path.pop()
            
        
        out = 0
        for node in [i for i in range(len(words)) if not numPredecessor[i]]:
            if out == 16:
                break
            dfs(node,[])
        return out
        
        
        


from collections import defaultdict
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        #idea is to sort words based on length and then apply dfs on each index
        n = len(words)
        words.sort(key=len)
        memo = {}
        
        def check_pred(word1,word2):
            if len(word2) - len(word1) != 1:
                return False
            for i in range(len(word2)):
                pred_str = word2[0:i] + word2[i+1:]
                if word1 == pred_str:
                    return True
            return False
        
        def recur(start):
            if start in memo:
                return memo[start]
            f_max = 1
            for i in range(start+1,n):
                c_max = 1
                if check_pred(words[start], words[i]):
                    c_max = 1 + recur(i)
                f_max = max(f_max,c_max)
            memo[start] = f_max
            return f_max
        
        ans = 0
        for i in range(n):
            ans = max(ans, recur(i))
        return ans
                    
                


from collections import defaultdict
class Solution:
    def is_neighbor(self, s1, s2):
        ss = s1
        bs = s2
        neighbor = False

        for i in range(len(ss) + 1):
            padded_str = ss[:i] + '*' + ss[i:]

            for p1, p2 in zip(padded_str, bs):
                # print((p1 == '*' or p1 == p2))
                neighbor = (p1 == '*' or p1 == p2)
                if not neighbor:
                    break
            if neighbor:
                return neighbor

    path_len = 1

    def build_chains(self, chain_graph, start_key, visited=set(), curr_path=2):
        # print(curr_path_len, start_key)
        visited.add(start_key)
        # print(start_key, curr_path)
        for string in chain_graph[start_key] - visited:
            if string in chain_graph:
                self.build_chains(chain_graph, string, visited, curr_path + 1)
            else:
                print(('term:', string, curr_path+1))
                self.path_len = max(self.path_len, curr_path+1)

    def longestStrChain(self, words: List[str]) -> int:
        str_chain_graph = defaultdict(set)
        for w1 in words:
            for w2 in words:
                if len(w2) - len(w1) == 1:
                    neighbor = self.is_neighbor(w1, w2)
                    if neighbor:
                        str_chain_graph[w1].add(w2)
        # print(str_chain_graph)
        for key in str_chain_graph:
            self.build_chains(str_chain_graph, key, set(), 1)
        return self.path_len


class Solution:
    def string1(self,s1,s2):
        if  abs(len(s1)-len(s2))>1 or abs(len(s1)-len(s2))==0:
            return False
        i=0
        j=0
        k=0
        while i<len(s1) and j<len(s2):
            if k>1:return False
            if s1[i]!=s2[j]:
                k+=1
                if len(s1)>len(s2):
                    i+=1
                else:
                    j+=1
            else:
                i+=1
                j+=1
        return True if k<=1 else False
    def longestStrChain(self, words: List[str]) -> int:
        words= sorted(words,key=len,reverse=True)
        d={}
        l=len(words[0])
        m=1
        for i in range(len(words)):
            w=words[i]
            m=max([m]+list(d.values()))
            d={key:val for key,val in list(d.items()) if len(key)<=len(w)+1}
            d[w]=1
            for key,val in list(d.items()):
                if self.string1(key,w):
                    d[w]=max(d[w],d[key]+1)
            l=len(w)
        return max([m]+list(d.values()))


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def isPred(w1, w2):
            if len(w1) + 1 != len(w2):
                return False
            flag = 0
            
            for i in range(len(w1)):
                if w1[i] != w2[i + flag] and flag == 0:
                    flag = 1
                    if w1[i] != w2[i + flag]:
                        return False
                elif w1[i] != w2[i + flag] and flag != 0:
                    return False
            return True
                    
        
        
        def dfs(i):
            if i in memo:
                return memo[i]
            
            max_level = 0
            for next_i in graph[i]:
                level = dfs(next_i)
                if level > max_level:
                    max_level = level
            memo[i] = max_level + 1
            return memo[i]
        
        n = len(words)
        if n <= 1:
            return n
        
        graph = collections.defaultdict(list)
        
        for i in range(n):
            for j in range(n):
                if isPred(words[i], words[j]):
                    graph[i].append(j)
        #graph, time: O(n*n*k), space(n*n*k)
        memo = {}
        ans = 0
        for i in range(n):
            level = dfs(i)
            if level > ans:
                ans = level
        #dfs, time: O(n), space(n)
        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        cache = {}
        words_dict = {word:None for word in words}
        def chain(word):
            if word in cache: return cache[word]
            if word in words:
                cache[word] = 1 + max(
                    map(chain, [word[:idx]+word[idx+1:] for idx in range(len(word))])
                )
                return cache[word]
            return 0
        return max(map(chain, words))

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def custom_equal(word1, word2):
            if len(word2) == len(word1): return 0
            for i in range(len(word2)):
                if word2[0:i] + word2[i+1:] == word1:
                    return 1
                
            return 0
        
        words.sort(key=lambda x: len(x))
        max_len = 1
        # maximum length ending at index i
        dp = [1 for _ in words]
        for i in range(1, len(words)):
            for j in range(i-1, -1, -1):
                # judge if word[j] is a predecessor of word[i]
                if len(words[j])+1 < len(words[i]):
                    break
                if custom_equal(words[j], words[i]):
                    dp[i] = max(dp[i], dp[j]+1)
                max_len = max(max_len, dp[i])
                
        return max_len
    
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        ans, dicti, memo = 0, collections.defaultdict(list), collections.defaultdict(int)
        for word in words:
            dicti[len(word)].append(word)
        chars = [chr(ch) for ch in range(ord('a'), ord('z')+1)]
        
        def find(count, word):
            if word in memo:
                return count + memo[word]
            if len(word) + 1 not in dicti:
                memo[word] = 0
                return count
                
            maxi = 0
            for i in range(len(word)+1):
                for ch in chars:
                    new_word = word[:i] + ch + word[i:]
                    if new_word in dicti[len(word)+1]:
                        maxi = max(maxi, find(1, new_word))
            memo[word] = maxi
            return count + memo[word]
        
        for word in sorted(words, key=lambda x:len(x)):
            el = find(1, word)
            # print(el, word)
            ans = max(ans, el)
        return ans

            


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        n = len(words)
        lis = defaultdict(int)
        # print(lis)
        words = sorted(words, key=len)
        print(words)
        ans = 0
        for i in range(n):
                w = words[i]
                # lis[w] = 1
                for j in range(len(words[i])):
                    s = w[0:j] + w[j+1:]
                    lis[w] = max(lis[w], lis[s] + 1)
                    
                ans = max(ans, lis[w])
        
        
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
#         words.sort(key=len)
        
#         dp = {}
#         ans = 1
        
#         for word in words:
#             dp[word] = 1
#             for i in range(len(word)):
#                 cand = word[:i] + word[i+1:]
#                 if cand in dp:
#                     dp[word] = max(dp[word], dp[cand] + 1)
#                     ans = max(ans, dp[word])    
#         return ans
               
        if not words:
            return 0
        
        self.memo = {}
        self.ans = 1
        self.words = set(words)
        for word in self.words:
            self.dfs(word)
        return self.ans
        
    def dfs(self, word):   
        if len(word) == 1:
            self.memo[word] = 1
            return 1
                
        self.memo[word] = 1
        for i in range(len(word)):
            nxt = word[:i] + word[i+1:]
            if nxt in self.words:
                self.memo[word] = max(self.memo[word], 1+self.dfs(nxt))
                self.ans = max(self.ans, self.memo[word])
        return self.memo[word]

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
#         words.sort(key=len)
        
#         dp = {}
#         ans = 1
        
#         for word in words:
#             dp[word] = 1
#             for i in range(len(word)):
#                 cand = word[:i] + word[i+1:]
#                 if cand in dp:
#                     dp[word] = max(dp[word], dp[cand] + 1)
#                     ans = max(ans, dp[word])    
#         return ans
               
        if not words:
            return 0
        
        self.memo = {}
        self.ans = 1
        self.words = set(words)
        for word in self.words:
            self.dfs(word, 1)
        return self.ans
        
    def dfs(self, word, count):   
        if len(word) == 1:
            self.memo[word] = 1
            return 1
                
        self.memo[word] = 1
        for i in range(len(word)):
            nxt = word[:i] + word[i+1:]
            if nxt in self.words:
                self.memo[word] = max(self.memo[word], 1+self.dfs(nxt, count + 1))
                self.ans = max(self.ans, self.memo[word])
        return self.memo[word]

from collections import defaultdict

class Solution:
    def is_one_apart(self, word1, word2):
        assert len(word1) >= len(word2)
        
        length_diff = len(word1) - len(word2)
        if length_diff != 1:
            return False
        
        i = 0
        j = 0
        while i < len(word1) and j < len(word2):
            if word1[i] == word2[j]:
                i += 1
                j += 1
            elif i - j >= 1:
                return False
            else:
                i += 1
        
        return True
    
    def longestStrChain(self, words: List[str]) -> int:
        length_words = defaultdict(list)
        chain_lengths = {}
        max_chain = 1
        
        for word in words:
            length_words[len(word)].append(word)
            chain_lengths[word] = 1
        
        for length in sorted(list(length_words.keys()), reverse=True):
            if length - 1 not in length_words:
                continue
            
            for word1 in length_words[length]:
                for word2 in length_words[length - 1]:
                    if chain_lengths[word1] + 1 <= chain_lengths[word2]:
                        continue
                    
                    if self.is_one_apart(word1, word2):
                        chain_lengths[word2] = chain_lengths[word1] + 1
                        max_chain = max(max_chain, chain_lengths[word2])
        
        # print(chain_lengths)
        
        return max_chain
        


class Solution:
    def checkPre(self, small, large):
        
        i = 0
        j = -1
        
        while True:
            l = r = True
            if i < len(small) and small[i] == large[i]:
                i += 1
            else:
                l = False
            
            if j + len(small) >= 0 and small[j] == large[j]:
                j -= 1
            else:
                r = False
            
            if not l and not r:
                break
        
        return i - j >= len(large)
        
    def longestStrChain(self, words: List[str]) -> int:
        table = dict()
        small = 10000
        large = 0
        
        for i in range(len(words)):
            length = len(words[i])
            if length not in table:
                small = min(length, small)
                large = max(length, large)
                table[length] = [i]
            else:
                table[length].append(i)
        
        pre = [1 for i in range(len(words))]
        for i in range(small, large):
            for j in range(len(table[i])):
                for k in range(len(table[i + 1])):
                    if self.checkPre(words[table[i][j]], words[table[i + 1][k]]):
                        pre[table[i + 1][k]] = max(pre[table[i + 1][k]], pre[table[i][j]] + 1)
                        
        return max(pre)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        '''
        DFS with memo
        '''
        words = set(words)
        dp = {}
        def chainEndingAt(word):
            if word not in words:
                return 0
            if word not in dp:
                chain_len = 1
                for i in range(len(word)):
                    pred = word[:i] + word[i+1:]
                    chain_len = max(chain_len, chainEndingAt(pred) + 1)
                dp[word] = chain_len
            return dp[word]
        return max(chainEndingAt(word) for word in words)
        
        '''
        DP
        dp function:
        chain[word2] = max(chain[word1]+1 for all word1 if word1 is predecessor of word2)
        '''
        dp = {}
        for w in sorted(words, key=len):
            dp[w] = 1
            for i in range(len(w)):
                dp[w] = max(dp[w], dp.get(w[:i] + w[i+1:],0) + 1)
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        visited = set()
        word_dict = {}
        
        for word in words:
            word_dict[word] = 1
        
        def visit(word):
            visited.add(word)
            
            for i in range(len(word)):
                child = word[:i] + word[i+1:]
                if child not in word_dict:
                    continue
                
                if child not in visited:
                    visit(child)
                
                word_dict[word] = max(word_dict[word], 1 + word_dict[child])    
        
        for word in words:
            visit(word)
        
        return max(word_dict.values())

                    
        
            


class Solution:
    def match(self, small, big):
        mismatch = 0
        i = 0
        while i < len(small):
            j = i + mismatch
            
            if small[i] == big[j]:
                i+=1
                continue
            elif not mismatch:
                mismatch = 1
            elif mismatch==1:
                return False
        
        return True
    
    def longestStrChain(self, words: List[str]) -> int:
        
        # sort by length
        if len(words) == 1:
            return 1
        len_counts = {}
        min_len = 17
        
        for w in words:
            l = len(w)
            if l not in len_counts:
                len_counts[l] = [w]
                if l < min_len:
                    min_len = l
            else:
                len_counts[l].append(w)
        
        # keep track of the last link, and count
        chains = {}
        
        # start the chains with the smallest words
        for w in len_counts[min_len]:
            chains[w] = 1
        ans = -1
        
        keys = sorted(list(len_counts.keys()))
        prev_len = keys[0]
        for l in keys[1:]:
            
            # get all words of this length
            poss = len_counts[l]
            if l != prev_len + 1:
                prev_len = l
                
                # just add these to dict
                for w in poss:
                    if w not in chains:
                        chains[w] = 1
                        
                continue
                
            poss_last = len_counts[prev_len]
            
            # print(poss, poss_last)
            for w in poss:
                if w not in chains:
                    chains[w] = 1

                #check for each word less than this
                for last in poss_last:
                    if self.match(last, w):
                        chains[w] = max(chains[w], chains[last]+1)
            
            prev_len = l
        # return max value in chains
        # print(chains)
        return max(chains.values())
                
        
        
            
        


class Solution:
    def isPredecessor(self, s1, s2):
        if len(s2) - len(s1) != 1: return False
        newcharfound = False
        for i in range(len(s2)):
            if not newcharfound:
                if i == len(s1): return True
                if s1[i] != s2[i]:
                    newcharfound = True
                    j = i
            else:
                if s1[j] != s2[i]:
                    return False
                j += 1
        return True
    
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        
        lentoword = collections.defaultdict(list)
        for i,w in enumerate(words):
            lentoword[len(w)].append((w,i))

        dp = [1 for i in range(len(words)+1)]
        dp[0] = 0 

        for i in range(1, len(words) + 1): 
            word = words[i-1]
            curlen = len(words[i-1])
            for nbor, nborindex in lentoword[curlen-1]:
                if self.isPredecessor(nbor, word):
                    dp[i] = max(dp[i], dp[nborindex+1] +1)
        return max(dp)


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        word_dict = defaultdict(int)
        longest = 0
        
        for word in words:
            for i in range(len(word)):
                wc = word[:i ] + word[i+1:]
                word_dict[word] = max(word_dict[wc]+1, word_dict[word])
            longest = max(longest, word_dict[word])
        return longest

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        def dfs(cur):
            if cur not in s:
                return 0
            if len(cur) == 0:
                return 0
            if cur in d:
                return d[cur]
            res = 1
            for i in range(len(cur)):
                t = cur[:i] + cur[i+1:]
                res = max(dfs(t)+1, res)
            d[cur] = res
            return d[cur]
            
        s = set(words) 
        d = {}
        _max = 0
        for w in words:
            _max = max(dfs(w), _max)
        return _max


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if len(words) == 0:
            return 0
        adj = {}
        for w in words:
            for i in range(len(w)):
                s = w[0:i] + w[i+1:]
                if s not in adj:
                    adj[s] = [w]
                else:
                    adj[s].append(w)
        res = 1
        queue = []
        visited = {}
        def dfs(root):
            if root in visited:
                return visited[root]
            if root not in adj:
                visited[root] = 1
                return 1
            distance = 1
            for next_ in adj[root]:
                distance = max(distance, dfs(next_)+1)
            visited[root] = distance
            return distance
        for w in words:
            length = dfs(w)
            res = max(res, length)
        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = collections.defaultdict(int)
        for w in sorted(words, key = len):
            for i in range(len(w)):
                dp[w] = max(dp[w], 1 +  dp[w[:i] + w[i + 1:]])
        return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        graph = {}
        max_dist = 0
        for word in words:
            length = len(word)
            dist = 0
            for i in range(length):
                if (check := word[:i] + word[i+1:]) in graph:
                    dist = max(graph[check], dist)
                graph[word] = dist+1
                max_dist = max(max_dist, graph[word])
        return max_dist

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = collections.defaultdict(int)
        
        for word in sorted(words, key=len):
            for i in range(len(word)):
                dp[word] = max(dp[word], dp[word[:i] + word[i+1:]] + 1)
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = set(words)
        @lru_cache()
        def helper(word):
            if not word or word not in words: 
                return 0
            return 1 + max(helper(word[:i] + word[i + 1:]) for i in range(len(word)))
        return max(helper(word) for word in words)

from functools import lru_cache

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        @lru_cache
        def chain_len(w):
            return 1 + max(chain_len(w[:i] + w[i+1:]) for i in range(len(w))) if w in valid else 0
        
        valid = set(words)
        return max(chain_len(w) for w in words)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        ans = defaultdict(int)
        words.sort(key=len)
        for s in words:
            n = len(s)
            now = 0
            for i in range(n):
                s1, s2 = s[:i], s[i+1:]
                now = max(now, ans[(s1, s2)])
            now += 1
            for i in range(n+1):
                s1, s2 = s[:i], s[i:]
                ans[(s1, s2)] = now
        return max(ans.values())

from functools import lru_cache

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        @lru_cache
        def chain_len(word):
            return 1 + max(chain_len(word[:i] + word[i+1:]) for i in range(len(word))) if word in valid else 0
        
        valid = set(words)
        return max(chain_len(word) for word in words)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        res = 0
        dic = collections.defaultdict(int)
        words.sort(key=lambda x : len(x))
        for w in words:
            m = len(w)
            for i in range(m):
                new = w[:i] + w[i + 1:]
                dic[w] = max(dic[w], dic[new] + 1)
            res = max(res, dic[w])
        return res

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        wc = {}
        for w in words:
            wc.setdefault(len(w), [])
            wc[len(w)].append(w)
        
        d = {}
        for i in sorted(wc.keys()):
            if i - 1 not in wc:
                for w in wc[i]:
                    d[w] = 1
            else:
                for w in wc[i]:
                    d[w] = 1
                    for i in range(len(w)):
                        if w[:i] + w[i+1:] in d:
                            d[w] = max(d[w], d[w[:i] + w[i+1:]] + 1)
        return max(d.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        wordSet = set(words)
        i_min = min(wordSet)
        @lru_cache()
        def aux(word):
            if not word or word not in wordSet: return 0
            if len(word) == i_min: return 1
            return 1+max(aux(word[:i]+word[i+1:]) for i in range(len(word)))
        return max(aux(w) for w in words)

import copy
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=lambda word: len(word), reverse=True)
        wordSet = set(copy.deepcopy(words))
        visited = set()
        
        def find_chain(word, length):
            if word not in wordSet or word in visited:
                return length
            
            visited.add(word)
            max_len = 0
            for i in range(len(word)):
                tmp = copy.deepcopy(word)
                tmp = tmp[:i] + tmp[i+1:]
                
                max_len = max(find_chain(tmp, length+1), max_len)
                
            return max_len
        
        max_len = 0
        for word in words:
            max_len = max(max_len, find_chain(word, 0))
        
        return max_len
        
            
                


class Solution:
    def predecessor(self, word_1, word_2):
        if abs(len(word_1)-len(word_2)) != 1: return False
        idx_1 = 0
        idx_2 = 0
        while(idx_1 < len(word_1) and idx_2 < len(word_2)):
            if word_1[idx_1] != word_2[idx_2]:
                if idx_1 != idx_2: return False
                if len(word_1) < len(word_2): idx_2 += 1
                else: idx_1 += 1
            else:
                idx_1 += 1
                idx_2 += 1
        return True
    def longestStrChain(self, words: List[str]) -> int:
        dict = {}
        dp = [1 for i in range(0, len(words))]
        words.sort(key=len)
        for i in range(0, len(words)):
            words[i] = ''.join(sorted(words[i]))
            if len(words[i]) in list(dict.keys()): dict[len(words[i])].append(i)
            else: dict[len(words[i])] = [i]
        for i in range(0, len(words)):
            if len(words[i])+1 in list(dict.keys()):
                for elem in dict[len(words[i])+1]:
                    if self.predecessor(words[elem], words[i]): dp[elem] = max(dp[elem], dp[i]+1)
        ans = 0
        for i in range(0, len(dp)): ans = max(ans, dp[i])
        return ans
            
       
        
        
            
            
        


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words = set(words)
        @lru_cache()
        def aux(word):
            if not word or word not in words: return 0
            return 1+max(aux(word[:i]+word[i+1:]) for i in range(len(word)))
        return max(aux(w) for w in words)

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        from collections import defaultdict
        words.sort(key=lambda word:len(word), reverse=True)
        length = defaultdict(int)
        longest = 0
        for word in words:
            length[word] = max(length[word], 1)
            longest = max(longest, length[word])
            for i in range(len(word)):
                length[word[:i] + word[i+1:]] = max(length[word[:i] + word[i+1:]], length[word] + 1)
        return longest

from collections import defaultdict, deque

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        
        words_set = set(words)
        
        def helper(word):
            
            if len(word) == 0:
                return 0
            
            for index in range(len(word)):
                
                string = word[:index] + word[index + 1:]
                
                if string not in maxChain and string in words_set:
                    helper(string)
                
                maxChain[word] = max(maxChain[word], maxChain[string] + 1)
                
            return maxChain[word]
        
        maxChain = defaultdict(lambda: 0)
                
        for word in words:
            
            maxVal = 0
            
            if word not in maxChain:
                helper(word)

        return max(maxChain.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        longest = 0
        word_dict = defaultdict(int)
        
        
        for word in words:
            for i in range(len(word)):
                wc = word[:i] + word[i+1:]
                word_dict[word] = max(word_dict[wc] + 1, word_dict[word])
                
            longest = max(longest, word_dict[word])
        return longest

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        '''
        DFS with memo
        '''
        words = set(words)
        dp = {}
        
        def chainEndingAt(word):
            if word not in words:
                return 0
            # if memo
            chain_len = 1
            for i in range(len(word)):
                pred = word[:i] + word[i+1:]
                chain_len = max(chain_len, chainEndingAt(pred) + 1)
            return chain_len
        
        ans = 0
        for word in words:
            ans = max(ans, chainEndingAt(word))
        return ans
        
        '''
        DP
        dp function:
        chain[word2] = max(chain[word1]+1 for all word1 if word1 is predecessor of word2)
        '''
        # dp = {}
        # for w in sorted(words, key=len):
        #     dp[w] = 1
        #     for i in range(len(w)):
        #         dp[w] = max(dp[w], dp.get(w[:i] + w[i+1:],0) + 1)
        # return max(dp.values())


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        dp = {}
        by_length = collections.defaultdict(set)
        for w in words:
            by_length[len(w)].add(w)
        for i, l in enumerate(sorted(by_length.keys())):
            if i == 0:
                for w in by_length[l]:
                    dp[w] = 1
            else:
                if not by_length[l - 1]:
                    for w in by_length[l]:
                        dp[w] = 1
                else:
                    for w in by_length[l]:
                        dp[w] = 1
                        for j in range(len(w)):
                            candidate = w[:j] + w[j + 1:]
                            if candidate in by_length[l - 1]:
                                dp[w] = max(dp[w], dp[candidate] + 1)
        return max(dp.values())

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
            d={j:1 for j in words}
            n=len(words);used={};c=[0]
            words=sorted(words,key=len)
            def dfs(s,l):
                if not s or s not in d:
                    c[0]=max(c[0],len(l))
                    return
                used[s]=1
                m=0
                for i in range(len(s)):
                     dfs(s[:i]+s[i+1:],l+[s])
                     m=max(m,len(l))
            for i in range(n-1,-1,-1):
                if words[i] not in used and c[0]<i:
                    dfs(words[i],[])
            return c[0]
                
                


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        def predecessor(word1, word2):
            for i in range(len(word2)):
                if word2[0:i] + word2[i + 1:] == word1:
                    return True
            return False
        words.sort(key = len)
        words_sort = {}
        for i in words:
            if len(i) in words_sort:
                words_sort[len(i)].append(i)
            else:
                words_sort[len(i)] = [i]
        mx = len(words_sort)
        def dfs(words, words_sort):
            max_chain = 1
            stack = [(1, word) for word in words[::-1]]
            while stack:
                current = stack.pop()
                if current[0] > max_chain:
                    max_chain = current[0]
                    if max_chain == mx:
                        return max_chain
                if len(current[1]) + 1 in words_sort:
                    for word in words_sort[len(current[1]) + 1]:
                        if predecessor(current[1], word):
                            if (1, word) in stack:
                                stack.remove((1, word))
                            stack.append((current[0] + 1, word))
                else:
                    continue
            return max_chain
        max_chain = dfs(words, words_sort)
        return max_chain


class Solution:
    def differ(self,x1,x2):
        i,j =0,0
        diff=0
        while i!=len(x1) and j!=len(x2):
            if x1[i]!=x2[j]:
                diff+=1
            else:
                i+=1
            if diff>1:
                return False
            j+=1
        return True
    
    def check(self,x1,x2):
        if x1==x2:
            return True
        
        if abs(len(x1)-len(x2))!=1:
            return False
        else:
            if len(x2)>len(x1):
                a,b = x1,x2
            else:
                a,b = x2,x1
            return self.differ(a,b)
    
    def longestStrChain(self, words: List[str]) -> int:
        words = list(set(words))
        lengths = {}
        for word in words:
            length = len(word)
            lengths[length] = lengths.get(length,[])+[word]
        
        lengths = sorted(lengths.items(),key = lambda x :x[0],reverse=True)
        value = {}
        max_chain = 1
        for i,length in enumerate(lengths):
            for word in length[1]:
                if i ==0:
                    value[word]=1
                else:
                    temp_max = 0
                    for temp_word in lengths[i-1][1]:
                        if self.check(word,temp_word):
                            temp_max = max(temp_max,value[temp_word])
                
                    value[word] = 1+temp_max
                    max_chain = max(value[word],max_chain)
        
        return max_chain

class Solution:
    def helper(self, memo, s, w):
        if w in memo: return memo[w]
        count = 0
        for i in range(len(w)):
            pre = w[:i] + w[i+1:]
            if pre in s:
                count = max(count, self.helper(memo, s, pre))
        memo[w] = 1 + count
        return 1 + count

    def longestStrChain(self, words: List[str]) -> int:
        ans = 0
        memo = {}
        for w in words:
            ans = max(ans, self.helper(memo, set(words), w))
        return ans

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        sets = collections.defaultdict(set)
        n, dp = 0, {}
        for word in words:
            n = max(n, len(word))
            sets[len(word)].add(word)
            dp[word] = 1
        
        ans = 1
        for i in reversed(list(range(2, n+1))):
            if i-1 not in sets:
                continue
            
            for word in sets[i]:
                for j in range(len(word)):
                    new_word = word[:j] + word[j+1:]
                    if new_word in sets[i-1]:
                        dp[new_word] = dp[word] + 1
                        ans = max(ans, dp[new_word])
        
        return ans


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        counter = collections.defaultdict(int)
        longest = 0
        
        for w in sorted(words, key=len):
            for i in range(len(w)):
                subword = w[:i] + w[i+1:]
                count = counter[subword] + 1
                counter[w] = max(counter[w], count)
                longest = max(longest, counter[w])
        
        return longest

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        words.sort(key=len)
        dp = [1 for _ in range(len(words))]
        start = collections.defaultdict(int)
        end = collections.defaultdict(int)
        
        for i in range(len(words)-1, -1, -1):
            start[(len(words[i]))] = i
            
        if len(start) == 1:
            return 1
            
        for i in range(len(words)):
            end[(len(words[i]))] = i
            
        res = 1
            
        for i in range(start[len(words[0])+1], len(words)):
            for j in range(start[len(words[i])-1], end[len(words[i])-1]+1):
                charA = set(words[j])
                charB = set(words[i])
                intersect = len(charA.intersection(charB))
                if intersect == len(charA):
                    dp[i] = max(dp[i], dp[j]+1)
            res = max(res, dp[i])

        return res


class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        if not words:
            return 0
        
        # bucket sort -> map len to words
        len2words = defaultdict(list)
        for w in words:
            len2words[len(w)].append(w)
        
        # build graph out of all pairs of words
        graph = defaultdict(list)
        in_degrees = defaultdict(int)
        for w1 in words:
            for w2 in len2words[len(w1)-1]:
                if self.isPredecessor(w1, w2):
                    # w1 have more words -> w2->w1
                    graph[w2].append(w1)
                    in_degrees[w1] += 1
        
        max_len = 1
        # topological sort to get the maximum length ending with each word
        queue = deque([(w, 1) for w in graph if in_degrees[w] == 0])
        while queue:
            word, path_len = queue.popleft()
            for child in graph[word]:
                in_degrees[child] -= 1
                if in_degrees[child] == 0:
                    queue.append((child, path_len + 1))
            
            max_len = path_len
        return max_len
        

    def isPredecessor(self, w1, w2):
        if len(w1) != len(w2) + 1:
            return False
        
        i1 = 0
        i2 = 0
        added = False
        while i1 < len(w1) and i2 < len(w2):
            if w1[i1] != w2[i2] and added:
                return False
            elif w1[i1] != w2[i2]:
                i1 += 1
                added = True
            else:
                i1 += 1
                i2 += 1

        return True

class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        self.ans = 0
        words = sorted(words, key=lambda x: len(x))
        
        self.n = len(words)
        self.m = {}
        for i in range(self.n):
            if words[i] in self.m:
                self.m[words[i]] = i
            else:
                self.m[words[i]] = i
        for i in range(self.n - 1, -1, -1):
            self.findLongest(words, i, 0)
        return self.ans
    
    def findLongest(self, words, i, cur_ans):
        for j in range(len(words[i])):
            if words[i][:j] + words[i][j + 1:] in self.m:
                self.findLongest(words, self.m[words[i][:j] + words[i][j + 1:]], cur_ans + 1)
            
        self.ans = max(self.ans, cur_ans + 1)