2025-04-22:形成目标字符串需要的最少字符串数Ⅰ。用go语言,给定一个字符串数组 words 和一个目标字符串 target。 如果一个字符串 x 是

2025-04-22:形成目标字符串需要的最少字符串数Ⅰ。用go语言,给定一个字符串数组 words 和一个目标字符串 target。

如果一个字符串 x 是 words 中某个字符串的开头部分(前缀),那么它被视为有效字符串。

现在希望通过拼接这些有效字符串来组装出 target,求出拼接所需的最少字符串数量。如果无法拼出 target,则返回 -1。

1 <= words.length <= 100。

1 <= words[i].length <= 5 * 1000。

输入确保 sum(words[i].length) <= 100000。

words[i] 只包含小写英文字母。

1 <= target.length <= 5 * 1000。

target 只包含小写英文字母。

输入: words = ["abc","aaaaa","bcdef"], target = "aabcdabc"。

输出: 3。

解释:

target 字符串可以通过连接以下有效字符串形成:

words[1] 的长度为 2 的前缀,即 "aa"。

words[2] 的长度为 3 的前缀,即 "bcd"。

words[0] 的长度为 3 的前缀,即 "abc"。

题目来自leetcode3291。

算法流程分解

1. 构建"最长公共前缀数组"(KMP 算法的 prefix function)

  • 对于每个 words 中的字符串 word,和目标字符串 target,我们用 KMP 算法计算字符串:word + "#" + target 的 prefix function 数组 pi

  • 这个 prefix function 数组 pi 记录的是:

    • 以当前索引结尾的字符串 s,其最长的相同前缀长度有多长。
  • 这里 word + "#" + target 这样拼接是为了避免 word 后缀与 target 前缀的错误匹配,从而准确找到 target 中各个位置与 word 前缀的最长匹配长度。


2. 计算 target 中每个位置可以匹配的最长有效字符串长度

  • 定义数组 back,长度等于 target 的长度。

  • 对每个 word,利用对应的前缀匹配数组 pi,更新 back 中相应位置的值。

  • 具体来说:

    • pi 中,m = len(word), 从索引 m + 1 开始是 target 的匹配信息。
    • target 的第 i 个字符,back[i] = max(back[i], pi[m + 1 + i])
    • 这个 back[i] 表示从 target[i] 位置开始,可以匹配到的最大有效字符串的长度。
  • 举例:

    • 如果 back[0] = 2,说明从 target 开头起,可以匹配某个 word 的长度为2的前缀,是一个有效字符串。

3. 利用动态规划计算最少拼接字符串数

  • 定义动态规划数组 dp,长度为 len(target) + 1,它表示拼接到 target 中索引为 i (前缀长度为 i)所需的最少有效字符串数量。

  • 初始化:

    • dp[0] = 0,拼接空字符串需要0个字符串。
    • 其余位置初始化为一个较大的数,表示尚未计算或不可达。
  • 状态转移:

    • i 从 0 到 len(target)-1 遍历:
      • 利用 back[i],代表从位置 i 开始能匹配的最大有效字符串长度 length = back[i]
      • 同时更新 dp[i + length] = min(dp[i + length], dp[i] + 1),表示使用一个有效字符串拼接该段后,拼接到位置 i + length 所需的最少字符串数量。
  • 如果最后 dp[len(target)] 的值依旧是未修改的无穷大,说明无法拼出 target,返回 -1。

  • 否则返回最小值。


复杂度分析

时间复杂度

  • 假设:

    • Wwords 数组中所有字符串长度的总和,W = sum(len(words[i])),题目限制 W <= 100000
    • N = len(target)N <= 5000
  • 对每个 word

    • 计算 prefix function 时间为 O(len(word) + N)
  • 总时间为:

    • 所有 word 前缀函数计算的时间和,即 O(W + N * len(words))
    • 这里因为 len(words) <= 100,且 N <= 5000,整体为 O(W + N * 100) 在最大输入下仍可接受。
  • 动态规划部分为 O(N)

  • 总结时间复杂度
    O(W + N * len(words)),其中最关键是 prefix function 计算。


空间复杂度

  • 主要空间开销:

    • 存储 prefix function 数组:最大长度为 len(word) + 1 + N,每次计算占用 O(len(word) + N),临时空间。
    • 动态规划数组 dp :长度为 N+1,空间为 O(N)
    • 辅助数组 back :长度为 N,空间为 O(N)
  • 由于 prefix function 每次计算后数组可释放,所以额外空间主要为 O(N + max(len(word)))


总结

  • 算法核心利用了 KMP 算法的 prefix function 来高效求每个位置能匹配的最大有效字符串长度,避免暴力匹配。
  • 通过动态规划计算拼接所需的最小字符串数。
  • 适合处理中等长度目标字符串和大量中长字符串的组合问题。
  • 时间复杂度主要受 prefix function 计算影响,空间复杂度较低,符合题目给定限制。

Go完整代码如下:

go 复制代码
package main

import (
	"fmt"
	"math"
)

func minValidStrings(words []string, target string) int {
	prefixFunction := func(word, target string) []int {
		s := word + "#" + target
		n := len(s)
		pi := make([]int, n)
		for i := 1; i < n; i++ {
			j := pi[i-1]
			for j > 0 && s[i] != s[j] {
				j = pi[j-1]
			}
			if s[i] == s[j] {
				j++
			}
			pi[i] = j
		}
		return pi
	}

	n := len(target)
	back := make([]int, n)
	for _, word := range words {
		pi := prefixFunction(word, target)
		m := len(word)
		for i := 0; i < n; i++ {
			back[i] = int(math.Max(float64(back[i]), float64(pi[m+1+i])))
		}
	}

	dp := make([]int, n+1)
	for i := 1; i <= n; i++ {
		dp[i] = int(1e9)
	}
	for i := 0; i < n; i++ {
		dp[i+1] = dp[i+1-back[i]] + 1
		if dp[i+1] > n {
			return -1
		}
	}
	return dp[n]
}

func main() {
	words := []string{"abc", "aaaaa", "bcdef"}
	target := "aabcdabc"
	results := minValidStrings(words, target)
	fmt.Println(results)
}

Python完整代码如下:

python 复制代码
# -*-coding:utf-8-*-

def minValidStrings(words, target):
    n = len(target)
    if n == 0:
        return 0
    back = [0] * n

    def prefix_function(word, target_str):
        s = word + '#' + target_str
        pi = [0] * len(s)
        for i in range(1, len(s)):
            j = pi[i - 1]
            while j > 0 and s[i] != s[j]:
                j = pi[j - 1]
            if s[i] == s[j]:
                j += 1
            pi[i] = j
        return pi

    for word in words:
        m = len(word)
        pi = prefix_function(word, target)
        for i in range(n):
            index_in_pi = m + 1 + i
            if index_in_pi < len(pi):
                back[i] = max(back[i], pi[index_in_pi])

    dp = [float('inf')] * (n + 1)
    dp[0] = 0
    for i in range(n):
        l = back[i]
        prev_pos = (i + 1) - l
        if prev_pos >= 0:
            if dp[prev_pos] + 1 < dp[i + 1]:
                dp[i + 1] = dp[prev_pos] + 1

    return dp[n] if dp[n] != float('inf') else -1

# Example usage:
words = ["abc", "aaaaa", "bcdef"]
target = "aabcdabc"
print(minValidStrings(words, target))  # Output: 3
相关推荐
常年游走在bug的边缘41 分钟前
基于spring boot 集成 deepseek 流式输出 的vue3使用指南
java·spring boot·后端·ai
廖广杰1 小时前
java虚拟机-为何元空间取代永久代
后端
李菠菜1 小时前
配置 MySQL 8 允许 Root 用户远程访问
后端·mysql
稀土君1 小时前
🔥 万「友」引力计划上线啦,轻松做任务赢积分“拿”华为MatePad Air、雷蛇机械键盘、 热门APP会员卡...
前端·人工智能·后端
tactfulleaner1 小时前
小于n的最大数
后端
Bohemian1 小时前
服务稳定性建设之隔离机制 学习笔记
后端·微服务·面试
异常君1 小时前
Java 并发利器:CyclicBarrier 从入门到精通
java·后端
爱编程的王小美1 小时前
Scala 入门指南
开发语言·后端·scala
李菠菜1 小时前
SpringBoot+Shiro同服务器多项目Cookie冲突解决方案
spring boot·后端·shiro
五号厂房1 小时前
一点关于网络的小知识:0.0.0.0 能否替代 127.0.0.1 使用?
后端