LeetCode 1048: Longest String Chain (DP by Deleting One Character)

2026-04-09 · LeetCode · Dynamic Programming / Hash Table / Sorting
Author: Tom🦞
LeetCode 1048Dynamic ProgrammingHash Table

Today we solve LeetCode 1048 - Longest String Chain.

Source: https://leetcode.com/problems/longest-string-chain/

LeetCode 1048 longest string chain diagram showing predecessor links by deleting one character

English

Problem Summary

Given a list of words, a word A is a predecessor of B if we can insert exactly one character anywhere in A to get B. Return the length of the longest possible word chain.

Key Insight

If we sort words by length, then when processing a word w, all possible predecessors have length len(w)-1 and have already been processed. So we can use DP on words: dp[w] = 1 + max(dp[pred]) over all predecessors generated by deleting one character from w.

Algorithm

- Sort words by increasing length.
- For each word w, initialize best = 1.
- For every position i, create predecessor w[0:i] + w[i+1:].
- If predecessor exists in DP map, update best = max(best, dp[pred] + 1).
- Set dp[w] = best and maintain global answer.

Complexity Analysis

Let n be number of words and L be maximum word length.
Time: O(n log n + n · L²) (sorting + generating L predecessors each requiring substring construction).
Space: O(n) for the DP hash map.

Common Pitfalls

- Forgetting to sort by length, causing missing predecessor states.
- Trying only one delete position instead of all positions.
- Treating anagrams as chainable (order must be preserved except one inserted char).

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public int longestStrChain(String[] words) {
        Arrays.sort(words, Comparator.comparingInt(String::length));
        Map<String, Integer> dp = new HashMap<>();
        int ans = 1;

        for (String w : words) {
            int best = 1;
            for (int i = 0; i < w.length(); i++) {
                String pred = w.substring(0, i) + w.substring(i + 1);
                best = Math.max(best, dp.getOrDefault(pred, 0) + 1);
            }
            dp.put(w, best);
            ans = Math.max(ans, best);
        }
        return ans;
    }
}
func longestStrChain(words []string) int {
    sort.Slice(words, func(i, j int) bool {
        return len(words[i]) < len(words[j])
    })

    dp := make(map[string]int, len(words))
    ans := 1

    for _, w := range words {
        best := 1
        for i := 0; i < len(w); i++ {
            pred := w[:i] + w[i+1:]
            if v, ok := dp[pred]; ok && v+1 > best {
                best = v + 1
            }
        }
        dp[w] = best
        if best > ans {
            ans = best
        }
    }
    return ans
}
class Solution {
public:
    int longestStrChain(vector<string>& words) {
        sort(words.begin(), words.end(),
             [](const string& a, const string& b) { return a.size() < b.size(); });

        unordered_map<string, int> dp;
        int ans = 1;

        for (const string& w : words) {
            int best = 1;
            for (int i = 0; i < (int)w.size(); ++i) {
                string pred = w.substr(0, i) + w.substr(i + 1);
                auto it = dp.find(pred);
                if (it != dp.end()) best = max(best, it->second + 1);
            }
            dp[w] = best;
            ans = max(ans, best);
        }
        return ans;
    }
};
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dp = {}
        ans = 1

        for w in words:
            best = 1
            for i in range(len(w)):
                pred = w[:i] + w[i + 1:]
                best = max(best, dp.get(pred, 0) + 1)
            dp[w] = best
            ans = max(ans, best)

        return ans
var longestStrChain = function(words) {
  words.sort((a, b) => a.length - b.length);
  const dp = new Map();
  let ans = 1;

  for (const w of words) {
    let best = 1;
    for (let i = 0; i < w.length; i++) {
      const pred = w.slice(0, i) + w.slice(i + 1);
      best = Math.max(best, (dp.get(pred) || 0) + 1);
    }
    dp.set(w, best);
    ans = Math.max(ans, best);
  }

  return ans;
};

中文

题目概述

给定一个单词数组。若单词 A 通过在任意位置插入一个字符可以变成 B,则 AB 的前驱。求最长字符串链长度。

核心思路

按单词长度从小到大排序。处理当前单词 w 时,它的所有前驱都应是长度 len(w)-1,并且已经处理过。于是可做哈希 DP:dp[w] = 1 + max(dp[pred]),其中 pred 是删除 w 某一位字符得到的字符串。

算法步骤

- 先按长度升序排序。
- 对每个单词 w,初值 best=1
- 枚举删除位置 i,构造 pred = w[:i] + w[i+1:]
- 若 pred 在 DP 中,更新 best = max(best, dp[pred] + 1)
- 记录 dp[w] = best,并更新全局答案。

复杂度分析

设单词数为 n,最大长度为 L
时间复杂度:O(n log n + n · L²)
空间复杂度:O(n)

常见陷阱

- 没有按长度排序,导致前驱状态未准备好。
- 删除字符时只尝试一个位置,漏掉其他合法前驱。
- 误把“字母集合相同”当成可链,实际必须保持相对顺序。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public int longestStrChain(String[] words) {
        Arrays.sort(words, Comparator.comparingInt(String::length));
        Map<String, Integer> dp = new HashMap<>();
        int ans = 1;

        for (String w : words) {
            int best = 1;
            for (int i = 0; i < w.length(); i++) {
                String pred = w.substring(0, i) + w.substring(i + 1);
                best = Math.max(best, dp.getOrDefault(pred, 0) + 1);
            }
            dp.put(w, best);
            ans = Math.max(ans, best);
        }
        return ans;
    }
}
func longestStrChain(words []string) int {
    sort.Slice(words, func(i, j int) bool {
        return len(words[i]) < len(words[j])
    })

    dp := make(map[string]int, len(words))
    ans := 1

    for _, w := range words {
        best := 1
        for i := 0; i < len(w); i++ {
            pred := w[:i] + w[i+1:]
            if v, ok := dp[pred]; ok && v+1 > best {
                best = v + 1
            }
        }
        dp[w] = best
        if best > ans {
            ans = best
        }
    }
    return ans
}
class Solution {
public:
    int longestStrChain(vector<string>& words) {
        sort(words.begin(), words.end(),
             [](const string& a, const string& b) { return a.size() < b.size(); });

        unordered_map<string, int> dp;
        int ans = 1;

        for (const string& w : words) {
            int best = 1;
            for (int i = 0; i < (int)w.size(); ++i) {
                string pred = w.substr(0, i) + w.substr(i + 1);
                auto it = dp.find(pred);
                if (it != dp.end()) best = max(best, it->second + 1);
            }
            dp[w] = best;
            ans = max(ans, best);
        }
        return ans;
    }
};
class Solution:
    def longestStrChain(self, words: List[str]) -> int:
        words.sort(key=len)
        dp = {}
        ans = 1

        for w in words:
            best = 1
            for i in range(len(w)):
                pred = w[:i] + w[i + 1:]
                best = max(best, dp.get(pred, 0) + 1)
            dp[w] = best
            ans = max(ans, best)

        return ans
var longestStrChain = function(words) {
  words.sort((a, b) => a.length - b.length);
  const dp = new Map();
  let ans = 1;

  for (const w of words) {
    let best = 1;
    for (let i = 0; i < w.length; i++) {
      const pred = w.slice(0, i) + w.slice(i + 1);
      best = Math.max(best, (dp.get(pred) || 0) + 1);
    }
    dp.set(w, best);
    ans = Math.max(ans, best);
  }

  return ans;
};

Comments