2025-06-11:两个字符串的切换距离。用go语言,给定两个长度相同

2025-06-11:两个字符串的切换距离。用go语言,给定两个长度相同的字符串 s 和 t,以及两个整数数组 nextCost 和 previousCost。我们需要通过一系列操作将 s 转换为 t,每次操作可以选择以下两种方式之一:

1.下一个字母操作:将字符 s[i] 变为字母表中的下一个字母('z' 变为 'a'),代价为 nextCost[j],其中 j 是当前字符在字母表中的位置('a'=0,'b'=1,...,'z'=25);

2.上一个字母操作:将字符 s[i] 变为字母表中的上一个字母('a' 变为 'z'),代价为 previousCost[j],其中 j 同上。

要求计算将 s 转换为 t 所需的最小总代价。

1 <= s.length == t.length <= 100000。

s 和 t 都只包含小写英文字母。

nextCost.length == previousCost.length == 26。

0 <= nextCost[i], previousCost[i] <= 1000000000。

输入:s = "leet", t = "code", nextCost = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], previousCost = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]。

输出:31。

解释:

选择下标 i = 0 并将 s[0] 向前切换 9 次,总代价为 9 。

选择下标 i = 1 并将 s[1] 向后切换 10 次,总代价为 10 。

选择下标 i = 2 并将 s[2] 向前切换 1 次,总代价为 1 。

选择下标 i = 3 并将 s[3] 向后切换 11 次,总代价为 11。

题目来自力扣3361。

代码大体过程详解

1. 预处理和前缀和数组构造

  • o 代码中有两个长度是 sigma*2 + 1 = 26*2 + 1 = 53 的前缀和数组,分别是 nxtSumpreSum
  • o 其中,nxtSum 用于计算向后(下一个字母)的代价累计和,preSum 用于计算向前(上一个字母)的代价累计和。
  • o 为什么长度是 26*2+1?为了方便处理循环移动(字母绕一圈),将字母表按顺序扩展两遍,方便计算连续区间的代价和,避免模运算复杂性。

具体构造:

  • o nxtSum[i+1] = nxtSum[i] + nextCost[i % 26],累计字母表位置的下一个字母代价。
  • o preSum[i+1] = preSum[i] + previousCost[i % 26],累计字母表位置的上一个字母代价。
  • o 这样 nxtSum[k] - nxtSum[j] 是字母表从 jk-1 方向上的代价累加。
  • o 同理 preSum[k] - preSum[j] 类似,但计算反方向的代价。

2. 逐字符计算从 s[i]到 t[i]的最小代价

遍历每个位置 i

  • o 令 x = s[i] - 'a'y = t[i] - 'a' —— 两字符对应数字索引。

计算两种操作方向的代价:

  • o 向后(顺时针)移动:
    • o 如果 y < x,说明要绕字母表圈一圈才能“向后”从 xy,所以把 y 加上 sigma (26)完成区间求和。
    • o res1 = nxtSum[y] - nxtSum[x] 计算从 xy-1 累计的 nextCost 代价(包含循环)。
  • o 向前(逆时针)移动:
    • o 如果 x < y,同样需要绕一圈,给 x 加上 sigma
    • o res2 = preSum[x+1] - preSum[y+1] 计算从 y+1x 累计的 previousCost 代价。
  • o 在这两种代价中选择代价进行累加:
    • o ans += min(res1, res2)

总结过程

  • o 利用两倍字母长度的前缀和数组快速计算绕过字母表一圈的连续移动代价(避免循环边界判断)。
  • o 对每一对 s[i]t[i],计算两条可选路径花费(顺时针或逆时针操作)。
  • o 选出较小的代价累加。
  • o 最后得到将 s 转换为 t 的最小总代价。

复杂度分析

  • o sigma = 26 是常数,因为字母固定是 26 个。

时间复杂度

  • o 生成 nxtSumpreSum 前缀和数组花费时间 O(sigma*2),即常数级别。
  • o 对字符串进行单次遍历,计算每个字符的最小代价,花费时间为 O(n),其中 n = len(s)
  • o 每个字符的代价计算是常数时间操作,包括索引计算和前缀和差值。

所以总时间复杂度是:O(n)

空间复杂度

  • o 使用了两个大小约为 53 (常数)的前缀和数组,nxtSumpreSum
  • o 不额外开辟与输入规模相关的数组。

所以总额外空间复杂度是:O(1) // 常数级别,不随 n 变化


总结

步骤/部分

描述

复杂度

前缀和数组构造

构造连续两遍字母表的代价前缀和

O(1)

遍历字符串

对每个字符计算确切的最短移动代价

O(n)

总时间复杂度

常数构造 + 线性遍历字符串

O(n)

额外空间复杂度

仅储存两个常数大小的前缀数组

O(1)

因此,这段代码整体设计清晰、高效,非常适合处理长度较长(最多10万)的字符串转换问题。

Go完整代码如下:

.

package main

import (
    "fmt"
)

func shiftDistance(s, t string, nextCost, previousCost []int) (ans int64) {
    const sigma = 26
    var nxtSum, preSum [sigma*2 + 1]int64
    for i := 0; i < sigma*2; i++ {
        nxtSum[i+1] = nxtSum[i] + int64(nextCost[i%sigma])
        preSum[i+1] = preSum[i] + int64(previousCost[i%sigma])
    }
    for i := range s {
        x := s[i] - 'a'
        y := t[i] - 'a'
        if y < x {
            y += sigma
        }
        res1 := nxtSum[y] - nxtSum[x]
        y = t[i] - 'a'
        if x < y {
            x += sigma
        }
        res2 := preSum[x+1] - preSum[y+1]
        ans += min(res1, res2)
    }
    return
}

func main() {
    s := "leet"
    t := "code"
    nextCost := []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
    previousCost := []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
    fmt.Println(shiftDistance(s, t, nextCost, previousCost))
}


Python完整代码如下:

.

# -*-coding:utf-8-*-

def shift_distance(s: str, t: str, next_cost: list, previous_cost: list) -> int:
    sigma = 26
    nxt_sum = [0] * (sigma * 2 + 1)
    pre_sum = [0] * (sigma * 2 + 1)
    
    for i in range(sigma * 2):
        nxt_sum[i+1] = nxt_sum[i] + next_cost[i % sigma]
        pre_sum[i+1] = pre_sum[i] + previous_cost[i % sigma]
    
    ans = 0
    for x_char, y_char in zip(s, t):
        x = ord(x_char) - ord('a')
        y = ord(y_char) - ord('a')
        
        # Calculate next shift cost
        if y < x:
            y += sigma
        res1 = nxt_sum[y] - nxt_sum[x]
        
        # Calculate previous shift cost
        y = ord(y_char) - ord('a')  # reset y
        x = ord(x_char) - ord('a')  # reset x
        if x < y:
            x += sigma
        res2 = pre_sum[x+1] - pre_sum[y+1]
        
        ans += min(res1, res2)
    
    return ans

def main():
    s = "leet"
    t = "code"
    next_cost = [1] * 26
    previous_cost = [1] * 26
    print(shift_distance(s, t, next_cost, previous_cost))

if __name__ == "__main__":
    main()



·



我们相信 Go 语言和算法为普通开发者提供了强有力的“面试利器”,并致力于分享全面的编程知识。在这里,您可以找到最新的 Go 语言教程、算法解析、提升面试竞争力的秘籍以及行业动态。


欢迎关注“福大大架构师每日一题”,让 Go 语言和算法助力您的职业发展

·

原文链接:,转发请注明来源!