当前位置: 首页 > news >正文

【华为机试】1208. 尽可能使字符串相等

文章目录

  • 1208. 尽可能使字符串相等
    • 描述
    • 示例 1
    • 示例 2
    • 示例 3
    • 提示
    • 解题思路
      • 方法一:滑动窗口(推荐)
      • 方法二:前缀和 + 二分查找
    • 代码实现
    • 测试用例
    • 关键点总结
    • 相似题目
    • 完整题解代码

1208. 尽可能使字符串相等

描述

给你两个长度相同的字符串,s 和 t。

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

示例 1

输入:s = “abcd”, t = “bcdf”, maxCost = 3
输出:3
解释:s 中的 “abc” 可以变为 “bcd”。开销为 3,所以最大长度为 3。

示例 2

输入:s = “abcd”, t = “cdef”, maxCost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。

示例 3

输入:s = “abcd”, t = “acde”, maxCost = 0
输出:1
解释:a -> a, cost = 0,字符串未发生变化,所以最大长度为 1。

提示

  • 1 <= s.length, t.length <= 10^5
  • 0 <= maxCost <= 10^6
  • s 和 t 都只含小写英文字母。

解题思路

方法一:滑动窗口(推荐)

这道题的本质是找到一个最长的连续子数组,使得子数组中所有元素的和不超过 maxCost

算法步骤:

  1. 计算开销数组:首先计算每个位置字符转换的开销 costs[i] = |s[i] - t[i]|
  2. 滑动窗口:使用双指针维护一个滑动窗口,窗口内的开销总和不超过 maxCost
  3. 更新答案:在滑动过程中记录最大窗口长度

算法流程:

计算每个位置的开销
初始化滑动窗口
右指针扩展窗口
窗口开销 <= maxCost?
更新最大长度
左指针收缩窗口
右指针到达末尾?
返回最大长度

时间复杂度: O(n),其中 n 是字符串长度
空间复杂度: O(1),只使用常数额外空间

方法二:前缀和 + 二分查找

算法思路:

  1. 计算开销数组的前缀和
  2. 对于每个起始位置,使用二分查找找到最远的结束位置

时间复杂度: O(n log n)
空间复杂度: O(n)

代码实现

// 方法一:滑动窗口
func equalSubstring(s string, t string, maxCost int) int {n := len(s)left, right := 0, 0currentCost := 0maxLength := 0for right < n {// 计算当前字符的开销cost := abs(int(s[right]) - int(t[right]))// 扩展窗口currentCost += costright++// 如果开销超过预算,收缩窗口for currentCost > maxCost {currentCost -= abs(int(s[left]) - int(t[left]))left++}// 更新最大长度maxLength = max(maxLength, right - left)}return maxLength
}func abs(x int) int {if x < 0 {return -x}return x
}func max(a, b int) int {if a > b {return a}return b
}

测试用例

func testEqualSubstring() {testCases := []struct {s, t     stringmaxCost  intexpected int}{{"abcd", "bcdf", 3, 3},{"abcd", "cdef", 3, 1},{"abcd", "acde", 0, 1},{"krrgw", "zjxss", 19, 2},{"abcd", "abcd", 0, 4},{"a", "b", 1, 1},{"a", "b", 0, 0},}for i, tc := range testCases {result := equalSubstring(tc.s, tc.t, tc.maxCost)if result == tc.expected {fmt.Printf("测试用例 %d 通过: s=%s, t=%s, maxCost=%d, 结果=%d\n", i+1, tc.s, tc.t, tc.maxCost, result)} else {fmt.Printf("测试用例 %d 失败: s=%s, t=%s, maxCost=%d, 期望=%d, 实际=%d\n", i+1, tc.s, tc.t, tc.maxCost, tc.expected, result)}}
}

关键点总结

  1. 问题转化:将字符串转换问题转化为子数组和不超过阈值的最大长度问题
  2. 滑动窗口:使用双指针维护满足条件的连续子数组
  3. 开销计算:使用 ASCII 码差的绝对值计算转换开销
  4. 边界处理:注意处理 maxCost = 0 的特殊情况
  5. 优化技巧:滑动窗口可以在 O(n) 时间内找到最优解

相似题目

  • 209. 长度最小的子数组 - 滑动窗口找最小长度
  • 1004. 最大连续1的个数 III - 滑动窗口找最大长度
  • 3. 无重复字符的最长子串 - 滑动窗口经典应用

完整题解代码

package mainimport "fmt"// 方法一:滑动窗口(推荐)
func equalSubstring(s string, t string, maxCost int) int {n := len(s)left, right := 0, 0currentCost := 0maxLength := 0for right < n {// 计算当前字符的开销cost := abs(int(s[right]) - int(t[right]))// 扩展窗口currentCost += costright++// 如果开销超过预算,收缩窗口for currentCost > maxCost {currentCost -= abs(int(s[left]) - int(t[left]))left++}// 更新最大长度maxLength = max(maxLength, right-left)}return maxLength
}// 方法二:前缀和 + 二分查找
func equalSubstring2(s string, t string, maxCost int) int {n := len(s)// 计算前缀和prefixSum := make([]int, n+1)for i := 0; i < n; i++ {cost := abs(int(s[i]) - int(t[i]))prefixSum[i+1] = prefixSum[i] + cost}maxLength := 0// 对于每个起始位置,二分查找最远的结束位置for i := 0; i < n; i++ {// 二分查找最远的j,使得prefixSum[j+1] - prefixSum[i] <= maxCostleft, right := i, n-1for left <= right {mid := left + (right-left)/2cost := prefixSum[mid+1] - prefixSum[i]if cost <= maxCost {maxLength = max(maxLength, mid-i+1)left = mid + 1} else {right = mid - 1}}}return maxLength
}// 方法三:暴力解法(用于验证)
func equalSubstring3(s string, t string, maxCost int) int {n := len(s)maxLength := 0for i := 0; i < n; i++ {currentCost := 0for j := i; j < n; j++ {cost := abs(int(s[j]) - int(t[j]))currentCost += costif currentCost <= maxCost {maxLength = max(maxLength, j-i+1)} else {break}}}return maxLength
}func abs(x int) int {if x < 0 {return -x}return x
}func max(a, b int) int {if a > b {return a}return b
}// 测试函数
func testEqualSubstring() {testCases := []struct {s, t     stringmaxCost  intexpected int}{{"abcd", "bcdf", 3, 3},{"abcd", "cdef", 3, 1},{"abcd", "acde", 0, 1},{"krrgw", "zjxss", 19, 2},{"abcd", "abcd", 0, 4},{"a", "b", 1, 1},{"a", "b", 0, 0},{"abcdef", "bcdefg", 5, 5},{"xyz", "abc", 10, 3}, // x->a: 23, y->b: 23, z->c: 23, 总开销=69 > 10,所以只能转换1个字符{"xyz", "abc", 2, 0},  // 任何字符转换开销都超过2,所以结果为0}fmt.Println("=== 测试滑动窗口算法 ===")for i, tc := range testCases {result := equalSubstring(tc.s, tc.t, tc.maxCost)if result == tc.expected {fmt.Printf("✅ 测试用例 %d 通过: s=%s, t=%s, maxCost=%d, 结果=%d\n",i+1, tc.s, tc.t, tc.maxCost, result)} else {fmt.Printf("❌ 测试用例 %d 失败: s=%s, t=%s, maxCost=%d, 期望=%d, 实际=%d\n",i+1, tc.s, tc.t, tc.maxCost, tc.expected, result)}}fmt.Println("\n=== 测试前缀和+二分查找算法 ===")for i, tc := range testCases {result := equalSubstring2(tc.s, tc.t, tc.maxCost)if result == tc.expected {fmt.Printf("✅ 测试用例 %d 通过: s=%s, t=%s, maxCost=%d, 结果=%d\n",i+1, tc.s, tc.t, tc.maxCost, result)} else {fmt.Printf("❌ 测试用例 %d 失败: s=%s, t=%s, maxCost=%d, 期望=%d, 实际=%d\n",i+1, tc.s, tc.t, tc.maxCost, tc.expected, result)}}fmt.Println("\n=== 测试暴力解法 ===")for i, tc := range testCases {result := equalSubstring3(tc.s, tc.t, tc.maxCost)if result == tc.expected {fmt.Printf("✅ 测试用例 %d 通过: s=%s, t=%s, maxCost=%d, 结果=%d\n",i+1, tc.s, tc.t, tc.maxCost, result)} else {fmt.Printf("❌ 测试用例 %d 失败: s=%s, t=%s, maxCost=%d, 期望=%d, 实际=%d\n",i+1, tc.s, tc.t, tc.maxCost, tc.expected, result)}}
}// 性能对比测试
func benchmarkTest() {s := "abcdefghijklmnopqrstuvwxyz"t := "bcdefghijklmnopqrstuvwxyza"maxCost := 100fmt.Println("\n=== 性能对比测试 ===")fmt.Printf("输入: s=%s, t=%s, maxCost=%d\n", s, t, maxCost)// 测试滑动窗口result1 := equalSubstring(s, t, maxCost)fmt.Printf("滑动窗口结果: %d\n", result1)// 测试前缀和+二分查找result2 := equalSubstring2(s, t, maxCost)fmt.Printf("前缀和+二分查找结果: %d\n", result2)// 测试暴力解法result3 := equalSubstring3(s, t, maxCost)fmt.Printf("暴力解法结果: %d\n", result3)if result1 == result2 && result2 == result3 {fmt.Println("✅ 所有算法结果一致")} else {fmt.Println("❌ 算法结果不一致")}
}// 调试函数:手动验证测试用例
func debugTestCase() {fmt.Println("\n=== 调试测试用例 ===")// 测试用例9: "xyz" -> "abc", maxCost=10s1, t1 := "xyz", "abc"maxCost1 := 10fmt.Printf("测试用例9: s=%s, t=%s, maxCost=%d\n", s1, t1, maxCost1)for i := 0; i < len(s1); i++ {cost := abs(int(s1[i]) - int(t1[i]))fmt.Printf("  %c -> %c: |%d - %d| = %d\n", s1[i], t1[i], int(s1[i]), int(t1[i]), cost)}// 计算所有可能的子字符串开销fmt.Println("  所有可能的子字符串开销:")for i := 0; i < len(s1); i++ {for j := i; j < len(s1); j++ {totalCost := 0for k := i; k <= j; k++ {totalCost += abs(int(s1[k]) - int(t1[k]))}fmt.Printf("    [%d,%d]: %s -> %s, 开销=%d\n", i, j, s1[i:j+1], t1[i:j+1], totalCost)}}result1 := equalSubstring(s1, t1, maxCost1)fmt.Printf("  结果: %d\n", result1)// 测试用例10: "xyz" -> "abc", maxCost=2maxCost2 := 2fmt.Printf("\n测试用例10: s=%s, t=%s, maxCost=%d\n", s1, t1, maxCost2)// 计算所有可能的子字符串开销fmt.Println("  所有可能的子字符串开销:")for i := 0; i < len(s1); i++ {for j := i; j < len(s1); j++ {totalCost := 0for k := i; k <= j; k++ {totalCost += abs(int(s1[k]) - int(t1[k]))}fmt.Printf("    [%d,%d]: %s -> %s, 开销=%d\n", i, j, s1[i:j+1], t1[i:j+1], totalCost)}}result2 := equalSubstring(s1, t1, maxCost2)fmt.Printf("  结果: %d\n", result2)
}func main() {fmt.Println("1208. 尽可能使字符串相等")fmt.Println("========================")// 运行调试函数debugTestCase()// 运行测试用例testEqualSubstring()// 运行性能对比benchmarkTest()// 交互式测试fmt.Println("\n=== 交互式测试 ===")fmt.Println("请输入测试用例 (格式: s t maxCost,例如: abcd bcdf 3)")var s, t stringvar maxCost intfmt.Print("请输入 s: ")fmt.Scanln(&s)fmt.Print("请输入 t: ")fmt.Scanln(&t)fmt.Print("请输入 maxCost: ")fmt.Scanln(&maxCost)if len(s) != len(t) {fmt.Println("❌ 错误:字符串长度必须相同")return}result := equalSubstring(s, t, maxCost)fmt.Printf("结果: %d\n", result)
}
http://www.xdnf.cn/news/1177291.html

相关文章:

  • 面试题(技术面+hr面)
  • 第五章 Freertos物联网实战 微信小程序篇
  • 进阶向:基于Python的轻量级Markdown笔记管理器
  • DPO:大语言模型偏好学习的高效方案
  • 5G-RAN与语义通信RAN
  • 4种灵活的方法从POCO手机中删除联系人
  • easyexcel流式导出
  • 网络测试工具
  • 在vue3中watch和watchEffect的区别
  • Windows下使用UIAutomation技术遍历桌面窗口和指定窗口内容的AutomationWalker.exe的C#源代码
  • C++高效实现轨迹规划、自动泊车、RTS游戏、战术迂回包抄、空中轨迹、手术机器人、KD树
  • Java技术栈/面试题合集(17)-Git篇
  • Spring-狂神说
  • day20 双向链表
  • MAC包头、IP包头 、UDP包头中的长度含义是啥?三者之间有啥区别?
  • 【SpringAI实战】提示词工程实现哄哄模拟器
  • 中小企业安全落地:低成本漏洞管理与攻击防御方案
  • SpringCache
  • 双紫擒龙紫紫红黄安装使用攻略,2025通达信指标源码,擒龙追踪源码公式学习
  • 遨游三防平板|国产芯片鸿蒙系统单北斗三防平板,安全高效
  • 算法调试技巧
  • 《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——4. 前后端联动:打通QML与C++的任督二脉
  • 【基础】go基础学习笔记
  • 极客大挑战2019-HTTP
  • 基于Odoo的微信小程序全栈开发探索分析
  • 探索复杂列表开发:从基础到高级的全面指南
  • SSE与Websocket有什么区别?
  • 如何在 conda 中删除环境
  • rust-结构体使用示例
  • Elasticsearch 的聚合(Aggregations)操作详解