当前位置: 首页 > 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.dtcms.com/a/296683.html

相关文章:

  • 蚁群优化算法(Ant Colony Optimization, ACO)简介
  • Scrcpy启动闪退
  • Function Calling:让大模型学会使用工具
  • es0102---语法格式、数据类型、整合springboot、创建库、创建映射、新增数据、自定义查询
  • tensorflow搭建神经网络
  • SQL基础⑩ | 数据类型篇
  • 哈希表模拟实现
  • Java学习日记_廖万忠
  • Spring Cloud Alibaba Sentinel 源码阅读之流量控制算法
  • C++编程基础四大件
  • Bright Data 实战指南:从竞品数据抓取到电商策略优化全流程
  • 探秘 VSAR软件:CAN报文转DBC信号的便捷工具
  • 力扣189:轮转数组
  • 5 个适合创意创作的网站,灵感不设限
  • 基于markdown封装的前端文档编辑工具,markdown.js的解析与应用
  • 蚁群优化算法(ACO)求解旅行商问题(TSP)
  • 碳油 PCB 技术解析:高精度制造与多场景应用实践
  • Python爬虫案例:Scrapy+XPath解析当当网网页结构
  • Spring Boot 3整合Spring AI实战:9轮面试对话解析AI应用开发
  • FreeRTOS—计数型信号量
  • 亚马逊Prime Day变革:精细化运营时代的号角
  • 基础05-Java控制流程:掌握if-else、switch和循环语句
  • 使用adb 发送广播 动态改变app内的值
  • 【PyTorch】图像二分类项目-部署
  • 【数字IC验证学习------- SOC 验证 和 IP验证和形式验证的区别】
  • NOTEPAD!NPCommand函数分析之comdlg32!GetSaveFileNameW--windows记事本源代码分析
  • 暑假集训篇之并发处理①练习题
  • prometheus监控k8s的metric详解-01-apiserver部分-05-其他
  • 局域网TCP通过组播放地址rtp推流和拉流实现实时喊话
  • 猎板碳油 PCB和普通PCB的区别