알고리즘/알고리즘(Java)

02. 영문자 순열 순서 알아내기

산을좋아한라쯔 2015. 3. 27. 20:03
반응형

#############

문제

#############

a,b,c,d,e,f,g,h의 8개 영문자로 만들 수 있는 순열수는 8! = 40,320이다.

이 순열들을  사전 순서로 배열하고 이들의 순서를 적어보면 아래와 같다.

 

 

문자열 

순서 

 abcdefgh

 abcdefhg

 abcdegfh

 ...

... 

 fbhacdeg

 26521 

 ...

...

 hgfedcba

40320 

 

이렇게 8개의 영문자로 만들어진 순열이 주어질 때, 이 순열이 몇 번째에 나오는지를 출력하는 프로그램을 작성하라.

 

입력: 8개의 영문자열 ex)fbhacdeg

 

출력: 입력된 영문자열의 순열 번호 ex)26521

 

###############

풀이

###############

이 문제는 n개의 문자에 대해서 순서를 바꿔서 표현할 수 있는 방법을 묻는 '문자열에 대한 순열조합(Permutation)'문제를 응용한 것이라 볼 수있다. 해서, 문자열에 대한 Permutation을 푸는 방법인 재귀호출을 써서 각 문자열조합 케이스를 만들어 내고, 문제에서 주어진 해당 문자열이 나올 때 멈춰서 이 때의 카운트를 출력하게 할 수 도 있다.

그러나 여기서는 이렇게 해당 문자열을 만들어 나가면서 비교하는 방식이 아닌, 순열의 가지는 특성을 이용해서 수치적으로 순서를 알아내고자 한다. 이렇게 하면, 각 문자열을 n! 시간만큼 만들어나가지 않아도 되므로, 쉽게 해당문자열의 번호를 알아낼 수 있다. 


8개의 문자열 중에서, 첫번째 문자 위치에 'a'가 들어간 문자열의 순서번호는 1에서 7! 까지가 될것이다. (왜냐면 첫번째를 a로 고정하고, 나머지 7개의 문자로 만들 수 있는 경우의 수는 7! 이므로)

 

만약에 첫번째 문자가 'b'라면 (7! + 1) 에서 (7! + 7!)까지, 'c'라면 (7! + 7! + 1) ~ (7! + 7! + 7!), 'd'라면 ((7! + 7! + 7!) ~ (7! + 7! + 7! + 1) ...

a: 1 ~ 7!

b: 1 + 7! ~ 2 x 7!

c: 1 + 2 x 7! ~ 3 x 7!

d: 1 + 3 x 7! ~ 4 x 7!

..

h: 1 + 7 x 7! ~ 8 x 7!  --> 8  x 7! = 8!

 

이렇게 보면, 첫번째 문자가 무엇이냐에 따라서는 규칙성을 발견할 수 있겠다. 이제 두번째 문자에 따른 규칙성을 찾아보자.

첫번째 문자가 'a' 였을 때 두번 째 문자에 따른 순서번호를 생각해보자.

만약 두번 째 문자가 'b'라면(a가 첫번째 문자로 이미 사용되었으므로, bcdefgh 중 가장 순서가 빠른 문자는 b), 순서는 1 ~ 6! 이다.

왜냐면 두 개의 문자를 고정했기에 남은 문자 6개로 만들 수 있는 경우의 수는 6! 이므로.

 

두번째 문자가 'c'의 경우는 (6! + 1) ~(6! + 6!) = ((6! + 1) ~(2 x 6!)

'd'의 경우는 (2 x 6! + 1) ~(3 x 6!)

'e'의 경우는 (3 x 6! + 1) ~(4 x 6!)

...

 

첫번째 문자가 'b'였을 때 두번 째 문자에 따른 순서를 생각해 보자.

'b'로 시작하는 경우는, 첫번째 문자가 'a'인 경우수 7! 다음이기에, 시작은 7! + 1

'a'의 경우는 (남아있는 a c d e f g h 중 가장 첫번째 문자), (7! + 1) ~ (7! + 6!)

'c'의 경우는, (7! + 6! + 1) ~ (7! + 2 x 6!)

'd'의 경우는 (7! + 2 x 6! + 1) ~ (7! + 3 x 6!)

...

 

이제 전체에 대한 규칙성을 생각해보면, 다음과 같은 공식을 생각할 수 있겠다.

orderNum = 1+ rank(arr[0] in arr[0..7]) * 7! + rank(arr[1] in arr[1..7]) * 6!) + ... + rank(arr[6] in arr[6..7]

 

만약 'abcdefgh'의 경우라면,

arr[] = {a,b,c,d,e,f,g,h}가 되고, rank(arr[0] in arr[0..7]) = 0 이다. 왜냐면 arr[0] = 'a'이고, 이는 a~h중에서 가장 처음에 있는 문자.

마찬가지로 rank(arr[1] in arr[1..7]) = rank('b' in {b~h}) = 0

이렇게 계산하면, orderNum=1이 된다.

 

만약 'bcadefgh'의 경우라면,

rank(arr[0] in arr[0..7]) = 1

rank(arr[1] in arr[1..7]) = rank('c' in {c,a,d,e,f,g,h}) = 1

rank(arr[2] in arr[2..7]) = rank('a' in {a,d,e,f,g,h}) = 0

rank(arr[3] ...) =0

 

따라서, orderNum = 1 + 1 x 7! + 2 x 6! = 1 + 5040 + 720 = 5761

 

여기서, rank는 어떻게 구할까?

일반적인 방법은, 문자들을 소팅하고, 주어진 문자가 어느 위치에 있는지를 찾는 것이다.

이렇게 해도 되겠지만, 여기서는 좀 더 탐색시간이 짧은 방법으로 해봤다.

 

abcdefgh 중에서, 한 개 문자에 대한 순서를 찾는 것은, 그 해당 문자가 'abcdefgh'의 어느 위치에 있는지를 찾으면 된다.

만약 'c'를 찾는 것이면 rank({a,b,c,d,e,f,g,h},'c')는 2가 리턴되고, {a,b,c,d,e,f,g,h}는 {a,b,0,d,e,f,g,h}가 된다.

다시 'e'를 찾게되면 rank({a,b,0,d,e,f,g,h},'e')가 되어 3이 리턴되고, 남아있는 배열은 {a,b,0,d,0,f,g,h}이 된다.

 

이렇게 하면, 한 번 찾은 문자는 0으로 바뀌고, 이를 카운트하지 않으면, 남아있는 문자들 중에서의 순서를 찾을 수 있을 것이다.

 

###############

소스

###############

 

package p1;

 

import java.util.Scanner;

 

public class CharacterSequence {

         public CharacterSequence(){

                  test();

         }

        

         private void test(){

                  String str = "hgfedcba";

                 

                  /**

                  Scanner scanner = new Scanner(System.in);

                  System.out.println("Enter character set to know the sequence order num(ex. fbhacdeg)");

                  str = scanner.next();

                  */

                                                    

                  long order = calculateOrderNum(str);

                  System.out.println("Order is "+ order);             

         }

        

         private long calculateOrderNum(String str){

                  byte[] tgtArr = str.getBytes();            

                  byte[] orderedArr = "abcdefgh".getBytes();

                 

                  long orderNum = cal(orderedArr,tgtArr);

                  return orderNum;

         }

        

         /**

          * arr[0..7]

          * orderNum = 1+ rank(arr[0] in arr[0..7]) * 7! + rank(arr[1] in arr[1..7]) * 6!) + ... + rank(arr[6] in arr[6..7]

          * @param orderedArr

          * @param tgtArr

          * @return

          */

         private long cal(byte[] orderedArr, byte[] tgtArr){

                  byte[] arr = new byte[orderedArr.length];

                  System.arraycopy(orderedArr, 0, arr, 0, arr.length);

                 

                  int i=0;

                  long orderNum=1;

                  do{

                           orderNum += (rank(arr,tgtArr[i]) * factorial(7-i));

                  }while(i++ < 6);

                 

                  return orderNum;

         }

        

         private int rank(byte[] arr, byte c){

                  int i=0;

                  int rank=0;

                  do{

                           if(arr[i] == 0){

                                   continue;                                  

                           }else if(c == arr[i]){                              

                                   arr[i] = 0;

                                   break;

                           }else{                            

                                   rank++;

                           }

                  }while(i++ < arr.length);

                                  

                  return rank;

         }

        

         private long factorial(long n){

                  if(n<=0) return 0;

                  if (n==1) return 1;

                  return (n*factorial(n-1));

         }

 

         public static void main(String[] args) {

                  new CharacterSequence();

                 

         }

 

}

 

-끝-

 


반응형