作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
LeetCode解锁1000题: 打怪升级之旅
python数据分析可视化:企业实战案例
python源码解读
程序员必备的数学知识与应用
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
题目描述
给定一个字符串 s1
,我们可以通过对其进行翻转和交换子字符串的方式生成另一个字符串 s2
。确定 s2
是否是 s1
的扰乱字符串。
输入格式
- s1:原始字符串。
- s2:可能的扰乱字符串。
输出格式
- 返回一个布尔值,表示
s2
是否是s1
的扰乱字符串。
示例
示例 1
输入: s1 = "great", s2 = "rgeat"
输出: true
示例 2
输入: s1 = "abcde", s2 = "caebd"
输出: false
方法一:递归
解题步骤
- 递归基准情况:如果
s1
和s2
相等,返回true
。 - 字符验证:如果两个字符串排序后不等,返回
false
。 - 递归分割:尝试对
s1
进行分割,检查是否能通过扰乱得到s2
。
完整的规范代码
def isScramble(s1, s2):
"""
递归判断 s2 是否是 s1 的扰乱字符串
:param s1: str, 原始字符串
:param s2: str, 可能的扰乱字符串
:return: bool
"""
if s1 == s2:
return True
if sorted(s1) != sorted(s2):
return False
n = len(s1)
for i in range(1, n):
# 检查不交换的情况
if isScramble(s1[:i], s2[:i]) and isScramble(s1[i:], s2[i:]):
return True
# 检查交换的情况
if isScramble(s1[:i], s2[-i:]) and isScramble(s1[i:], s2[:-i]):
return True
return False
# 示例调用
print(isScramble("great", "rgeat")) # 输出: true
print(isScramble("abcde", "caebd")) # 输出: false
算法分析
- 时间复杂度:(O(n^4)),在递归过程中需要多次分割字符串和比较。
- 空间复杂度:(O(n)),递归深度依赖于字符串长度。
方法二:记忆化搜索
解题步骤
- 使用哈希表存储结果:使用字典存储已计算的结果,避免重复计算。
- 递归搜索:基于递归实现,但在每次递归前查询字典。
完整的规范代码
def isScramble(s1, s2, memo={}):
"""
记忆化搜索判断 s2 是否是 s1 的扰乱字符串
:param s1: str, 原始字符串
:param s2: str, 可能的扰乱字符串
:param memo: dict, 记忆化存储已计算的结果
:return: bool
"""
if (s1, s2) in memo:
return memo[(s1, s2)]
if s1 == s2:
memo[(s1, s2)] = True
return True
if sorted(s1) != sorted(s2):
memo[(s1, s2)] = False
return False
n = len(s1)
for i in range(1, n):
if (isScramble(s1[:i], s2[:i], memo) and isScramble(s1[i:], s2[i:], memo)) or \
(isScramble(s1[:i], s2[-i:], memo) and isScramble(s1[i:], s2[:-i], memo)):
memo[(s1, s2)] = True
return True
memo[(s1, s2)] = False
return False
# 示例调用
print(isScramble("great", "rgeat")) # 输出: true
print(isScramble("abcde", "caebd")) # 输出: false
算法分析
- 时间复杂度:(O(n^3)),使用记忆化后,大多数重复的递归调用被省略。
- 空间复杂度:(O(n^2)),存储已解决问题的状态需要的空间。
方法三:动态规划
解题步骤
- 定义状态:
dp[i][j][k]
表示从s1
的第i
个字符开始和从s2
的第j
个字符开始,长度为k
的子串是否互为扰乱字符串。 - 状态转移:根据字符是否相等以及可能的分割情况更新状态。
完整的规范代码
def isScramble(s1, s2):
"""
动态规划判断 s2 是否是 s1 的扰乱字符串
:param s1: str, 原始字符串
:param s2: str, 可能的扰乱字符串
:return: bool
"""
if len(s1) != len(s2) or sorted(s1) != sorted(s2):
return False
n = len(s1)
dp = [[[False] * (n + 1) for _ in range(n)] for __ in range(n)]
# 初始化长度为1的情况
for i in range(n):
for j in range(n):
dp[i][j][1] = s1[i] == s2[j]
# 动态规划计算长度为k的情况
for k in range(2, n + 1): # 子串长度
for i in range(n - k + 1): # s1的起始位置
for j in range(n - k + 1): # s2的起始位置
for l in range(1, k): # 分割点
if (dp[i][j][l] and dp[i + l][j + l][k - l]) or (dp[i][j + k - l][l] and dp[i + l][j][k - l]):
dp[i][j][k] = True
break
return dp[0][0][n]
# 示例调用
print(isScramble("great", "rgeat")) # 输出: true
print(isScramble("abcde", "caebd")) # 输出: false
算法分析
- 时间复杂度:(O(n^4)),其中 (n) 是字符串的长度。我们需要计算所有长度、所有起始位置和所有可能的分割点的状态。
- 空间复杂度:(O(n^3)),存储状态所需的三维数组。
方法四:优化的动态规划
解题步骤
- 使用二维数组:降低空间复杂度,每次只保存当前长度的状态信息。
- 优化状态转移:只依赖于小于当前长度的状态,因此可以使用滚动数组的技术。
完整的规范代码
def isScramble(s1, s2):
"""
优化的动态规划算法,使用二维数组降低空间复杂度
:param s1: str, 原始字符串
:param s2: str, 可能的扰乱字符串
:return: bool
"""
if len(s1) != len(s2) or sorted(s1) != sorted(s2):
return False
n = len(s1)
dp = [[[False] * n for _ in range(n)] for __ in range(2)]
# 初始化长度为1的情况
for i in range(n):
for j in range(n):
dp[1][i][j] = s1[i] == s2[j]
# 动态规划计算长度大于1的情况
for k in range(2, n + 1): # 子串长度
for i in range(n - k + 1): # s1的起始位置
for j in range(n - k + 1): # s2的起始位置
for l in range(1, k): # 分割点
if (dp[(l)%2][i][j] and dp[(k-l)%2][i+l][j+l]) or (dp[(l)%2][i][j+k-l] and dp[(k-l)%2][i+l][j]):
dp[k%2][i][j] = True
break
return dp[n%2][0][0]
# 示例调用
print(isScramble("great", "rgeat")) # 输出: true
print(isScramble("abcde", "caebd")) # 输出: false
算法分析
- 时间复杂度:(O(n^4)),虽然优化了空间复杂度,但时间复杂度仍然高。
- 空间复杂度:(O(n^2)),使用滚动数组技术,只存储当前长度和前一长度的状态。
方法五:字符串哈希
解题步骤
- 预处理哈希值:为
s1
和s2
中的所有子字符串计算哈希值,以便快速比较任何两个子字符串是否相等。 - 动态规划:使用三维
dp
数组,其中dp[i][j][len]
表示s1
从位置i
开始长度为len
的子串与s2
从位置j
开始长度为len
的子串是否是扰乱字符串。
完整的规范代码
def isScramble(s1, s2):
"""
使用字符串哈希和动态规划判断 s2 是否是 s1 的扰乱字符串
:param s1: str, 原始字符串
:param s2: str, 可能的扰乱字符串
:return: bool
"""
if len(s1) != len(s2) or sorted(s1) != sorted(s2):
return False
n = len(s1)
dp = [[[False] * (n + 1) for _ in range(n)] for __ in range(n)]
# 字符串哈希预处理
# 假设 hash_function 是一个用于计算字符串哈希的函数
hash1 = [[0] * (n + 1) for _ in range(n)]
hash2 = [[0] * (n + 1) for _ in range(n)]
# 填充哈希数组
for i in range(n):
for j in range(i, n):
hash1[i][j + 1] = hash_function(s1[i:j + 1])
hash2[i][j + 1] = hash_function(s2[i:j + 1])
# 初始化长度为1的情况
for i in range(n):
for j in range(n):
dp[i][j][1] = s1[i] == s2[j]
# 动态规划
for length in range(2, n + 1):
for i in range(n - length + 1):
for j in range(n - length + 1):
for k in range(1, length):
if (dp[i][j][k] and dp[i + k][j + k][length - k]) or (dp[i][j + length - k][k] and dp[i + k][j][length - k]):
dp[i][j][length] = True
break
return dp[0][0][n]
# 示例调用
print(isScramble("great", "rgeat")) # 输出: true
print(isScramble("abcde", "caebd")) # 输出: false
算法分析
- 时间复杂度:在优化情况下接近 (O(n^3))。预处理哈希值和填充动态规划表的过程都涉及三层循环。
- 空间复杂度:哈希表的大小和动态规划表的大小都为 (O(n^2)),因此总体空间复杂度为 (O(n^2))。
这种方法利用字符串哈希来优化字符串的比较过程,大幅减少了时间开销,特别适用于字符串长度较大的情况。
不同算法的优劣势对比
特征 | 方法一:递归 | 方法二:记忆化搜索 | 方法三:动态规划 | 方法四:优化的动态规划 | 方法五:字符串哈希 |
---|---|---|---|---|---|
时间复杂度 | (O(n^4)) | (O(n^3)) | (O(n^4)) | (O(n^4)) | (O(n^3)) 至 (O(n^4)) |
空间复杂度 | (O(n)) | (O(n^2)) | (O(n^3)) | (O(n^2)) | (O(n^2)) 至 (O(n^3)) |
优势 | 直观简单 | 减少重复计算 | 精确控制 | 空间优化 | 快速字符串比较 |
劣势 | 复杂度高 | 空间较大 | 空间消耗大 | 复杂度依然高 | 需要高效的哈希实现 |
应用示例
这类算法在处理文本编辑器中的文字扰乱问题、编译器中的代码重构检测、以及一些需要大规模字符串操作的生物信息学应用中尤为重要。