环状 DNA 序列的最小表示法 | 豆包MarsCode AI刷题
接触了一个新的算法,布斯算法,但其实是贪心+双指针。但是双倍字符串这个思路还是很新奇的,记录一下。
摘要
小C正在研究一种环状 DNA 序列结构。由于环状结构的特性,可以从任意位置读取序列。任务是确定字典序最小的表示形式。本文采用 布斯算法(Booth's Algorithm) 来高效地解决这一问题。
问题描述
环状 DNA 序列最小表示
给定一个字符串 dna_sequence
,表示环状 DNA 序列,可从任意位置开始读取。目标是找到所有可能表示形式中的 字典序最小 的表示。
示例:
- 输入:
dna_sequence = "ATCA"
- 环状序列所有可能表示:
"ATCA"
"TCAA"
"CAAT"
"AATC"
字典序最小的表示为:"AATC"
。
测试样例
示例 1
输入:
dna_sequence = "ATCA"
输出:
"AATC"
示例 2
输入:
dna_sequence = "CGAGTC"
输出:
"AGTCCG"
示例 3
输入:
dna_sequence = "TTGAC"
输出:
"ACTTG"
算法原理
布斯算法的核心思路
布斯算法专门用于求解环状字符串的 字典序最小表示。其主要原理是利用双倍字符串和贪心策略,避免了暴力枚举所有旋转字符串的高成本。
- 构造双倍字符串:
- 为模拟环状结构,将字符串重复一次。例如:
- 输入:
"ATCA"
。 - 双倍字符串:
"ATCAATCA"
。
- 输入:
- 为模拟环状结构,将字符串重复一次。例如:
- 双指针比较:
- 使用两个指针
i
和j
分别指向两个候选起点。 - 使用变量
k
表示目前比对字符串的长度 - 从两指针位置开始,逐字符比较对应字母的大小。
- 使用两个指针
- 贪心更新:
- 如果
T[i + k] < T[j + k]
,则保留i
位置作为较优的起点,跳过j
,更新j=j+k+1
,如果此时i==j
,则j++
(确保不是同一起点)。 - 如果
T[i + k] > T[j + k]
,则保留j
位置作为较优的起点,跳过i
,更新i=i+k+1
,如果此时i==j
,则i++
,理由同上。 - 如果字符相等,继续比较下一字符,即
k++
。
- 如果
- 终止条件:
- 当某一指针超出原字符串长度时,选择另一个指针作为起点。
算法步骤
-
初始化:
- 构造双倍字符串 。
- 设置两个指针 ,,以及一个匹配长度 。
-
指针比较:
- 比较 和 :
- 如果 ,继续比较下一位,令 。
- 如果 ,说明 i 是更优的起点,跳过 j:
- 更新 ,并重置 。
- 如果 ,说明 是更优的起点,跳过 :
- 更新 ,并重置 。
- 如果 ,则将 。
- 比较 和 :
-
输出结果:
- 最终的最小起点是 和 中较小的一个,返回从该起点开始的长度为 的子串。
时间复杂度
- 字符串比较:每个字符最多比较两次,总体复杂度为 。
- 总时间复杂度:。
空间复杂度
- 使用了双倍字符串和常量辅助变量。
- 总空间复杂度:。
Go 实现
package main
import "fmt"
// 布斯算法解决环状字符串的最小字典序表示
func solution(dna_sequence string) string {
n := len(dna_sequence)
// 构造双倍字符串
doubleS := dna_sequence + dna_sequence
// 初始化双指针
i, j := 0, 1
k := 0
// 双指针扫描
for i < n && j < n && k < n {
if doubleS[i+k] == doubleS[j+k] {
k++
} else if doubleS[i+k] > doubleS[j+k] {
// 如果 i 开始的字典序更大,移动 i 指针
i = i + k + 1
k = 0
if i == j {
i++
}
} else {
// 如果 j 开始的字典序更大,移动 j 指针
j = j + k + 1
k = 0
if i == j {
j++
}
}
}
// 最小起点
pos := min(i, j)
return doubleS[pos : pos+n]
}
// 辅助函数:返回较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
// 测试用例
fmt.Println(solution("ATCA") == "AATC") // true
fmt.Println(solution("CGAGTC") == "AGTCCG") // true
fmt.Println(solution("TTGAC") == "ACTTG") // true
}
Python 实现
def solution(dna_sequence: str) -> str:
"""
使用布斯算法解决环状字符串的最小字典序表示问题。
:param dna_sequence: 待处理的 DNA 序列字符串
:return: DNA 序列的最小字典序表示
"""
n = len(dna_sequence)
# 构造双倍字符串
double_s = dna_sequence + dna_sequence
# 初始化双指针
i, j, k = 0, 1, 0
# 双指针扫描
while i < n and j < n and k < n:
if double_s[i + k] == double_s[j + k]:
k += 1
elif double_s[i + k] > double_s[j + k]:
# 如果 i 开始的字典序更大,移动 i 指针
i = i + k + 1
k = 0
if i == j:
i += 1
else:
# 如果 j 开始的字典序更大,移动 j 指针
j = j + k + 1
k = 0
if i == j:
j += 1
# 最小起点
pos = min(i, j)
return double_s[pos:pos + n]
if __name__ == "__main__":
# 测试用例
print(solution("ATCA") == "AATC") # 应输出 True
print(solution("CGAGTC") == "AGTCCG") # 应输出 True
print(solution("TTGAC") == "ACTTG") # 应输出 True
输出解释
示例 1:
输入:
dna_sequence = "ATCA"
所有可能表示:
"ATCA"
"TCAA"
"CAAT"
"AATC"
字典序最小为:"AATC"
。
示例 2:
输入:
dna_sequence = "CGAGTC"
所有可能表示:
"CGAGTC"
"GAGTCC"
"AGTCCG"
- ...
字典序最小为:"AGTCCG"
。
示例 3:
输入:
dna_sequence = "TTGAC"
所有可能表示:
"TTGAC"
"TGACT"
"GACTT"
"ACTTG"
- ...
字典序最小为:"ACTTG"
。