题目传送门
题意简述
给定一个由字符 'U' 和 'C' 组成的字符串 S,以及一个编辑距离限制 m。编辑距离是指将字符串 S 转化为其他字符串时所需的最少编辑操作次数。允许的编辑操作包括插入、删除或替换单个字符。目标是在编辑距离不超过 m 的条件下,修改字符串 S 以包含尽可能多的 "UCC" 子串。例如,对于字符串 "UCUUCCCCC" 和编辑距离限制 m = 3,可以通过编辑字符串生成最多包含 3 个 "UCC" 子串的序列。
解题思路
-
初步匹配:首先在字符串中寻找已经存在的 "UCC" 子串,记录这些子串的位置并标记为已访问。这一步的目的是在不进行任何编辑操作的情况下,尽可能多地找到现有的 "UCC" 子串。
-
编辑操作策略:
-
替换操作:在未被标记的位置,尝试通过替换操作将 "UC" 或 "CC" 转换为 "UCC"。替换操作的代价较低(1次操作),因此优先考虑。
-
插入操作:在未被标记的位置,尝试通过插入操作形成新的 "UCC" 子串。插入操作需要 2 次操作,因此在替换操作无法满足条件时考虑。
-
删除操作:在某些情况下,删除多余的字符可能有助于形成更多的 "UCC" 子串,但通常不如替换和插入操作有效。
-
贪心策略:采用贪心算法,优先进行代价较低的编辑操作(替换),然后考虑插入操作,最后是删除操作。通过这种策略,尽可能多地形成 "UCC" 子串。
4. 处理剩余编辑距离:如果在上述操作后编辑距离还有剩余,计算可以通过插入操作形成的额外 "UCC" 子串数量。每 3 次插入操作可以形成一个新的 "UCC" 子串。
代码实现
import java.util.Arrays;
public class Main {
public static int solution(int m, String s) {
int n = s.length();
boolean[] vis = new boolean[n];
Arrays.fill(vis, false);
int ret = 0;
int pos = 0;
// 初步匹配 "UCC" 子串
while (m >= 0 && pos + 2 < n) {
if (s.charAt(pos) == 'U' && s.charAt(pos + 1) == 'C' && s.charAt(pos + 2) == 'C') {
ret++;
vis[pos] = vis[pos + 1] = vis[pos + 2] = true;
pos += 3;
} else {
pos++;
}
}
// 尝试通过替换操作形成 "UCC"
pos = 0;
while (m > 0 && pos + 1 < n) {
if (vis[pos] || vis[pos + 1]) {
pos++;
continue;
}
if (s.charAt(pos) == 'U' && s.charAt(pos + 1) == 'C') {
ret++;
m--;
vis[pos] = vis[pos + 1] = true;
pos += 2;
} else {
pos++;
}
}
// 尝试通过替换操作形成 "CC"
pos = n - 1;
while (m > 0 && pos - 1 >= 0) {
if (vis[pos] || vis[pos - 1]) {
pos--;
continue;
}
if (s.charAt(pos) == 'C' && s.charAt(pos - 1) == 'C') {
ret++;
m--;
vis[pos] = vis[pos - 1] = true;
pos -= 2;
} else {
pos--;
}
}
// 处理剩余的编辑距离
pos = 0;
while (m >= 2 && pos < n) {
if (vis[pos]) {
pos++;
} else {
m -= 2;
ret++;
pos++;
}
}
// 计算剩余编辑距离可以形成的 "UCC" 子串
ret += m / 3;
return ret;
}
public static void main(String[] args) {
System.out.println(solution(3, "UCUUCCCCC") == 3);
System.out.println(solution(6, "U") == 2);
System.out.println(solution(2, "UCCUUU") == 2);
}
}
时间复杂度分析
该算法的时间复杂度为 𝑂(𝑛),其中 𝑛 是字符串的长度。主要的时间消耗在于遍历字符串以寻找和修改子串。由于每个字符最多被访问一次,因此整体复杂度是线性的。
用到的算法和数据结构
-
贪心算法:通过优先进行代价较低的编辑操作(替换)来最大化 "UCC" 子串的数量。贪心算法在每一步选择中都做出局部最优的选择,以期望达到全局最优。
-
布尔数组:用于标记已经访问和处理过的字符位置,避免重复操作。布尔数组的使用使得我们可以快速判断某个位置是否已经被处理过,从而提高算法效率。
详细分析
在初步匹配阶段,我们通过遍历字符串,直接找到所有已经存在的 "UCC" 子串,并将这些位置标记为已访问。这一步不需要消耗编辑距离,因为我们只是识别现有的子串。在编辑操作阶段,我们首先尝试通过替换操作来形成新的 "UCC" 子串。替换操作的代价是 1,因此在编辑距离允许的情况下,优先进行替换操作。接下来,我们考虑插入操作,插入操作的代价是 2,因此在替换操作无法满足条件时,我们尝试通过插入操作来形成新的子串。最后,我们处理剩余的编辑距离。如果在上述操作后编辑距离还有剩余,我们计算可以通过插入操作形成的额外 "UCC" 子串数量。每 3 次插入操作可以形成一个新的 "UCC" 子串,因此我们将剩余的编辑距离除以 3,得到可以形成的额外子串数量。通过这种贪心策略,我们能够在给定的编辑距离限制下,最大化字符串中 "UCC" 子串的数量。