문제:
사 전적으로 최소 원형 하위 문자열은 그러한 모든 회전 중에서 가장 낮은 사전 순을 갖는 스트링의 회전을 찾는 문제이다. 예를 들어, "bbaaccaadd"의 사 전적으로 최소 회전은 "aaccaaddbb"입니다.
해결책:
AO (n) 시간 알고리즘은 Jean Pierre Duval (1983)에 의해 제안되었습니다.
두 지표 감안 i
하고 j
, 듀발의 알고리즘은 길이의 문자열 세그먼트 비교 j - i
에서 시작 i
하고 j
(불리는 "결투" ). 경우 index + j - i
문자열의 길이보다 큰 경우, 세그먼트가 주위에 배치하여 형성된다.
예를 들어, s = "baabbaba", i = 5 및 j = 7을 고려하십시오. j-i = 2이므로 i = 5에서 시작하는 첫 번째 세그먼트는 "ab"입니다. j = 7에서 시작하는 두 번째 세그먼트는 줄 바꿈으로 구성되며 "ab"입니다. 위의 예에서와 같이 문자열이 사전 식으로 동일한 경우, i에서 시작하는 문자열을 i = 5로 선택합니다.
위 과정은 한 명의 당첨자가 될 때까지 반복됩니다. 입력 문자열의 길이가 홀수 인 경우 첫 번째 반복에서 비교하지 않고 마지막 문자가 승리합니다.
시간 복잡성 :
첫 번째 반복은 길이 1 (n / 2 비교) 각각의 n 개 문자열을 비교하고, 두 번째 반복은 i 번째 반복이 2 개의 문자열을 비교할 때까지 길이 2의 n / 2 문자열 (n / 2 비교) 등을 비교할 수 있습니다. 길이 n / 2 (n / 2 비교). 승자의 수는 매번 반으로 줄어들 기 때문에 재귀 트리의 높이는 log (n)이므로 O (n log (n)) 알고리즘을 제공합니다. 작은 n의 경우 이것은 대략 O (n)입니다.
첫 번째 반복에서는 n / 2 수상자, 두 번째 반복 n / 4 수상자 등을 저장해야하므로 공간 복잡도도 O (n)입니다. (Wikipedia는이 알고리즘이 일정한 공간을 사용한다고 주장합니다. 어떻게 이해하지 못합니다).
다음은 스칼라 구현입니다. 좋아하는 프로그래밍 언어로 자유롭게 변환하십시오.
def lexicographicallyMinRotation(s: String): String = {
@tailrec
def duel(winners: Seq[Int]): String = {
if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
else {
val newWinners: Seq[Int] = winners
.sliding(2, 2)
.map {
case Seq(x, y) =>
val range = y - x
Seq(x, y)
.map { i =>
val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
(i, segment)
}
.reduce((a, b) => if (a._2 <= b._2) a else b)
._1
case xs => xs.head
}
.toSeq
duel(newWinners)
}
}
duel(s.indices)
}