写在前面
第一次参加字节青训营,也是头一次遇到官方许可使用AI参与算法刷题的活动,使用字节的在线IDE环境MarsCode写代码,自动补全和语法高亮部分还是很丝滑的,也贴心在侧边栏加上了MarsCode AI辅助编程,可谓是敲代码效率翻倍。
不久前我们学校刚举办过Datawhale和字节MarsCode联合发起的AI编程高校行,其中在线IDE也是使用的MarsCode,当时有很多同学都没有顺利地调出代码,跟我抱怨说AI不聪明一类的话,今天我就使用经典的提示词工程,来证明一下MarsCode解题的潜力。
话不多说,直接上道难题试试。
字典序最小的01字符串(难)
问题描述
小U拥有一个由0和1组成的字符串,她可以进行最多k次操作,每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。
例如,小U当前有一个字符串 01010,她最多可以进行 2 次相邻字符交换操作。通过这些操作,她可以将字符串调整为 00101,这是可以通过不超过2次操作得到的字典序最小的字符串。
现在,小U想知道,经过最多k次操作后,能够得到的字典序最小的字符串是什么。
测试样例
样例1:
输入:
n = 5, k = 2, s = "01010"
输出:'00101'
样例2:
输入:
n = 7, k = 3, s = "1101001"
输出:'0110101'
样例3:
输入:
n = 4, k = 1, s = "1001"
输出:'0101'
问题分析
吴恩达老师在提示词工程中提出经典的两大步骤:
- 给出清晰的指示
- 给AI充足时间思考
那么下面我们第一步是给AI清晰明确的指示,既然是解题,那么我们首先让它分析问题并给出解题思路。
Q: 请你分析这道算法题是什么类型的,基于这种类型的算法题,给出常见的解题思路
A:
这道算法题属于贪心算法类型。贪心算法通常用于在每一步选择中都采取在当前状态下最优的选择,以期望通过一系列局部最优的选择达到全局最优。解题思路
理解问题:
- 题目要求通过最多
k次相邻字符交换操作,使得字符串的字典序最小。- 字典序最小意味着字符串中的字符应该尽可能地按照从小到大的顺序排列。
贪心策略:
- 从字符串的第一个字符开始,尝试将当前字符移动到尽可能靠前的位置,以减少字符串的字典序。
- 每次选择当前字符能够移动到的最靠前的位置,并且确保移动的步数不超过
k。具体步骤:
- 遍历字符串中的每一个字符。
- 对于当前字符,计算它能够移动到的最靠前的位置,同时确保移动的步数不超过剩余的
k次操作。- 如果当前字符可以移动到更靠前的位置,执行交换操作,并更新
k的值。- 继续遍历下一个字符,直到遍历完整个字符串。
优化考虑:
- 在实际实现中,可以使用一个优先队列(最小堆)来动态地选择当前最小的字符,并将其移动到合适的位置。
- 这样可以避免每次都从头开始查找最小的字符,从而提高效率。
通过上述步骤,可以逐步构建出字典序最小的字符串。
可以看出,AI生成的解题逻辑还是基本没有问题的,它能够正确推断出题目类型并给出解题步骤,并且在解释过程中一步步推断,对应提示工程中的思维链(Chain of Thought),详细的推断过程利于降低AI出错概率,这也是现在主流AI模型在回答问题时都会尝试生成一长串内容的原因之一。
接下来代码生成才是重头戏。
生成代码
Q:那么,请你根据上述解题步骤,给出一个C++题解,要有详细的注释,并且写完再对生成的代码分析时间复杂度
A:
好的,下面是一个基于上述解题思路的C++题解,包含详细的注释.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // for swap
using namespace std;
string solution(int n, int k, string s) {
// 遍历字符串中的每一个字符
for (int i = 0; i < n; ++i) {
// 找到当前字符能够移动到的最靠前的位置
int min_pos = i;
for (int j = i + 1; j <= i + k && j < n; ++j) {
if (s[j] < s[min_pos]) {
min_pos = j;
}
}
// 如果找到了更靠前的位置,执行交换操作
if (min_pos != i) {
// 计算需要交换的步数
int steps = min_pos - i;
// 将字符移动到目标位置
for (int j = min_pos; j > i; --j) {
swap(s[j], s[j - 1]);
}
// 更新剩余的交换次数
k -= steps;
}
}
return s;
}
int main() {
cout << (solution(5, 2, "01010") == "00101") << endl;
cout << (solution(7, 3, "1101001") == "0110101") << endl;
cout << (solution(4, 1, "1001") == "0101") << endl;
return 0;
}
代码分析
遍历字符串:
外层循环遍历字符串中的每一个字符,时间复杂度为
O(n)。
寻找最靠前的位置:
- 内层循环从当前字符的下一个位置开始,寻找能够移动到的最靠前的位置。
- 内层循环的最坏情况是遍历从
i+1到i+k的所有字符,时间复杂度为O(k)。执行交换操作:
- 如果找到了更靠前的位置,执行交换操作,将字符移动到目标位置。
- 交换操作的时间复杂度为
O(steps),其中steps是交换的步数,最坏情况下为k。时间复杂度分析
- 外层循环遍历字符串的时间复杂度为
O(n)。- 内层循环在最坏情况下遍历
k个字符,时间复杂度为O(k)。- 因此,总的时间复杂度为
O(n * k)。空间复杂度分析
- 代码中没有使用额外的数据结构,只使用了常数级别的额外空间。
- 因此,空间复杂度为
O(1)。通过上述代码和分析,我们可以看到,该算法在时间复杂度上为
O(n * k),在空间复杂度上为O(1),能够有效地解决题目要求的问题。
提交验证
进入到喜闻乐见的Ctrl+C/V环境,复制粘贴后提交代码稳过,可以看出在提示工程的推动下,即使是面对难题MarsCode依然有较好的推理能力。
感悟
虽然这次小试牛刀随便选了一道较水的难题,但是AI所体现出的解题效率和码字速度也都已经远超一般人,给我带来了不小的震撼,现在平时学习生活中好多地方都用到了AI,更可怕的是即使在计算机的专业课程上也有不少人对AI产品产生了重度依赖,笔者平时也是禁用了Github Copilot,OpenAI ChatGPT等一众AI工具修改程序(仅用来写文案做敷衍了事的文字工作),不想让TAB键打断思维,更不想被它替代。这次青训营也算是我和AI的首次长时间的“合作”,我也在努力学习提示工程,让AI成为高效率工具。