문자열이 완전히 동일한 하위 문자열로 만들어 졌는지 어떻게 확인합니까?


128

I 문자열을 취하는 함수를 작성해야하고, 그 반환해야 true또는 false입력이 반복되는 문자 시퀀스를 구성하는지 여부에 따라. 주어진 문자열의 길이는 항상보다 길고 1문자 시퀀스에는 적어도 하나의 반복이 있어야합니다.

"aa" // true(entirely contains two strings "a")
"aaa" //true(entirely contains three string "a")
"abcabcabc" //true(entirely containas three strings "abc")

"aba" //false(At least there should be two same substrings and nothing more)
"ababa" //false("ab" exists twice but "a" is extra so false)

아래 기능을 만들었습니다.

function check(str){
  if(!(str.length && str.length - 1)) return false;
  let temp = '';
  for(let i = 0;i<=str.length/2;i++){
    temp += str[i]
    //console.log(str.replace(new RegExp(temp,"g"),''))
    if(!str.replace(new RegExp(temp,"g"),'')) return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

이것을 확인하는 것은 실제 문제의 일부입니다. 이와 같은 비효율적 인 솔루션을 감당할 수 없습니다. 우선, 문자열의 절반을 반복합니다.

두 번째 문제는 replace()각 루프에서 사용하므로 속도가 느려진다는 것입니다. 성능과 관련하여 더 나은 솔루션이 있습니까?


19
이 링크는 당신에게 유용 할 수 있습니다. 나는 항상 알고리즘 문제에 대한 좋은 소스로 geekforgeeks을 찾을 - geeksforgeeks.org/...
— Leron_says_get_back_Monica

9
이것을 빌려 프로그래밍 골프 교환 사이트에서 코딩 과제로 삼을 수 있습니까?
— ouflak

7
@ouflak 당신이 할 수 있습니다.
— Maheer Ali


24
@Shidersz 신경망을 사용하는 것은 대포를 사용하여 모기를 쏘는 것과 같은 느낌입니다.
— JAD

답변:


186

이와 같은 문자열에 대한 멋진 작은 정리가 있습니다.

문자열은 문자열 자체가 사소한 회전 인 경우에만 여러 번 반복되는 동일한 패턴으로 구성됩니다.

여기서 회전이란 문자열 앞쪽에서 몇 개의 문자를 삭제하고 뒤쪽으로 이동하는 것을 의미합니다. 예를 들어 문자열 hello을 회전하여 다음 문자열 중 하나를 형성 할 수 있습니다.

hello (the trivial rotation)
elloh 
llohe 
lohel 
ohell 

이것이 왜 작동하는지 확인하려면 먼저 문자열이 문자열 w의 k 반복 사본으로 구성되어 있다고 가정하십시오. 그런 다음 반복 된 패턴의 첫 번째 사본 (w)을 끈 앞면에서 삭제하고 뒷면에 ​​타킹하면 같은 문자열이 나타납니다. 반대 방향은 약간 까다로울 수 있지만 문자열을 회전하고 시작한 항목을 다시 가져 오면 동일한 회전의 패턴을 가진 여러 복사본으로 문자열을 바둑판 식으로 배열하기 위해 회전을 반복적으로 적용 할 수 있다는 아이디어입니다. 회전을 위해 끝으로 이동 해야하는 문자열).

이제 질문은 이것이 사실인지 확인하는 방법입니다. 이를 위해 사용할 수있는 또 다른 아름다운 정리가 있습니다.

x와 y가 같은 길이의 문자열이면 x가 yy의 하위 문자열 인 경우에만 x는 y의 회전입니다.

예를 들어, 다음과 같이 lohel회전 함을 알 수 있습니다 hello.

hellohello
   ^^^^^

우리의 경우 모든 문자열 x는 항상 xx의 하위 문자열이됩니다 (x의 각 복사본마다 한 번씩 두 번 나타남). 따라서 기본적으로 문자열 x가 첫 번째 또는 중간 문자와 일치하지 않고 문자열 x가 xx의 하위 문자열인지 확인하면됩니다. 여기에 하나의 라이너가 있습니다.

function check(str) {
    return (str + str).indexOf(str, 1) !== str.length;
}

indexOf빠른 문자열 일치 알고리즘을 사용하여 구현 한다고 가정하면 시간 O (n)에서 실행되며 여기서 n은 입력 문자열의 길이입니다.

도움이 되었기를 바랍니다!


13
아주 좋아요! jsPerf 벤치 마크 페이지에 추가했습니다 .
— user42723

10
@ user42723 쿨! 정말, 정말 빠른 것 같습니다.
— templatetypedef

5
참고 : "문자열은 동일한 패턴으로 여러 번 반복 된 경우에만 문자열 자체가 중요하지 않습니다."라는 문구를 뒤집을 때까지이 문장을 믿기가 힘들었습니다. 그림을 이동.
— Axel Podehl

11
그 정리에 대한 언급이 있습니까?
— HRK44

4
첫 번째 진술은 dox.org/10.1016/j.tcs.2008.04.020의 " Lemma 2.3 : x와 x의 회전이 같으면 x는 반복적"과 같다고 생각 합니다. 또한보십시오 : stackoverflow.com/a/2553533/1462295
— BurnsBA

67

캡처 그룹 과 역 참조를 통해이를 수행 할 수 있습니다 . 처음 캡처 된 값이 반복되는지 확인하십시오.

function check(str) {
  return /^(.+)\1+$/.test(str)
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

위의 RegExp에서 :

  1. ^과 $에 대한 스탠드 시작과 끝 앵커 위치를 예측한다.
  2. (.+)모든 패턴을 캡처하고 값을 제외합니다 (제외 \n).
  3. \1첫 번째 캡처 된 값의 역 참조이며 캡처 된 값의 \1+반복을 확인합니다.

여기에 정규식 설명

RegExp 디버깅의 경우 https://regex101.com/r/pqlAuP/1/debugger

성능 : https://jsperf.com/reegx-and-loop/13


2
이 줄이 무엇을하고 있는지 설명해 주실 수 있습니까? /^(.+)\1+$/.test(str)
— Thanveer Shah

34
또한이 솔루션의 복잡성은 무엇입니까? 나는 확실하지 않지만 OP가 가지고있는 것보다 훨씬 빠르지는 않습니다.
— Leron_says_get_back_Monica 19

8
@PranavCBalan 나는 알고리즘에 익숙하지 않기 때문에 주석 섹션에 글을 쓴다. 그러나 몇 가지 언급 할 사항이 있습니다. OP에는 이미 작동하는 솔루션이 있으므로 더 나은 성능을 제공 할 솔루션을 요구하고 있으며 솔루션이 어떻게 성능을 발휘하는지 설명하지 않았습니다. 짧다는 것이 더 빠른 것은 아닙니다. 또한, 당신이 준 링크에서 : If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).당신이 쓴대로 역 참조를 사용하고 있으므로 여전히 O (n)입니까?
— Leron_says_get_back_Monica 19

5
다른 문자와 같은 방식으로 줄 바꿈 문자를 일치시켜야하는 경우 [\s\S]대신 사용할 수 있습니다 .. 점 문자는 개행에서 일치하지 않습니다. 대안은 모든 공백 문자와 공백이 아닌 문자를 검색합니다. 즉, 개행 문자가 일치합니다. (이것은보다 직관적 인 것보다 빠릅니다 (.|[\r\n]).) 그러나 문자열에 개행 문자가 포함되어 있지 않으면 단순 .이 가장 빠릅니다. dotall 플래그 가 구현 되면 훨씬 간단 해집니다 .
— HappyDog

2
하지가 /^(.+?)\1+$/조금 더 빨리? (12 단계 vs 20 단계)
— 온라인 Thomas

29

아마도 가장 빠른 알고리즘 접근법은 선형 시간에 Z 함수 를 작성하는 것입니다.

이 문자열의 Z- 함수는 길이 n의 배열이며, i 번째 요소는 s의 첫 문자와 일치하는 위치 i에서 시작하여 최대 문자 수와 같습니다.

즉, z [i]는 s와 i에서 시작하는 접미사 사이의 가장 긴 공통 접두사 길이입니다.

참조를위한 C ++ 구현 :

vector<int> z_function(string s) {
    int n = (int) s.length();
    vector<int> z(n);
    for (int i = 1, l = 0, r = 0; i < n; ++i) {
        if (i <= r)
            z[i] = min (r - i + 1, z[i - l]);
        while (i + z[i] < n && s[z[i]] == s[i + z[i]])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    return z;
}

JavaScript 구현
최적화 추가-z-array의 절반 및 조기 종료 빌드

function z_function(s) {
  var n = s.length;
  var z = Array(n).fill(0);
  var i, l, r;
  //for our task we need only a half of z-array
  for (i = 1, l = 0, r = 0; i <= n/2; ++i) {
    if (i <= r)
      z[i] = Math.min(r - i + 1, z[i - l]);
    while (i + z[i] < n && s[z[i]] == s[i + z[i]])
      ++z[i];

      //we can check condition and return here
     if (z[i] + i === n && n % i === 0) return true;
    
    if (i + z[i] - 1 > r)
      l = i, r = i + z[i] - 1;
  }
  return false; 
  //return z.some((zi, i) => (i + zi) === n && n % i === 0);
}
console.log(z_function("abacabacabac"));
console.log(z_function("abcab"));

그런 다음 in을 나누는 인덱스를 확인해야합니다 . 당신이 그런 발견하면 i것을 i+z[i]=n다음 문자열 s길이로 압축 할 수 있습니다 i당신은 반환 할 수 있습니다 true.

예를 들어

string s= 'abacabacabac'  with length n=12`

Z- 배열은

(0, 0, 1, 0, 8, 0, 1, 0, 4, 0, 1, 0)

우리는 그것을 찾을 수 있습니다

i=4
i+z[i] = 4 + 8 = 12 = n
and
n % i = 12 % 4 = 0`

그래서 s길이 4의 3 회 반복 하위 문자열로 표현 될 수 있습니다.


3
return z.some((zi, i) => (i + zi) === n && n % i === 0)
— Pranav C Balan

2
Salman A와 Pranav C Balan에 JavaScript를 추가해 주셔서 감사합니다.
— MBo

1
추가 반복을 피하여 대체 방법const check = (s) => { let n = s.length; let z = Array(n).fill(0); for (let i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r) z[i] = Math.min(r - i + 1, z[i - l]); while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; // check condition here and return if (z[i] + i === n && n % i === 0) return true; if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1; } // or return false return false; }
— Pranav C Balan

2
z- 함수를 사용하는 것은 좋은 생각이지만 '정보가 무겁다'는 정보는 많이 사용되지 않습니다.
— Axel Podehl

@Axel Podehl 그럼에도 불구하고 문자열을 O (n) 시간으로 처리합니다 (각 문자는 최대 두 번 사용됨). 어쨌든 우리는 모든 문자를 확인해야하므로 이론적으로 더 빠른 알고리즘은 없습니다 (최적화 된 내장 메소드가 성능을 능가 할 수 있음). 또한 마지막 편집에서 문자열 길이의 1/2로 계산을 제한했습니다.
— MBo

23

gnasher729의 답변을 읽고 구현했습니다. 아이디어는 반복이있는 경우 소수의 반복이 있어야한다는 것입니다.

function* primeFactors (n) {
    for (var k = 2; k*k <= n; k++) {
        if (n % k == 0) {
            yield k
            do {n /= k} while (n % k == 0)
        }
    }
    if (n > 1) yield n
}

function check (str) {
    var n = str.length
    primeloop:
    for (var p of primeFactors(n)) {
        var l = n/p
        var s = str.substring(0, l)
        for (var j=1; j<p; j++) {
            if (s != str.substring(l*j, l*(j+1))) continue primeloop
        }
        return true
    }
    return false
}

약간 다른 알고리즘은 다음과 같습니다.

function check (str) {
    var n = str.length
    for (var p of primeFactors(n)) {
        var l = n/p
        if (str.substring(0, n-l) == str.substring(l)) return true
    }
    return false
}

이 페이지에서 사용 된 알고리즘이 포함 된 jsPerf 페이지 를 업데이트했습니다 .


불필요한 검사를 건너 뛰기 때문에 정말 빠릅니다.
— Pranav C Balan

1
아주 좋은 것은 하위 문자열 호출을하기 전에 첫 번째 문자가 지정된 위치에서 다시 발생하는지 확인하는 것입니다.
— Ben Voigt

function*나와 같이 처음으로 넘어지는 사람들 에게는 일반 함수가 아닌 생성기를 선언하기위한 것입니다.
— Julien Rousé를

17

문자열 S의 길이가 N이고 하위 문자열 s의 복제본으로 구성된 다음 s의 길이는 N을 나눕니다. 예를 들어 S의 길이가 15 인 경우 하위 문자열의 길이는 1, 3 또는 5입니다.

S를 s의 (p * q) 사본으로 만들어 보자. 그런 다음 S는 또한 p 개의 사본으로 만들어집니다 (s, 반복 된 q 번). 따라서 N이 소수이거나 1 인 경우 S는 길이가 1 인 부분 문자열의 복사본으로 만 만들 수 있습니다. N이 복합 인 경우 길이가 N / p 인 부분 문자열의 소수를 검사하여 소수를 나누면됩니다. S의 길이

따라서 N = S의 길이를 결정한 다음 시간 O (sqrt (N))에서 모든 주요 요소를 찾으십시오. 요인 N이 하나만있는 경우 S가 동일한 문자열로 N 번 반복되는지 확인하고, 그렇지 않으면 각 소수 요인 p에 대해 S가 첫 번째 N / p 문자의 p 반복으로 구성되어 있는지 확인하십시오.


다른 솔루션을 확인하지는 않았지만 매우 빠릅니다. "특별한 경우가 아니기 때문에"인수 N이 하나만있는 경우 ...을 검사하고 그렇지 않으면 "을 생략 할 수 있습니다. 다른 구현 옆에 jsPerf에서 실행될 수있는 Javascript 구현을 보는 것이 좋을 것입니다.
— user42723

1
나는 이제 내 대답에
— user42723

10

재귀 함수도 매우 빠를 것이라고 생각합니다. 첫 번째 관찰은 최대 반복 패턴 길이가 총 스트링 길이의 절반이라는 것입니다. 그리고 가능한 모든 반복 패턴 길이를 테스트 할 수 있습니다 : 1, 2, 3, ..., str.length / 2

재귀 함수 isRepeating (p, str)은이 패턴이 str에서 반복되는지 테스트합니다.

str이 패턴보다 긴 경우 재귀는 첫 번째 부분 (p와 동일한 길이)이 반복 및 나머지 str의 길이 여야합니다. 따라서 str은 길이 p.length의 조각으로 효과적으로 나뉩니다.

테스트 된 패턴과 str의 크기가 같으면 재귀가 여기서 끝납니다.

길이가 다르거 나 ( "aba"및 패턴 "ab"에 대해 발생 함) 조각이 다른 경우 false가 반환되어 재귀를 전파합니다.

function check(str)
{
  if( str.length==1 ) return true; // trivial case
  for( var i=1;i<=str.length/2;i++ ) { // biggest possible repeated pattern has length/2 characters

    if( str.length%i!=0 ) continue; // pattern of size i doesn't fit
    
    var p = str.substring(0, i);
    if( isRepeating(p,str) ) return true;
  }
  return false;
}


function isRepeating(p, str)
{
  if( str.length>p.length ) { // maybe more than 2 occurences

    var left = str.substring(0,p.length);
    var right = str.substring(p.length, str.length);
    return left===p && isRepeating(p,right);
  }
  return str===p; 
}

console.log(check('aa')) //true
console.log(check('aaa')) //true 
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

성능 : https://jsperf.com/reegx-and-loop/13


1
if( str===p.repeat(str.length/i) ) return true;재귀 함수를 사용하는 대신 확인하는 것이 더 빠릅니까?
— Chronocidal

1
console.logs를 jsperf 테스트에 넣지 말고, globals 섹션 내에서 함수를 준비하고, globals 섹션에서 테스트 문자열을 준비하십시오 (죄송합니다, jsperf를 편집 할 수 없습니다)
— Salman A

@ 살만-좋은 지적. 방금 전임 도구 (Pranav C)에서 jsperf를 수정했는데 처음으로 멋진 도구 인 jsperf를 사용했습니다.
— Axel Podehl

@SalmanA : 업데이트 : jsperf.com/regex-and-loop/1 ... 정보 주셔서 감사합니다 ... 심지어 그것에 익숙하지 않습니다 (Jsperf) ... 정보 주셔서 감사합니다
— Pranav C Balan

안녕 Salman, jsperf.com/reegx-and-loop/10에 대해 대단히 감사합니다 -예, 새로운 성능 테스트는 훨씬 더 의미가 있습니다. 기능 설정은 준비 코드에 들어가야합니다.
— Axel Podehl

7

이것을 파이썬으로 작성했습니다. 나는 그것이 플랫폼이 아니라는 것을 알고 있지만 30 분의 시간이 걸렸습니다. PS => 피톤

def checkString(string):
    gap = 1 
    index= 0
    while index < len(string)/2:
        value  = [string[i:i+gap] for i in range(0,len(string),gap) ]

        x = [string[:gap]==eachVal for eachVal in value]

        if all(x):
            print("THEY ARE  EQUAL")
            break 

        gap = gap+1
        index= index+1 

checkString("aaeaaeaaeaae")

6

내 접근 방식은 gnasher729와 비슷합니다. 잠재적 인 부분 문자열 길이를 주요 초점으로 사용하지만 수학 및 프로세스 집약도가 적습니다.

L : 원래 줄의 길이

S : 유효한 부분 문자열의 잠재적 길이

L / 2에서 1의 정수 부분으로 S를 반복합니다. L / S가 정수인 경우 L / S 번 반복 된 원래 문자열의 주먹 S 문자에 대해 원래 문자열을 확인하십시오.

1이 아닌 L / 2에서 뒤로 루프하는 이유는 가능한 가장 큰 부분 문자열을 얻는 것입니다. 1에서 L / 2까지 가능한 가장 작은 부분 문자열 루프를 원할 경우. 예 : "abababab"에는 가능한 하위 문자열로 "ab"와 "abab"이 모두 있습니다. 참 / 거짓 결과에만 관심이 있다면 둘 중 어느 것이 더 빠를 것인가? 이것은 적용 할 문자열 / 하위 문자열의 유형에 따라 다릅니다.


5

다음 Mathematica 코드 는 목록이 적어도 한 번 반복되는지 거의 감지합니다. 문자열이 적어도 한 번 반복되면 true를 반환하지만 문자열이 반복 된 문자열의 선형 조합 인 경우 true를 반환 할 수도 있습니다.

IsRepeatedQ[list_] := Module[{n = Length@list},
   Round@N@Sum[list[[i]] Exp[2 Pi I i/n], {i, n}] == 0
];

반복되는 문자열에 0이되어야 "전체 길이"기여에 대한이 코드 보이지만, 문자열 accbbd은 두 개의 반복 된 문자열의 합이기 때문에 또한, 반복 된 것으로 간주 ababab하고 012012.

아이디어는 고속 푸리에 변환을 사용하고 주파수 스펙트럼을 찾는 것입니다. 다른 주파수를 살펴보면이 이상한 시나리오도 감지 할 수 있어야합니다.


4

여기서 기본 아이디어는 길이 1에서 시작하여 원래 문자열 길이의 절반에서 멈추는 잠재적 하위 문자열을 검사하는 것입니다. 우리는 원래 문자열 길이를 균등하게 나누는 하위 문자열 길이 만 봅니다 (예 : str.length % substring.length == 0).

이 구현에서는 두 번째 문자로 이동하기 전에 가능한 각 하위 문자열 반복의 첫 번째 문자를 확인하므로 하위 문자열이 길면 시간을 절약 할 수 있습니다. 전체 하위 문자열을 검사 한 후 불일치가 없으면 true를 반환합니다.

검사 할 잠재적 인 하위 문자열이 부족하면 false를 반환합니다.

function check(str) {
  const len = str.length;
  for (let subl = 1; subl <= len/2; ++subl) {
    if ((len % subl != 0) || str[0] != str[subl])
      continue;
    
    let i = 1;
    for (; i < subl; ++i)
    {
      let j = 0;
      for (; j < len; j += subl)
        if (str[i] != str[j + i])
          break;
      if (j != len)
        break;
    }
    
    if (i == subl)
      return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false


-1

JavaScript에 익숙하지 않으므로 이것이 얼마나 빠를 지 모르겠지만 여기에는 내장 만 사용하는 선형 시간 솔루션 (합리적인 내장 구현 가정)이 있습니다. 의사 코드로 알고리즘을 설명하겠습니다.

function check(str) {
    t = str + str;
    find all overlapping occurrences of str in t;
    for each occurrence at position i
        if (i > 0 && i < str.length && str.length % i == 0)
            return true;  // str is a repetition of its first i characters
    return false;
}

이 아이디어는 MBo의 답변과 비슷합니다. i길이를 나누는 각각에 대해 , 문자를 이동 한 후에도 동일하게 유지되는 경우에만 str첫 번째 i문자 가 반복 됩니다 i.

그러한 내장 기능을 사용할 수 없거나 비효율적 일 수 있습니다. 이 경우 KMP 알고리즘을 수동으로 구현하는 것이 항상 가능 하며 MBo의 답변에있는 알고리즘과 거의 동일한 코드를 필요로합니다.


OP는 반복 이 존재 하는지 알고 싶어 합니다 . 함수의 두 번째 줄은 반복 횟수를 계산 합니다. 설명해야 할 부분입니다. 예를 들어 "abcabcabc"에는 "abc"가 3 번 반복되지만 두 번째 줄 은 반복이 있는지 어떻게 확인 했 습니까?
— Lawrence

@Lawrence 나는 당신의 질문을 이해하지 못합니다. 이 알고리즘은 문자열을 경우에만 길이의 일부 제수에 대한 경우의 문자열의 반복이라는 생각을 기반으로 i, s[0:n-i] == s[i:n],, 또는 동등 s == s[i:n] + s[0:i]. 두 번째 줄에 반복이 있는지 여부를 해결해야하는 이유는 무엇입니까?
— infmagic2047

알고리즘을 이해하는지 알려 드리겠습니다. 먼저 strform에 추가 t한 다음 inside t를 찾기 위해 스캔 합니다 . 좋아, 이것은 작동 할 수있다 (나는 downvote를 철회했다). 그러나 strlen (str)에서는 선형이 아닙니다. 길이가 L이라고 가정합니다 . 그런 다음 각 위치 p = 0,1,2, ...에서 str [0..L-1] == t [p..p + L-1]이 O (L ) 시각. p의 값을 거칠 때 O (L) 검사를 수행해야하므로 O (L ^ 2)입니다. strtstr
— 로렌스

-10

간단한 아이디어 중 하나는 문자열을 ""의 하위 문자열로 바꾸는 것입니다. 텍스트가 존재하면 거짓이고 그렇지 않은 경우에도 마찬가지입니다.

'ababababa'.replace(/ab/gi,'')
"a" // return false
'abababab'.replace(/ab/gi,'')
 ""// return true


예, abc 또는 unicorn의 경우 사용자가 / abc / 또는 / unicorn /으로 확인하지 않습니다. 컨텍스트가없는 경우 죄송합니다.
— Vinod kumar G

3
질문은 더 명확 할 수 있지만 요청하는 것은 문자열이 다른 문자열의 2 개 이상의 반복으로 완전히 구성되는지 여부를 결정하는 방법입니다. 특정 하위 문자열을 검색하지 않습니다.
— HappyDog

2
질문에 약간의 설명을 추가하여 지금 명확하게해야합니다.
— HappyDog

@Vinod 이미 정규식을 사용하려는 경우 일치를 고정하고 테스트를 사용해야합니다. 조건을 확인하기 위해 문자열을 수정할 이유가 없습니다.
— Marie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.