当前位置: 首页 > news >正文

动态规划的“递归之舞”:破解字符串的深层结构——扰乱字符串

哈喽,各位在算法的深渊中,享受着逻辑与结构之美的“思想家”们,我是前端小L。

我们的DP征程,已经穿越了线性的“一维走廊”和矩阵的“二维棋盘”。今天,我们将进入一个全新的、由递归定义出的“分形”世界。在这里,问题会不断地分裂、重组,形成一个看似无穷无尽的可能性迷宫。

这,就是“扰乱字符串”。它不是一个简单的序列匹配,而是对两个字符串结构同源性的终极拷问。这道题,将是我们从二维DP思维,迈向更高维度的结构化DP的毕业典礼。

力扣 87. 扰乱字符串

https://leetcode.cn/problems/scramble-string/

题目分析:递归的“DNA” 题目的核心,在于那个“扰乱”操作的定义:

  1. 将一个字符串 s 分割成两个非空子串 s1s2

  2. 有两种选择

    • 保持原样 s1 + s2

    • 交换顺序 s2 + s1

  3. s1s2 递归地进行这个过程。

这个定义,本身就充满了强烈的递归暗示。要判断 s1 是否是 s2 的一个扰乱字符串,我们就要看 s1 能否通过某种分割和交换,使其左右两部分,分别与 s2 相应的两部分,构成“扰乱”关系。

一个重要的“剪枝”前提: 如果 s1s2 互为扰乱,那么它们必然拥有完全相同的字符集和字符数量。换句话说,它们必须互为字母异位词 (Anagram)。这是一个强有力的必要条件,我们可以在递归的每一步,都先检查这个条件,如果连字符都对不上,那后续的结构分析就毫无意义了。

从递归到DP:三维状态的诞生

面对这种递归结构,最直观的解法就是写一个递归函数 isScramble(str1, str2)。但我们很快会发现,这种朴素递归会因为大量的重复子问题计算而超时。这正是DP登场的信号!

我们需要把递归的参数,转化为DP的状态维度。 isScramble 需要比较两个子串。如何唯一地确定一个子串?需要起始位置长度。 所以,要比较 s1 的子串和 s2 的子串,我们需要三个信息:s1 的起始位置 is2 的起始位置 j,以及两个子串共同的长度 len

1. DP状态定义 (三维DP的核心): dp[i][j][len] 表示:从 s1 的索引 i 开始、长度为 len 的子串,是否是 s2 从索引 j 开始、长度为 len 的子串的一个扰乱。这是一个布尔值

2. 状态转移的“分裂与重组”: 这是本题最精妙、也最核心的部分。为了计算 dp[i][j][len],我们需要模拟那个“分割”动作。我们可以尝试所有可能的分割点 k1 <= k < len),将长度为 len 的子串,分割成长度为 klen-k 的两部分。

对于每一种分割 k,我们都有两种“重组”的可能(对应原题的“交换”或“不交换”):

  • 可能性A:不交换

    • s1 的左半部分 s1[i...i+k-1]s2 的左半部分 s2[j...j+k-1] 互为扰乱。

    • 并且

    • s1 的右半部分 s1[i+k...i+len-1]s2 的右半部分 s2[j+k...j+len-1] 互为扰乱。

    • 翻译成DP: dp[i][j][k] && dp[i+k][j+k][len-k]

  • 可能性B:交换

    • s1 的左半部分 s1[i...i+k-1]s2右半部分 s2[j+len-k...j+len-1] 互为扰乱。

    • 并且

    • s1 的右半部分 s1[i+k...i+len-1]s2左半部分 s2[j...j+k-1] 互为扰乱。

    • 翻译成DP: dp[i][j+len-k][k] && dp[i+k][j][len-k]

只要在所有可能的分割点 k 中,存在任意一种可能性(A或B)为 true,那么 dp[i][j][len] 就为 true

3. Base Cases & 迭代顺序

  • Base Case: len = 1dp[i][j][1]true,当且仅当 s1[i] == s2[j]

  • 迭代顺序: 状态 len 的计算,依赖于比它更小的长度。所以,我们的最外层循环,必须是 len2n

代码实现 (三维DP)

class Solution {
public:bool isScramble(string s1, string s2) {int n = s1.length();if (n != s2.length()) return false;// dp[len][i][j]vector<vector<vector<bool>>> dp(n + 1, vector<vector<bool>>(n, vector<bool>(n, false)));// Base Case: len = 1for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {dp[1][i][j] = (s1[i] == s2[j]);}}// 迭代:len 从 2 到 nfor (int len = 2; len <= n; ++len) {for (int i = 0; i <= n - len; ++i) {for (int j = 0; j <= n - len; ++j) {// 检查 anagram 剪枝 (可选,但能优化)// ...// 枚举分割点 kfor (int k = 1; k < len; ++k) {// 可能性 A: 不交换bool caseA = dp[k][i][j] && dp[len - k][i + k][j + k];// 可能性 B: 交换bool caseB = dp[k][i][j + len - k] && dp[len - k][i + k][j];if (caseA || caseB) {dp[len][i][j] = true;break; // 只要找到一种可能,就可以确定为true}}}}}return dp[n][0][0];}
};

(注:也可以用记忆化搜索(自顶向下DP)来实现,逻辑是完全一样的,有时代码会更直观)

总结:DP与递归的共生关系

今天这道题,是动态规划与递归关系的一次最深刻的诠释。它告诉我们:

任何可以用“分治+最优子结构”思想解决的递归问题,都可以被转化为一个动态规划问题。

递归的参数,定义了DP状态的维度

递归的分解,定义了DP状态的转移方程

递归的终点,定义了DP的Base Cases

我们从线性DP的“一维行军”,到二维DP的“矩阵博弈”,再到今天三维DP的“递归之舞”,我们对动态规划的理解,已经从解决特定模式的问题,上升到了能够为任意具有最优子结构的递归问题,自主建模的高度。

咱们下期见~

http://www.dtcms.com/a/495036.html

相关文章:

  • 淮北市做网站最好的公司上海网站搭建平台公司
  • 网站域名设计推荐新华网海南频道
  • InternVL3.5多模态多大模型改进点及视觉分辨率路由模块技术浅尝
  • 人工智能基础知识笔记十八:Prompt Engineering
  • 深入理解Shell与反弹Shell:从原理到实战
  • LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
  • EvalScope模型压力测试实战
  • 极速网站建设服务商厦门网站建设网络推广
  • 新建网站如何调试网页设计公司的调研
  • 模拟oracle 索引平衡树叶子节点
  • Android 内存优化
  • Java JVM “垃圾回收(GC)”面试清单(含超通俗生活案例与深度理解)
  • Python快速落地的临床知识问答与检索项目(2025年9月教学实现部分)
  • 从0到1掌握Spring Boot自动配置:自定义配置实战指南
  • 索引设计速查:哪些字段该建索引?哪些不能建?
  • 自己的主机做网站服务器小树建站平台
  • 英集芯-IP5385开发调试总结
  • ProfiNet转EtherNet/IP工业PLC网关:打通仓储PLC与机器人通讯链路
  • Linux C/C++ 学习日记(27):KCP协议(三):代码结构分析与使用示例
  • 系统移植篇之uboot-5:DDR内存
  • 新开传奇网站刚开上海软件开发公司排名
  • C语言之可变参函数
  • Centos 7 环境下mysql的安装及配置
  • CentOS修改MySQL数据目录后重启失败的问题及解决方案
  • 南宁市优化网站宜昌网站建设
  • 医药网站 备案做哪个网站的直播好
  • 永磁同步电机电流环低“采样与基频比率”(S2F)性能影响与改进
  • Vue3 - defineExpose的使用
  • Go Web 编程快速入门 01 - 环境准备与第一个 Web 应用
  • 图像处理之腐蚀算法-收缩去噪