参加青训营这段时间体验了利用豆包MarsCode辅助刷题,这带来了不少好处,比如:
- 提供解题思路,豆包MarsCode可以给出一些解题思路和算法步骤,
- 及时发现问题,AI可以立即检查代码正确性,发现一些潜在的思路错误并提出改进意见
- 代码优化,能够自动分析时间空间复杂度,提供优化建议
- 多语言支持,可以根据需求转换成合适的语言。比如目前AI刷题平台仅支持python、java、c++提测,我就需要利用AI将JavaScript代码转换成其中的一种
下面以[字符串趋同最小代价问题](字符串趋同最小代价问题 - MarsCode)题目为例,感受一下AI带来的刷题效率提升。
问题描述
小U 和 小R 各自拥有一个长度相等的二进制字符串 A 和 B。现在,他们想要将这两个字符串修改成相同的字符串。每次修改可以选择以下两种操作:
- 交换同一个字符串中的任意两个字符,交换操作的成本为它们索引之差的绝对值
|i - j|。 - 对某个字符进行取反操作,取反的成本为 2。
小U 和 小R 想知道,将字符串 A 和 B 修改为相同字符串的最小总成本是多少?
测试样例
样例1:
输入:
str1 = "10001",str2 = "10000"
输出:2
样例2:
输入:
str1 = "100100",str2 = "100001"
输出:2
样例3:
输入:
str1 = "1010",str2 = "0111"
输出:3
样例4:
输入:
str1 = "1100",str2 = "0011"
输出:4
题目分析
我最初的想法是由于交换的成本是索引之差,那么交换好两个字符的成本最低为1。而对某个字符进行取反操作,取反成本为2,那么让两个字符正确的成本是4,也就是说当两个字符索引之差小于等于4且能交换的时候,就选择交换,否则选择让两个字符都取反。根据这个思路编写了如下代码:
function solution(str1, str2) {
// 找到不同的位
let diss = []
for(let i=0; i<str1.length; i++){
if(str1[i] !== str2[i]){
diss.push(i)
}
}
// 如果相邻且不同,则交换
// 剩下的取反
let res = 0;
let flag = false; // 用于标记最后一位是否取反
if(diss.length < 2) return diss.length*2;
for(let i=0; i<diss.length-1; i++){
if(str1[diss[i]] !== str1[diss[i+1]]){
res += 1
flag = true;
i++;
}else{
res += 2
flag = false
}
}
if(!flag){
res += 2;
}
return res;
}
但很遗憾的是,测试用例只通过了一个,AI给出了如下建议:
对于交换逻辑,AI提出我只考虑了相邻的不同位进行交换,检查代码发现确实如此,与我最初的想法有差异,交换条件应该改为交换成本小于等于4。为了便于找到不同位,计算交换成本,将0变1和1变0的索引分别存储到两个数组中。修改后代码如下:
function solution(str1, str2) {
// 找到不同的位
let diss0 = []; // 需要从0变1的索引
let diss1 = []; // 需要从1变0的索引
for (let i = 0; i < str1.length; i++) {
if (str1[i] !== str2[i]) {
if (str1[i] === '0') {
diss0.push(i);
} else {
diss1.push(i);
}
}
}
// 如果不同的位数为0或1,直接返回取反的成本
if (diss0.length + diss1.length <= 1) return (diss0.length + diss1.length) * 2;
// 计算交换和取反的最小成本
let res = 0;
let i = 0, j = 0;
while (i < diss0.length && j < diss1.length) {
// 如果可以交换且成本小于等于4
if (Math.abs(diss0[i] - diss1[j]) <= 4) {
res += Math.abs(diss0[i] - diss1[j]); // 交换成本
i++;
j++;
} else {
// 否则取反
res += 2; // 取反成本
if (diss0[i] < diss1[j]) {
i++;
} else {
j++;
}
}
}
// 处理剩余的位
while (i < diss0.length) {
res += 2; // 取反成本
i++;
}
while (j < diss1.length) {
res += 2; // 取反成本
j++;
}
return res;
}
结果提测遇到一个测试用例不通过:
输入:
str1 = "1101000000001101",str2 = "0010110111100010"
输出:17
调试代码发现,交换的判定条件有一定的局限性,比如当索引为1,3的字符需要从0变为1,索引为5,7的字符需要从1变为0。根据我们的代码逻辑应该交换索引为1和5的字符,交换索引为3和7的字符,代价为8。但实际上可以让索引为1和7的字符取反,交换索引为3和5的字符,这样代价最终为6。因此我们的实现思路存在问题,针对这种情况可以考虑使用动态规划思想解决。告诉AI我们的想法就能自动分析并解决问题。
甚至可以提问AI“可以进一步优化吗”,就能得到优化后的结果
AI生成代码如下:
function solution(str1, str2) {
// 找到不同的位
let diff_id = [];
for (let i = 0; i < str1.length; i++) {
if (str1[i] !== str2[i]) {
diff_id.push(i);
}
}
let m = diff_id.length;
if (m === 0) {
return 0;
}
// 初始化dp数组
let dp = new Array(m + 1).fill(0);
dp[1] = 2; // 一个不同的位取反的成本
// 动态规划状态转移
for (let i = 2; i <= m; i++) {
dp[i] = dp[i - 1] + 2; // 取反操作
if (str1[diff_id[i - 2]] !== str1[diff_id[i - 1]]) {
dp[i] = Math.min(dp[i], dp[i - 2] + Math.abs(diff_id[i - 1] - diff_id[i - 2])); // 交换操作
}
if (i >= 4 && str1[diff_id[i - 3]] !== str1[diff_id[i - 1]] && str1[diff_id[i - 4]] !== str1[diff_id[i - 2]]) {
dp[i] = Math.min(dp[i], dp[i - 4] + Math.abs(diff_id[i - 1] - diff_id[i - 3]) + Math.abs(diff_id[i - 2] - diff_id[i - 4])); // 多步交换操作
}
}
return dp[m];
}
然后利用AI改成python并提交,用例全部通过