题目解析
本题要求我们计算一个由字符 a 和 b 组成的字符串 S 的“成本”,成本定义为字符串中按非递减顺序排列的最长子字符串的长度。我们可以进行最多一次交换操作,目标是找到在执行最多一次交换操作后,字符串 S 的最小可能成本。
解题思路:
-
成本的定义:
字符串的成本是其最长非递减子串的长度。例如,字符串"abba"的最长非递减子串是"bb",所以成本为2。通过交换相邻的两个字符,我们有可能使某些子串变长,从而减少字符串的成本。 -
交换操作的影响:
我们最多可以交换字符串中的一对相邻字符。交换后,我需要重新计算字符串的成本,看是否比原始字符串的成本更低。 -
步骤:
- 先计算原始字符串的成本。
- 然后枚举所有可能的交换位置,计算交换后的成本。
- 返回交换后的最小成本。
-
关键点:
- 计算一个字符串的“成本”即计算其最长的非递减子串。
- 然后,通过交换每对相邻字符来查看是否能使非递减子串变长。
代码详解
python
def solution(N: int, S: str) -> int:
# 计算字符串 S 中最长非递减子字符串的长度
def calculate_cost(S: str) -> int:
max_len = 1
current_len = 1
for i in range(1, len(S)):
if S[i] >= S[i-1]: # 如果当前字符不小于前一个字符
current_len += 1
max_len = max(max_len, current_len)
else:
current_len = 1 # 重置当前非递减子字符串的长度
return max_len
# 计算初始字符串 S 的成本
initial_cost = calculate_cost(S)
# 尝试所有可能的交换位置
min_cost = initial_cost
for i in range(N - 1):
# 交换 S[i] 和 S[i+1]
swapped_S = S[:i] + S[i+1] + S[i] + S[i+2:]
# 计算交换后的成本
swapped_cost = calculate_cost(swapped_S)
# 更新最小成本
min_cost = min(min_cost, swapped_cost)
return min_cost
# 测试用例
if __name__ == '__main__':
print(solution(N = 4, S = "abba") == 2) # 交换 "ab" -> "ba" 后,最长非递减子串是 "bb" 长度为 2
print(solution(N = 5, S = "baabb") == 2) # 交换 "ba" -> "ab" 后,最长非递减子串是 "aa" 长度为 2
print(solution(N = 3, S = "bab") == 2) # 交换 "ba" -> "ab" 后,最长非递减子串是 "ab" 或 "ba" 长度为 2
代码分析:
-
calculate_cost函数:
该函数用于计算一个字符串中最长的非递减子串的长度。我通过遍历字符串,在遇到不满足非递减条件时重置子串长度,并记录最长的非递减子串。 -
solution函数:- 首先,计算原始字符串的成本
initial_cost。 - 然后,遍历所有可能的相邻字符交换位置,交换字符后再计算新的成本
swapped_cost,并更新最小成本。
- 首先,计算原始字符串的成本
知识总结
-
最长非递减子串的计算:
- 这个问题的关键在于如何计算一个字符串的最长非递减子串。我们可以通过遍历字符串并比较相邻的字符,累积非递减子串的长度,遇到递减时重置长度。
-
交换操作的影响:
- 交换操作可能影响到最长非递减子串的长度。通过交换相邻字符,我们能够尝试将一些字符连接成更长的非递减子串,从而降低字符串的成本。
-
暴力枚举与优化:
- 由于题目限制最多只允许交换一次,我们采用了暴力枚举每个交换位置的策略,并对每个可能的交换计算交换后的成本。虽然这种方式在最坏情况下时间复杂度为 O(N^2),但是对一般情况已经能够有效处理。
学习方法与心得
-
逐步拆解问题:
- 在开始编码之前,我们需要仔细理解题目中的关键定义,如“成本”的定义(最长非递减子串的长度)。然后,我们可以将问题拆解为多个小问题,逐一解决。
-
数据结构的使用:
- 本题没有涉及复杂的数据结构,主要通过字符串的遍历和简单的变量记录来实现。然而,在处理类似的问题时,理解如何高效地计算某种属性(如非递减子串的长度)是非常重要的。
-
问题调试和优化:
- 在实际编程过程中,调试是不可避免的。代码中可能会遇到边界情况或者交换后字符串未正确更新的情况。通过逐步调试和打印中间结果,可以帮助发现问题。
学习建议
-
理解问题的本质:
- 在解决编程题时,首先需要理解题目要求的核心问题,并仔细分析题目中的每个条件。例如,本题的核心就是“计算最长非递减子串的长度”,理解这一点之后,我们可以开始思考如何高效地计算这个长度。
-
从简单的暴力方法开始:
- 在面对较为复杂的问题时,可以先使用暴力方法实现基本功能,确保能正确解决问题,再考虑如何优化。如果暴力方法已经能够在给定数据范围内正常工作,就可以暂时不考虑优化。
-
多做练习,提升算法设计能力:
- 通过不断刷题,我们可以积累更多的算法技巧和解题思路,同时提升对时间复杂度和空间复杂度的敏感度。豆包MarsCode AI提供了丰富的题库,可以帮助巩固算法基础并解决各种类型的问题。
可能出现的问题及解决办法
-
交换后的字符串计算错误:
- 在交换字符后,确保字符串正确更新。可以通过调试打印交换后的字符串,验证交换是否生效。
-
计算非递减子串时漏掉边界情况:
- 需要注意字符串只有一个字符或为空的特殊情况,这些情况下成本应该为 1 或 0。
总结:
通过本题的学习,我不仅掌握了计算最长非递减子串的方法,还了解了如何通过交换操作影响字符串的结构,并通过暴力方法解决问题。学习过程中,不仅要注重算法本身,还应注重对边界情况的处理、复杂度分析以及如何优化和提高算法的性能。