对称子串分割问题 | 豆包MarsCode AI刷题

62 阅读3分钟

问题描述

小R正在研究一种特殊的字符串分割问题。他需要判断一个给定的字符串是否可以分成若干个子串,每个子串都具有类似纸张折叠式的对称性。具体来说,一个子串具有类似纸张折叠的对称性,当且仅当它是一个长度为偶数的回文串。

现在,小R希望你帮助他判断字符串是否满足这个要求。如果可以分割,返回True;否则返回False。

问题解决

首先,很明显可以发现这是一个动态规划问题:

  1. 需要判断每个子串是否满足条件(偶数长度的回文串)
  2. 可以用dp数组记录每个位置是否可以成功分割
  3. 对于每个位置,尝试向前找可能的分割点

基于这个思路,我先写出了第一版代码:

def solution(s: str) -> bool:  
    def is_valid(sub: str) -> bool:  
        # 检查是否为偶数长度的回文串  
        if len(sub) % 2 != 0:  
            return False  
        return sub == sub[::-1]  
    
    n = len(s)  
    # dp[i]表示前i个字符是否可以有效分割  
    dp = [False] * (n + 1)  
    dp[0] = True  # 空串可以分割  
    
    for i in range(1, n + 1):  
        # 尝试不同的分割长度  
        for j in range(i):  
            # 如果前j个字符可以分割,且j到i的子串有效  
            if dp[j] and is_valid(s[j:i]):  
                dp[i] = True  
                break  
    
    return dp[n]

豆包给出的意见是:

image.png

基于这个意见,我重构优化了一遍答案:

def solution(s: str) -> bool:  
    n = len(s)  
    # 预计算所有有效的子串位置  
    valid_splits = set()  
    
    # 只需要检查偶数长度的子串  
    for i in range(n):  
        for length in range(2, n-i+1, 2):  # 步长为2确保偶数长度  
            sub = s[i:i+length]  
            if sub == sub[::-1]:  
                valid_splits.add((i, i+length))  
    
    # dp[i]表示前i个字符是否可以有效分割  
    dp = [False] * (n + 1)  
    dp[0] = True  
    
    for i in range(1, n + 1):  
        for j in range(0, i, 2):  # 步长为2因为要求偶数长度  
            if dp[j] and (j, i) in valid_splits:  
                dp[i] = True  
                break  
    
    return dp[n]

首先,采用预计算策略,将所有满足条件(偶数长度且回文)的子串位置存储在集合中,避免了在动态规划过程中的重复判断;

其次,在遍历过程中巧妙利用了题目中偶数长度的限制条件,通过设置步长为2来跳过不必要的奇数长度检查;

最后,在动态规划的状态转移过程中,利用预计算的结果和集合的O(1)查找特性,配合提前终止机制,大大减少了计算量。

学习心得

这道题让我对动态规划和字符串处理有了更深的理解。最初看到题目时,我只想到了简单的遍历和判断,但通过与豆包的交流,我学会了如何用动态规划来优化解决方案。

关键的优化点在于预处理和合理使用数据结构。预先计算所有有效的子串并用集合存储,避免了重复计算;利用题目中偶数长度的限制条件,可以减少遍历次数。这种优化思维不仅提高了代码效率,也让解决方案更加优雅。

这个问题也展示了如何将看似复杂的问题分解成可管理的子问题。通过动态规划,我们把"是否可以分割整个字符串"的问题转化为"是否可以分割到当前位置"的子问题。这种思维方式对解决其他复杂问题也很有帮助。