주기적 단어
문제 설명
순환 단어는 원 안에 쓰여진 단어라고 생각할 수 있습니다. 주기적 단어를 나타 내기 위해 임의의 시작 위치를 선택하고 문자를 시계 방향으로 읽습니다. 따라서 "picture"와 "turepic"은 동일한 순환 단어를 나타냅니다.
String [] 단어가 주어지며, 각 요소는 순환 단어를 나타냅니다. 표시되는 다른 순환 단어 수를 리턴하십시오.
가장 빠른 승리 (Big O, 여기서 n = 문자열의 문자 수)
주기적 단어
문제 설명
순환 단어는 원 안에 쓰여진 단어라고 생각할 수 있습니다. 주기적 단어를 나타 내기 위해 임의의 시작 위치를 선택하고 문자를 시계 방향으로 읽습니다. 따라서 "picture"와 "turepic"은 동일한 순환 단어를 나타냅니다.
String [] 단어가 주어지며, 각 요소는 순환 단어를 나타냅니다. 표시되는 다른 순환 단어 수를 리턴하십시오.
가장 빠른 승리 (Big O, 여기서 n = 문자열의 문자 수)
답변:
여기 내 해결책이 있습니다. 나는 여전히 O (n 2 ) 일지 모른다고 생각 하지만 평균 사례가 그보다 훨씬 낫다고 생각합니다.
기본적으로 각 문자열을 정규화하여 회전이 동일한 형식을 갖도록 작동합니다. 예를 들면 다음과 같습니다.
'amazing' -> 'mazinga'
'mazinga' -> 'mazinga'
'azingam' -> 'mazinga'
'zingama' -> 'mazinga'
'ingamaz' -> 'mazinga'
'ngamazi' -> 'mazinga'
'gamazin' -> 'mazinga'
정규화는 최소 문자 (문자 코드)를 찾고 문자가 마지막 위치에 있도록 문자열을 회전하여 수행됩니다. 해당 문자가 두 번 이상 나타나면 각 발생 후 문자가 사용됩니다. 이는 각 순환 단어에 표준 표현을 제공하여 맵에서 키로 사용할 수 있습니다.
최악의 경우 정규화는 n 2 입니다 (예 : 문자열의 모든 문자가 동일 aaaaaa
). 그러나 대부분의 경우 몇 번만 발생하며 실행 시간이에 가까워집니다 n
.
내 노트북 (이중 코어 Intel Atom @ 1.66GHz 및 1GB 램)에서이 기능을 실행 /usr/share/dict/words
하는 데 (평균 9.5 자, 234,937 개의 단어) 약 7.6 초가 걸립니다.
#!/usr/bin/python
import sys
def normalize(string):
# the minimum character in the string
c = min(string) # O(n) operation
indices = [] # here we will store all the indices where c occurs
i = -1 # initialize the search index
while True: # finding all indexes where c occurs is again O(n)
i = string.find(c, i+1)
if i == -1:
break
else:
indices.append(i)
if len(indices) == 1: # if it only occurs once, then we're done
i = indices[0]
return string[i:] + string[:i]
else:
i = map(lambda x:(x,x), indices)
for _ in range(len(string)): # go over the whole string O(n)
i = map(lambda x:((x[0]+1)%len(string), x[1]), i) # increment the indexes that walk along O(m)
c = min(map(lambda x: string[x[0]], i)) # get min character from current indexes O(m)
i = filter(lambda x: string[x[0]] == c, i) # keep only the indexes that have that character O(m)
# if there's only one index left after filtering, we're done
if len(i) == 1:
break
# either there are multiple identical runs, or
# we found the unique best run, in either case, we start the string from that
# index
i = i[0][0]
return string[i:] + string[:i]
def main(filename):
cyclic_words = set()
with open(filename) as words:
for word in words.readlines():
cyclic_words.add(normalize(word[:-1])) # normalize without the trailing newline
print len(cyclic_words)
if __name__ == '__main__':
if len(sys.argv) > 1:
main(sys.argv[1])
else:
main("/dev/stdin")
내가 사용한 방법은 문자열의 각 문자에서 시작하는 각 단어의 롤링 해시를 계산하는 것이 었습니다. 롤링 해시이므로 모든 n 해시를 계산하는 데 O (n) (여기서 n은 단어 길이) 시간이 걸립니다. 문자열은 기본 1114112 숫자로 처리되므로 해시가 고유합니다. (이것은 Haskell 솔루션과 유사하지만 문자열을 두 번만 통과하기 때문에 더 효율적입니다.)
그런 다음 각 입력 단어에 대해 알고리즘은 가장 낮은 해시를 확인하여 이미 해시 세트에 있는지 확인합니다 (파이썬 세트, 따라서 조회는 세트 크기에서 O (1) 임). 그렇다면 단어 또는 그 회전 중 하나가 이미 표시 된 것입니다. 그렇지 않으면 해당 해시를 세트에 추가합니다.
명령 줄 인수는 한 줄에 하나의 단어가 포함 된 파일 이름이어야합니다 (예 /usr/share/dict/words
:).
import sys
def rollinghashes(string):
base = 1114112
curhash = 0
for c in string:
curhash = curhash * base + ord(c)
yield curhash
top = base ** len(string)
for i in range(len(string) - 1):
curhash = curhash * base % top + ord(string[i])
yield curhash
def cycles(words, keepuniques=False):
hashes = set()
uniques = set()
n = 0
for word in words:
h = min(rollinghashes(word))
if h in hashes:
continue
else:
n += 1
if keepuniques:
uniques.add(word)
hashes.add(h)
return n, uniques
if __name__ == "__main__":
with open(sys.argv[1]) as words_file:
print(cycles(line.strip() for line in words_file)[0])
이것의 효율성에 대해 잘 모르면 아마도 오히려 나쁠 것입니다. 아이디어는 먼저 모든 단어의 가능한 모든 회전을 만들고 문자열을 고유하게 나타내는 값을 세고 최소값을 선택하는 것입니다. 그렇게하면 순환 그룹에 고유 한 숫자를 얻게됩니다.
이 번호로 그룹화하고 이들 그룹의 수를 확인할 수 있습니다.
n이 목록의 단어 수이고 m이 단어의 길이 인 경우 모든 단어에 대한 '순환 그룹 번호'를 계산하는 것은 O(n*m)
정렬 O(n log n)
및 그룹화 O(n)
입니다.
import Data.List
import Data.Char
import Data.Ord
import Data.Function
groupUnsortedOn f = groupBy ((==) `on` f) . sortBy(compare `on` f)
allCycles w = init $ zipWith (++) (tails w)(inits w)
wordval = foldl (\a b -> a*256 + (fromIntegral $ ord b)) 0
uniqcycle = minimumBy (comparing wordval) . allCycles
cyclicGroupCount = length . groupUnsortedOn uniqcycle
게임의 규칙을 이해 했으므로 다시 시작하기로 결정했습니다.
길이가 3 인 고유 한 무작위로 구성된 "단어"(소문자 만)의 10000 개의 단어 사전 유사한 방식으로 길이 4, 5, 6, 7, 8의 문자열로 구성된 다른 사전이 작성되었습니다.
ClearAll[dictionary]
dictionary[chars_,nWords_]:=DeleteDuplicates[Table[FromCharacterCode@RandomInteger[{97,122},
chars],{nWords}]];
n=16000;
d3=Take[dictionary[3,n],10^4];
d4=Take[dictionary[4,n],10^4];
d5=Take[dictionary[5,n],10^4];
d6=Take[dictionary[6,n],10^4];
d7=Take[dictionary[7,n],10^4];
d8=Take[dictionary[8,n],10^4];
g
사전의 현재 버전을 확인합니다. 최상위 단어는 순환 변형 (있는 경우)과 결합됩니다. 단어와 해당 단어 out
가 처리 된 단어 의 출력 목록에 추가됩니다 . 출력 단어가 사전에서 제거됩니다.
g[{wds_,out_}] :=
If[wds=={},{wds,out},
Module[{s=wds[[1]],t,c},
t=Table[StringRotateLeft[s, k], {k, StringLength[s]}];
c=Intersection[wds,t];
{Complement[wds,t],Append[out,c]}]]
f
모든 단어 사전을 통해 실행됩니다.
f[dict_]:=FixedPoint[g,{dict,{}}][[2]]
예 1 : 실제 단어
r = f[{"teaks", "words", "spot", "pots", "sword", "steak", "hand"}]
Length[r]
{{ "steak", "teaks"}, { "hand"}, { "pots", "spot"}, { "sword", "words"}}
4
예 2 : 인공어. 길이가 3 인 문자열 사전. 먼저 타이밍. 그런 다음 사이클 단어의 수입니다.
f[d3]//AbsoluteTiming
Length[%[[2]]]
5402
단어 길이의 함수로서의 타이밍 . 각 사전에 10000 단어.
나는 결과를 O로 해석하는 방법을 특별히 모른다. 간단한 용어로, 타이밍은 대략 3 개의 문자 사전에서 4 개의 문자 사전으로 두 배가된다. 타이밍은 4 ~ 8 자로 거의 무시할 정도로 증가합니다.
이것은 2 차 시간을 피하면서 O (n)에서 수행 할 수 있습니다. 아이디어는 기본 문자열을 두 번 통과하는 완전한 원을 구성하는 것입니다. 따라서 "amazingamazin"을 완전한 원형 문자열로 구성하여 "amazing"에 해당하는 모든 순환 문자열을 확인합니다.
다음은 Java 솔루션입니다.
public static void main(String[] args){
//args[0] is the base string and following strings are assumed to be
//cyclic strings to check
int arrLen = args.length;
int cyclicWordCount = 0;
if(arrLen<1){
System.out.println("Invalid usage. Supply argument strings...");
return;
}else if(arrLen==1){
System.out.println("Cyclic word count=0");
return;
}//if
String baseString = args[0];
StringBuilder sb = new StringBuilder();
// Traverse base string twice appending characters
// Eg: construct 'amazingamazin' from 'amazing'
for(int i=0;i<2*baseString.length()-1;i++)
sb.append(args[0].charAt(i%baseString.length()));
// All cyclic strings are now in the 'full circle' string
String fullCircle = sb.toString();
System.out.println("Constructed string= "+fullCircle);
for(int i=1;i<arrLen;i++)
//Do a length check in addition to contains
if(baseString.length()==args[i].length()&&fullCircle.contains(args[i])){
System.out.println("Found cyclic word: "+args[i]);
cyclicWordCount++;
}
System.out.println("Cyclic word count= "+cyclicWordCount);
}//main
이것이 매우 효율적인지 모르겠지만 이것이 첫 번째 균열입니다.
private static int countCyclicWords(String[] input) {
HashSet<String> hashSet = new HashSet<String>();
String permutation;
int count = 0;
for (String s : input) {
if (hashSet.contains(s)) {
continue;
} else {
count++;
for (int i = 0; i < s.length(); i++) {
permutation = s.substring(1) + s.substring(0, 1);
s = permutation;
hashSet.add(s);
}
}
}
return count;
}
확실하지 않은 문제를 이해하고 있지만 이것은 댓글에 게시 된 @dude 예제와 일치합니다. 내 잘못된 분석을 수정하십시오.
문자열 목록의 주어진 N 단어에서 각 단어 W에 대해 최악의 경우 W의 모든 문자를 단계별로 실행해야합니다. 해시 작업이 일정한 시간에 완료되었다고 가정해야합니다.
use strict;
use warnings;
my @words = ( "teaks", "words", "spot", "pots", "sword", "steak", "hand" );
sub count
{
my %h = ();
foreach my $w (@_)
{
my $n = length($w);
# concatenate the word with itself. then all substrings the
# same length as word are rotations of word.
my $s = $w . $w;
# examine each rotation of word. add word to the hash if
# no rotation already exists in the hash
$h{$w} = undef unless
grep { exists $h{substr $s, $_, $n} } 0 .. $n - 1;
}
return keys %h;
}
print scalar count(@words), $/;