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

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

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

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

1 <= words.length <= 100。

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

输入确保 sum(wordsi.length) <= 100000。

wordsi 只包含小写英文字母。

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

target 只包含小写英文字母。

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

输出: 3。

解释:

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

words1 的长度为 2 的前缀,即 "aa"。

words2 的长度为 3 的前缀,即 "bcd"。

words0 的长度为 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。

  • 否则返回最小值。


复杂度分析

时间复杂度

  • 假设:

    • W 是 words 数组中所有字符串长度的总和,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
相关推荐
余槐i2 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8263 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
凤山老林4 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne5 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF5 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li5 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
upala6 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna6 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构
码事漫谈6 小时前
因为太想讨好你,所以AI会骗你
后端
吴凡wudianmei6 小时前
每调一次模型都花钱?如何用工程手段把调用压到最少?
前端·后端·架构