题目分析
题目要求我们在给定的字符串中,通过不超过 k 次相邻字符交换操作,将字符串调整为字典序最小的字符串。对于一个由 '0' 和 '1' 组成的字符串,字典序最小的字符串是尽可能多地将 '0' 移动到字符串的前面,尽可能少地让 '1' 出现在前面。因此,问题的核心是在限定操作次数内,通过交换相邻字符的方式,使得字符串的字典序最小。
解题思路
字典序的定义:字典序最小的字符串是包含尽可能多的 '0' 在字符串的最前面,剩下的 '1' 尽可能集中在后面。因此,我们的目标就是尽可能地把 '0' 向前移动,直到操作次数用完。
交换策略:由于每次操作只能交换相邻的字符,最有效的方式是从字符串的左端开始,遇到 '1' 时,尽量将它和后面的 '0' 交换,直到用完允许的操作次数。通过这种方式,我们可以把 '0' 尽量移到最前面。
贪心算法的应用:我们可以使用贪心策略来选择操作。在每次遍历字符串时,我们寻找从当前位置开始,能够移动的最近的 '0',并将它移动到当前位置。每次移动都会消耗一个操作次数,直到操作次数用尽。
操作的限制:每次交换只能交换相邻的字符,因此我们需要计算每个 '0' 能够被移动到的位置,同时考虑剩余的操作次数。在移动 '0' 时,如果剩余的操作次数不足以完成当前交换,就停止移动。
详细步骤
步骤1:遍历字符串 从左到右遍历字符串,对于每个 '1',我们寻找距离它最近的 '0',并进行交换。如果找到的 '0' 能够在剩余的操作次数内被移动到当前位置,就交换它们,并更新剩余操作次数。
步骤2:进行交换操作 对于每个遇到的 '1',将最接近的 '0' 移动到它前面。每次交换操作消耗一次操作次数,并更新剩余的 k。
步骤3:输出结果 最后,字符串经过最多 k 次操作后,就变成了字典序最小的字符串,输出该字符串。
代码实现
#include <iostream>
#include <string>
using namespace std;
string solution(int n, int k, string s) {
// 遍历每个字符,尽量将0移动到前面
for (int i = 0; i < n && k > 0; ++i) {
// 找到第一个'1',并且尝试将它与后面的'0'交换
if (s[i] == '1') {
for (int j = i + 1; j < n && s[j] == '0' && k > 0; ++j) {
// 交换s[i]和s[j]
swap(s[i], s[j]);
k--; // 每交换一次消耗一次操作
i = j; // 更新当前位置
break;
}
}
}
return s;
}
int main() {
cout << solution(5, 2, "01010") << endl; // 输出:00101
cout << solution(7, 3, "1101001") << endl; // 输出:0110101
cout << solution(4, 1, "1001") << endl; // 输出:0101
return 0;
}
贪心策略:我们从字符串的左端开始,如果遇到 '1',就找它后面最近的 '0' 并进行交换,直到操作次数用完。 交换过程:通过内层的 for 循环,我们查找当前 '1' 后面最接近的 '0',并通过 swap 操作将它们交换。在每次交换后,我们减少操作次数 k,并更新当前的字符位置。 效率:由于每次只进行一次交换,因此时间复杂度大约是 O(n),其中 n 是字符串的长度。在最坏情况下,我们需要遍历字符串中的每个字符并进行一次交换。
本题的解决思路
主要依赖于贪心算法。我们通过每次将 '0' 尽可能地移到前面,来实现字典序最小的目标。由于交换只能是相邻字符的交换,因此我们需要在每次遇到 '1' 时,尽量将其后面的 '0' 移到前面,直到操作次数 k 用尽。
个人思考
贪心思想的应用:这道题让我更加理解了贪心策略的实际应用。在这个问题中,贪心地选择将 '0' 移动到前面,能够达到最优解。每次尽可能地做出最优的局部选择(即移动最近的 '0'),并且确保每个选择都是可行的。
操作次数的限制:题目中的操作次数 k 是一个重要的限制因素。在实际应用中,如何在有限的资源(如操作次数)下尽量优化结果是一个常见的优化问题。通过贪心策略,我们确保在每次操作时都能最大化地利用剩余操作次数。
边界情况的处理:在代码实现中,我们需要特别注意操作次数 k 可能提前耗尽的情况。为了避免不必要的交换,我们在每次交换前检查剩余的操作次数是否足够。
优化空间:虽然本解法是一个有效的贪心策略,但在更复杂的问题中,我们可能需要考虑更加优化的方案,如使用优先队列(堆)来提前确定应该交换的位置。对于这种字符串变换类的问题,贪心算法通常是一个不错的起点。