Hard
题目描述
给定两个长度相等的字符串 word1 和 word2。你需要将 word1 转换为 word2。
为此,将 word1 分割成一个或多个连续子串。对于每个子串 substr,你可以执行以下操作:
- 替换:将
substr中任意一个位置的字符替换为另一个小写英文字母。 - 交换:交换
substr中的任意两个字符。 - 反转子串:反转
substr。
每个操作计为一步,每个子串的每个字符在每种操作类型中最多只能使用一次(即,任何单个索引在替换、交换或反转操作中最多只能参与一次)。
返回将 word1 转换为 word2 所需的最小操作数。
示例 1:
输入:word1 = "abcdf", word2 = "dacbe"
输出:4
示例 2:
输入:word1 = "abceded", word2 = "baecfef"
输出:4
示例 3:
输入:word1 = "abcdef", word2 = "fedabc"
输出:2
约束条件:
1 <= word1.length == word2.length <= 100word1和word2仅包含小写英文字母。
解题思路
这是一个复杂的动态规划问题。关键思路如下:
核心思想:对于字符串的每个子串,我们需要计算将其转换为目标子串的最小操作数。由于可以对子串进行反转操作,所以每个子串实际上有两种形态:原始形态和反转后的形态。
动态规划状态:
dp[i][j]表示将word1[0...i-1]转换为word2[0...j-1]的最小操作数- 对于每个子串,我们需要考虑是否反转它,然后计算最优的操作组合
子串操作优化: 对于一个子串,最优策略是:
- 首先使用交换操作:如果
word1[i] == word2[j]且word1[j] == word2[i],可以通过一次交换同时修复两个位置 - 然后对剩余不匹配的字符使用替换操作
算法步骤:
- 使用动态规划枚举所有可能的子串分割方式
- 对每个子串,计算原始状态和反转状态的最小操作数
- 反转操作本身需要 1 步,所以反转状态需要额外加 1
- 选择最优方案
时间复杂度为 O(n³),空间复杂度为 O(n²)。
代码实现
class Solution {
public:
int minOperations(string word1, string word2) {
int n = word1.length();
vector<vector<int>> dp(n + 1, vector<int>(n + 1, INT_MAX));
dp[0][0] = 0;
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (dp[i][j] == INT_MAX) continue;
// Try all possible next substrings
for (int len = 1; i + len <= n && j + len <= n; len++) {
string s1 = word1.substr(i, len);
string s2 = word2.substr(j, len);
// Calculate cost without reverse
int cost1 = getCost(s1, s2);
dp[i + len][j + len] = min(dp[i + len][j + len], dp[i][j] + cost1);
// Calculate cost with reverse (add 1 for reverse operation)
reverse(s1.begin(), s1.end());
int cost2 = getCost(s1, s2) + 1;
dp[i + len][j + len] = min(dp[i + len][j + len], dp[i][j] + cost2);
}
}
}
return dp[n][n];
}
private:
int getCost(string s1, string s2) {
int n = s1.length();
vector<bool> used(n, false);
int operations = 0;
// First, perform swaps
for (int i = 0; i < n; i++) {
if (used[i] || s1[i] == s2[i]) continue;
for (int j = i + 1; j < n; j++) {
if (used[j] || s1[j] == s2[j]) continue;
if (s1[i] == s2[j] && s1[j] == s2[i]) {
used[i] = used[j] = true;
operations++;
break;
}
}
}
// Then, perform replacements
for (int i = 0; i < n; i++) {
if (!used[i] && s1[i] != s2[i]) {
operations++;
}
}
return operations;
}
};
class Solution:
def minOperations(self, word1: str, word2: str) -> int:
n = len(word1)
dp = [[float('inf')] * (n + 1) for _ in range(n + 1)]
dp[0][0] = 0
for i in range(n + 1):
for j in range(n + 1):
if dp[i][j] == float('inf'):
continue
# Try all possible next substrings
for length in range(1, n - max(i, j) + 1):
if i + length > n or j + length > n:
break
s1 = word1[i:i + length]
s2 = word2[j:j + length]
# Calculate cost without reverse
cost1 = self.getCost(s1, s2)
dp[i + length][j + length] = min(dp[i + length][j + length],
dp[i][j] + cost1)
# Calculate cost with reverse (add 1 for reverse operation)
s1_rev = s1[::-1]
cost2 = self.getCost(s1_rev, s2) + 1
dp[i + length][j + length] = min(dp[i + length][j + length],
dp[i][j] + cost2)
return dp[n][n]
def getCost(self, s1: str, s2: str) -> int:
n = len(s1)
used = [False] * n
operations = 0
# First, perform swaps
for i in range(n):
if used[i] or s1[i] == s2[i]:
continue
for j in range(i + 1, n):
if used[j] or s1[j] == s2[j]:
continue
if s1[i] == s2[j] and s1[j] == s2[i]:
used[i] = used[j] = True
operations += 1
break
# Then, perform replacements
for i in range(n):
if not used[i] and s1[i] != s2[i]:
operations += 1
return operations
public class Solution {
public int MinOperations(string word1, string word2) {
int n = word1.Length;
int[,] dp = new int[n + 1, n + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
dp[i, j] = int.MaxValue;
}
}
dp[0, 0] = 0;
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (dp[i, j] == int.MaxValue) continue;
for (int len = 1; i + len <= n && j + len <= n; len++) {
string s1 = word1.Substring(i, len);
string s2 = word2.Substring(j, len);
int cost1 = GetCost(s1, s2);
dp[i + len, j + len] = Math.Min(dp[i + len, j + len], dp[i, j] + cost1);
char[] arr = s1.ToCharArray();
Array.Reverse(arr);
string s1Rev = new string(arr);
int cost2 = GetCost(s1Rev, s2) + 1;
dp[i + len, j + len] = Math.Min(dp[i + len, j + len], dp[i, j] + cost2);
}
}
}
return dp[n, n];
}
private int GetCost(string s1, string s2) {
int n = s1.Length;
bool[] used = new bool[n];
int operations = 0;
for (int i = 0; i < n; i++) {
if (used[i] || s1[i] == s2[i]) continue;
for (int j = i + 1; j < n; j++) {
if (used[j] || s1[j] == s2[j]) continue;
if (s1[i] == s2[j] && s1[j] == s2[i]) {
used[i] = used[j] = true;
operations++;
break;
}
}
}
for (int i = 0; i < n; i++) {
if (!used[i] && s1[i] != s2[i]) {
operations++;
}
}
return operations;
}
}
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minOperations = function(word1, word2) {
const n = word1.length;
const memo = new Map();
function dp(i) {
if (i === n) return 0;
if (memo.has(i)) return memo.get(i);
let minOps = Infinity;
for (let j = i; j < n; j++) {
const substr1 = word1.slice(i, j + 1);
const substr2 = word2.slice(i, j + 1);
const ops = getMinOps(substr1, substr2);
minOps = Math.min(minOps, ops + dp(j + 1));
}
memo.set(i, minOps);
return minOps;
}
function getMinOps(s1, s2) {
const len = s1.length;
let minOps = Infinity;
// Try all possible combinations of operations
for (let reverse = 0; reverse < 2; reverse++) {
let curr = s1;
let ops = 0;
if (reverse) {
curr = curr.split('').reverse().join('');
ops++;
}
// Count character frequencies
const freq1 = {};
const freq2 = {};
for (let i = 0; i < len; i++) {
freq1[curr[i]] = (freq1[curr[i]] || 0) + 1;
freq2[s2[i]] = (freq2[s2[i]] || 0) + 1;
}
// Calculate swaps needed
let swaps = 0;
const chars1 = Object.keys(freq1);
const chars2 = Object.keys(freq2);
for (const char of chars1) {
if (freq2[char]) {
swaps += Math.min(freq1[char], freq2[char]);
}
}
// Remaining characters need replacement
const totalMatched = swaps;
const replacements = len - totalMatched;
// Swaps operations needed (each swap fixes 2 positions if both need swapping)
const swapOps = Math.floor(swaps / 2);
const remainingAfterSwaps = swaps % 2;
ops += swapOps + remainingAfterSwaps + replacements;
minOps = Math.min(minOps, ops);
}
return minOps;
}
return dp(0);
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n³) |
| 空间复杂度 | O(n²) |
相关题目
- . Edit Distance (Medium)