Hard

题目描述

一个由小写英文字母组成的原字符串,可以通过以下步骤编码:

  1. 任意将其分割成若干个非空子字符串的序列。
  2. 任意选择序列中的一些元素(可能没有),并将每个元素替换为其长度(作为数字字符串)。
  3. 将序列连接起来作为编码字符串。

例如,编码原字符串 “abcdefghijklmnop” 的一种方式可能是:

  1. 分割成序列:[“ab”, “cdefghijklmn”, “o”, “p”]。
  2. 选择第二和第三个元素分别替换为它们的长度。序列变为 [“ab”, “12”, “1”, “p”]。
  3. 连接序列的元素得到编码字符串:“ab121p”。

给定两个编码字符串 s1 和 s2,由小写英文字母和数字 1-9(包含)组成,如果存在一个原字符串可以同时编码为 s1 和 s2,则返回 true。否则,返回 false。

注意:测试用例保证 s1 和 s2 中连续数字的个数不超过 3。

示例 1:

输入:s1 = "internationalization", s2 = "i18n"
输出:true
解释:可能的原字符串是 "internationalization"。

示例 2:

输入:s1 = "l123e", s2 = "44"
输出:true
解释:可能的原字符串是 "leetcode"。

示例 3:

输入:s1 = "a5b", s2 = "c5b"
输出:false
解释:不可能存在这样的原字符串。

约束条件:

  • 1 <= s1.length, s2.length <= 40
  • s1 和 s2 仅由数字 1-9(包含)和小写英文字母组成
  • s1 和 s2 中连续数字的个数不超过 3

解题思路

这是一道复杂的动态规划问题。核心思想是:两个编码字符串能表示同一个原字符串,当且仅当它们在每个位置上都能匹配。

解题思路:

  1. 状态设计:使用三维DP状态 (i, j, diff),其中:

    • i 表示 s1 的当前位置
    • j 表示 s2 的当前位置
    • diff 表示两字符串在原字符串中的位置差(s1的位置 - s2的位置)
  2. 数字处理:当遇到数字时,需要考虑所有可能的分割方式。例如 “123” 可以分割为:

    • 1 + 23 = 1个字符 + 23个字符
    • 12 + 3 = 12个字符 + 3个字符
    • 123 = 123个字符
  3. 匹配策略

    • 字母vs字母:必须相同
    • 字母vs数字:调整位置差
    • 数字vs数字:尝试所有可能的分割组合
  4. 优化:使用记忆化搜索避免重复计算,同时限制 diff 的范围在合理区间内。

关键在于正确处理数字字符串的所有可能解释,并维护两个字符串在原字符串中的相对位置关系。

代码实现

class Solution {
public:
    unordered_map<string, bool> memo;
    
    bool possiblyEquals(string s1, string s2) {
        return dfs(s1, s2, 0, 0, 0);
    }
    
    bool dfs(const string& s1, const string& s2, int i, int j, int diff) {
        string key = to_string(i) + "," + to_string(j) + "," + to_string(diff);
        if (memo.count(key)) return memo[key];
        
        if (i == s1.length() && j == s2.length()) {
            return memo[key] = (diff == 0);
        }
        
        if (i < s1.length() && isdigit(s1[i])) {
            string num = "";
            for (int k = i; k < s1.length() && isdigit(s1[k]) && k < i + 3; k++) {
                num += s1[k];
                int val = stoi(num);
                if (dfs(s1, s2, k + 1, j, diff - val)) {
                    return memo[key] = true;
                }
            }
        } else if (j < s2.length() && isdigit(s2[j])) {
            string num = "";
            for (int k = j; k < s2.length() && isdigit(s2[k]) && k < j + 3; k++) {
                num += s2[k];
                int val = stoi(num);
                if (dfs(s1, s2, i, k + 1, diff + val)) {
                    return memo[key] = true;
                }
            }
        } else {
            if (diff > 0) {
                if (j < s2.length() && dfs(s1, s2, i, j + 1, diff - 1)) {
                    return memo[key] = true;
                }
            } else if (diff < 0) {
                if (i < s1.length() && dfs(s1, s2, i + 1, j, diff + 1)) {
                    return memo[key] = true;
                }
            } else {
                if (i < s1.length() && j < s2.length() && s1[i] == s2[j]) {
                    if (dfs(s1, s2, i + 1, j + 1, 0)) {
                        return memo[key] = true;
                    }
                }
            }
        }
        
        return memo[key] = false;
    }
};
class Solution:
    def possiblyEquals(self, s1: str, s2: str) -> bool:
        from functools import lru_cache
        
        @lru_cache(None)
        def dfs(i, j, diff):
            if i == len(s1) and j == len(s2):
                return diff == 0
            
            if i < len(s1) and s1[i].isdigit():
                num = ""
                for k in range(i, min(i + 3, len(s1))):
                    if s1[k].isdigit():
                        num += s1[k]
                        val = int(num)
                        if dfs(k + 1, j, diff - val):
                            return True
                    else:
                        break
            elif j < len(s2) and s2[j].isdigit():
                num = ""
                for k in range(j, min(j + 3, len(s2))):
                    if s2[k].isdigit():
                        num += s2[k]
                        val = int(num)
                        if dfs(i, k + 1, diff + val):
                            return True
                    else:
                        break
            else:
                if diff > 0:
                    if j < len(s2) and dfs(i, j + 1, diff - 1):
                        return True
                elif diff < 0:
                    if i < len(s1) and dfs(i + 1, j, diff + 1):
                        return True
                else:
                    if i < len(s1) and j < len(s2) and s1[i] == s2[j]:
                        if dfs(i + 1, j + 1, 0):
                            return True
            
            return False
        
        return dfs(0, 0, 0)
public class Solution {
    private Dictionary<string, bool> memo = new Dictionary<string, bool>();
    
    public bool PossiblyEquals(string s1, string s2) {
        return Dfs(s1, s2, 0, 0, 0);
    }
    
    private bool Dfs(string s1, string s2, int i, int j, int diff) {
        string key = $"{i},{j},{diff}";
        if (memo.ContainsKey(key)) return memo[key];
        
        if (i == s1.Length && j == s2.Length) {
            return memo[key] = (diff == 0);
        }
        
        if (i < s1.Length && char.IsDigit(s1[i])) {
            string num = "";
            for (int k = i; k < s1.Length && char.IsDigit(s1[k]) && k < i + 3; k++) {
                num += s1[k];
                int val = int.Parse(num);
                if (Dfs(s1, s2, k + 1, j, diff - val)) {
                    return memo[key] = true;
                }
            }
        } else if (j < s2.Length && char.IsDigit(s2[j])) {
            string num = "";
            for (int k = j; k < s2.Length && char.IsDigit(s2[k]) && k < j + 3; k++) {
                num += s2[k];
                int val = int.Parse(num);
                if (Dfs(s1, s2, i, k + 1, diff + val)) {
                    return memo[key] = true;
                }
            }
        } else {
            if (diff > 0) {
                if (j < s2.Length && Dfs(s1, s2, i, j + 1, diff - 1)) {
                    return memo[key] = true;
                }
            } else if (diff < 0) {
                if (i < s1.Length && Dfs(s1, s2, i + 1, j, diff + 1)) {
                    return memo[key] = true;
                }
            } else {
                if (i < s1.Length && j < s2.Length && s1[i] == s2[j]) {
                    if (Dfs(s1, s2, i + 1, j + 1, 0)) {
                        return memo[key] = true;
                    }
                }
            }
        }
        
        return memo[key] = false;
    }
}
var possiblyEquals = function(s1, s2) {
    const memo = new Map();
    
    function getPossibleLengths(s, start) {
        const lengths = new Set();
        let num = 0;
        for (let i = start; i < s.length && i < start + 3 && /\d/.test(s[i]); i++) {
            num = num * 10 + parseInt(s[i]);
            lengths.add(num);
        }
        return lengths;
    }
    
    function dfs(i1, i2, diff) {
        if (i1 === s1.length && i2 === s2.length) {
            return diff === 0;
        }
        
        const key = `${i1},${i2},${diff}`;
        if (memo.has(key)) {
            return memo.get(key);
        }
        
        let result = false;
        
        if (diff > 0) {
            // s1 is ahead, need to consume from s2
            if (i2 < s2.length) {
                if (/[a-z]/.test(s2[i2])) {
                    result = dfs(i1, i2 + 1, diff - 1);
                } else {
                    const lengths = getPossibleLengths(s2, i2);
                    for (const len of lengths) {
                        let j = i2;
                        while (j < s2.length && /\d/.test(s2[j])) j++;
                        if (dfs(i1, j, diff - len)) {
                            result = true;
                            break;
                        }
                    }
                }
            }
        } else if (diff < 0) {
            // s2 is ahead, need to consume from s1
            if (i1 < s1.length) {
                if (/[a-z]/.test(s1[i1])) {
                    result = dfs(i1 + 1, i2, diff + 1);
                } else {
                    const lengths = getPossibleLengths(s1, i1);
                    for (const len of lengths) {
                        let j = i1;
                        while (j < s1.length && /\d/.test(s1[j])) j++;
                        if (dfs(j, i2, diff + len)) {
                            result = true;
                            break;
                        }
                    }
                }
            }
        } else {
            // diff === 0, both strings are at same position
            if (i1 < s1.length && i2 < s2.length) {
                const c1 = s1[i1];
                const c2 = s2[i2];
                
                if (/[a-z]/.test(c1) && /[a-z]/.test(c2)) {
                    if (c1 === c2) {
                        result = dfs(i1 + 1, i2 + 1, 0);
                    }
                } else if (/[a-z]/.test(c1) && /\d/.test(c2)) {
                    const lengths = getPossibleLengths(s2, i2);
                    for (const len of lengths) {
                        let j = i2;
                        while (j < s2.length && /\d/.test(s2[j])) j++;
                        if (dfs(i1, j, -len)) {
                            result = true;
                            break;
                        }
                    }
                } else if (/\d/.test(c1) && /[a-z]/.test(c2)) {
                    const lengths = getPossibleLengths(s1, i1);
                    for (const len of lengths) {
                        let j = i1;
                        while (j < s1.length && /\d/.test(s1[j])) j++;
                        if (dfs(j, i2, len)) {
                            result = true;
                            break;
                        }
                    }
                } else {
                    // both are digits
                    const lengths1 = getPossibleLengths(s1, i1);
                    const lengths2 = getPossibleLengths(s2, i2);
                    
                    for (const len1 of lengths1) {
                        let j1 = i1;
                        while (j1 < s1.length && /\d/.test(s1[j1])) j1++;
                        for (const len2 of lengths2) {
                            let j2 = i2;
                            while (j2 < s2.length && /\d/.test(s2[j2])) j2++;
                            if (dfs(j1, j2, len1 - len2)) {
                                result = true;
                                break;
                            }
                        }
                        if (result) break;
                    }
                }
            } else if (i1 === s1.length && i2 < s2.length && /\d/.test(s2[i2])) {
                const lengths = getPossibleLengths(s2, i2);
                for (const len of lengths) {
                    let j = i2;
                    while (j < s2.length && /\d/.test(s2[j])) j++;
                    if (dfs(i1, j, -len)) {
                        result = true;
                        break;
                    }
                }
            } else if (i2 === s2.length && i1 < s1.length && /\d/.test(s1[i1])) {
                const lengths = getPossibleLengths(s1, i1);
                for (const len of lengths) {
                    let j = i1;
                    while (j < s1.length && /\d/.test(s1[j])) j++;
                    if (dfs(j, i2, len)) {
                        result = true;
                        break;
                    }
                }
            }
        }
        
        memo.set(key, result);
        return result;
    }
    
    return dfs(0, 0, 0);
};

复杂度分析

复杂度类型大小
时间复杂度O(n × m × d)
空间复杂度O(n × m × d)

其中 n 和 m 分别是 s1 和 s2 的长度,d 是可能的位置差范围(通常为常数)。由于连续数字不超过3位,实际的状态空间相对有限。

相关题目