안녕하세요. Message입니다.

오늘은 알고스팟의 WILDCARD(와일드카드) 문제를 Java로 풀이하는 포스팅입니다.

문제링크 : https://algospot.com/judge/problem/read/WILDCARD

 

 

1. 문제파악

이번 문제에서의 어려운 점은 교재에도 나와 있듯이 "" 문자를 어떻게 해결하느냐 입니다.

일반 문자와 "?" 문자는 1:1 매칭으로 해결되기 때문에 어려운점은 없습니다.

하지만 "" 문자가 나올 경우 크게 두가지 정도를 생각해야 합니다.

  ① "*" 문자가 파일명의 몇번째 인덱스까지 대응되는지 계산하는 로직

  ② "*******a" : "ab" 케이스처럼 많은 중복 계산이 발생하는 경우

1번은 어느정도 고민이 필요한 부분 같습니다만,  2번의 경우에는 동적계획법을 이용하면 될 것같습니다.

 

 

2. 일반문자, "?" 문자 매칭

일반문자와 "?" 문자는 1:1로 매칭해서 인덱스만 올려주면 되기 때문에 크게 어려울 건 없습니다.

하지만 이것을 반복문으로 구현할지, 재귀형태로 구현할지 선택지가 나뉘게 됩니다.

반복문으로 구현하고자 한다면, 아래와 같이 while문의 조건만 잘 성정해주면 문제 없습니다.

이후에 왜 반복문을 탈출했는지(문자 불일치, 길이초과, '*' 문자를 만난 경우 등) 확인해주면 됩니다. 

1
2
3
4
5
while(wIdx < wc.length && fnIdx < fn.length
    && (wc[wIdx] == '?' || wc[wIdx] == fn[fnIdx]) ){
        ++wIdx;
        ++fnIdx;
}
cs

 

재귀로 구현하고자 할 경우에는 다음 호출되는 함수에 증가된 인덱스를 넣어주면 간단하게 처리할 수 있습니다.

교재에서는 반복문보다 재귀로 구현할 경우 시간 복잡도 측면에서 유리하다고 나와 있네요.

(잘 이해는 안가지만요..)

1
2
3
if(wIdx < wc.length && fnIdx < fn.length)
    if(wc[wIdx] == '?' || wc[wIdx] == fn[fnIdx])
        return cache[wIdx][fnIdx] = match(wIdx+1, fnIdx+1);
cs

 

Tip. for문에서 전위/후위 증감연산자

for문에서 전위/후위 증감연산자는 동일한 기능을 수행합니다.

하지만 내부적인 동작원리가 다르기 때문에 전위 증감연산자를 사용하는 것이 좋습니다.

  - 전위 증감 연산자 : ① i=i+1 ② return i

  - 후위 증감 연산자 : ① const int temp=i ② i=i+1 ③ return temp

차이점을 살펴보면, 후위 증감 연산자의 경우 임시 변수인 temp를 만들어서 리턴합니다.

따라서 후위 증감 연산자를 사용할 경우 반복문 크기가 커질수록 비효율적입니다.

 

 

3. "*" 문자 처리

"*" 문자를 처리하는 방법도 반복문과 재귀 두가지로 구현할 수 있습니다.

반복문으로 구현할 경우 와일드카드의 인덱스는 1을 높여주어서 "*" 다음의 문자를 입력합니다.

그리고 일반 문자는 파일명의 길이를 넘지 않을 때 까지(fIdx+i<=fn.length) 1씩 증가시켜서 비교합니다. 

1
2
3
4
5
6
if(wc[wIdx] == '*'){
    for(int i=0; fIdx+i<=fn.length++i){ 
        if(match(wIdx+1, fIdx+i)) 
            return true;
    }
}
cs

 

재귀로 구현하는 케이스는 for문을 재귀함수로 고치는 과정에서 if문 안에 함수가 2개가 들어갑니다.

1
2
3
4
5
if (wc[wIdx] == '*') {  
    if (match(wIdx + 1, fnIdx) == 1 ||   
       (fnIdx < fn.length && match(wIdx, fnIdx + 1== 1))  
            return cache[wIdx][fnIdx] = 1;  
}
cs

 

즉, 2가지 중에 한가지만 만족해도 캐쉬에 값을 저장하고 1값을 리턴합니다.

  ① match(wIdx + 1, fnIdx) == 1 : 와일드 카드의 "*" 다음 문자가 파일명과 일치하는 경우 ex) r*ed vs red

  ② match(wIdx, fnIdx + 1== 1 : 와일드 카드는 변동 없고, 파일명의 인덱스만 증가 ex) r*ed vs rrrrrrrrrrrrrrred

저는 반복문을 재귀로 수정하면서 2번이 왜 필요한지 약간 헷갈렸습니다만,

만약 1번만 존재한다면 다음 호출되는 재귀함수에서 "*" 다음 문자와 파일명 문자가 불일치 할 경우 바로 종료되버립니다.

따라서 와일드카드의 인덱스를 유지하면서 파일명의 인덱스만 높여주는 부분도 필요합니다.

즉, 1번이 충족한다면 다시 위에서 구현한 1:1 매칭 로직으로 갈 것이고,

2번의 경우에는 와일드카드의 인덱스를 증가시키지 않았으니 다시 if (wc[wIdx] == '*') 구문을 만나면서

결과적으로 파일명의 인덱스만 증가한 효과를 볼 수 있습니다.

 

 

3. 동적계획법 적용

위의 코드에서도 재귀 호출 부분에 적용되어 있지만, 

동적계획법인 메모이제이션을 적용하여 구현한 코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package Algospot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;
 
public class Main {
 
    char[] wc;    // 와일드카드 문자열
    char[] fn;    // 파일이름 문자열
    ArrayList<String> fileNameArray;
    
    /* 동적프로그래밍 저장 배열
     * -1 : 계산되지 않음
     *  1 : 일치
     *  0 : 불일치 */
    int[][] cache;
    
    
    Main(String wildCard, ArrayList<String> fileNameArray){
        this.wc = wildCard.toCharArray();
        this.fileNameArray = fileNameArray;
        this.cache = new int[101][101];
    }
    
    public int match(int wIdx, int fnIdx){
        
        // 동적 프로그래밍
        if(cache[wIdx][fnIdx] != -1)
            return cache[wIdx][fnIdx];
        
        // 1:1 대응, 일치하면 인덱스 증가 후 재귀호출
        if(wIdx < wc.length && fnIdx < fn.length)
            if(wc[wIdx] == '?' || wc[wIdx] == fn[fnIdx])
                return cache[wIdx][fnIdx] = match(wIdx+1, fnIdx+1);
        
        // 와일드카드와 파일명 모두 끝에 도달, 파일명도 끝에 도달했어야 일치
        if(wIdx == wc.length)
            return cache[wIdx][fnIdx] = (fnIdx == fn.length)? 1 : 0;
 
        // * 문자를 만난 경우
        if (wc[wIdx] == '*') {
            if (match(wIdx + 1, fnIdx) == 1 || 
               (fnIdx < fn.length && match(wIdx, fnIdx + 1== 1))
                return cache[wIdx][fnIdx] = 1;
        }
        
        return cache[wIdx][fnIdx] = 0;
    }
    
    public void solve(){
        
        ArrayList<String> resFileName = new ArrayList<String>();
        for(int i=0; i<fileNameArray.size(); i++){
            
            // 캐쉬 초기화
            for(int[] arr : this.cache)
                Arrays.fill(arr, -1);
            
            String temp = fileNameArray.get(i);
            fn = temp.toCharArray();
            if(match(0,0== 1)
                resFileName.add(temp);
        }
        
        // 정렬 후 출력
        Collections.sort(resFileName);
        for(String str : resFileName)
            System.out.println(str);
    }
    
    
    public static void main(String[] args) {
        
        Scanner sc = new Scanner(System.in);
        int numTextCase = sc.nextInt();
        
        for(int i =0; i<numTextCase; i++){
            
            String wildcard = sc.next();
            int numFileName = sc.nextInt();
            
            ArrayList<String> fileNameArray = new ArrayList<String>();
            for(int j=0; j<numFileName; j++)
                fileNameArray.add(sc.next());
            
            Main main = new Main(wildcard, fileNameArray);
            main.solve();
        }
    }
}
cs

 

 

4. 마무리

C++과는 달라서 그런진 모르겠지만, Java에서는 int 형과 boolean 형의 캐스팅이 안되더군요

따라서 리턴하면서 [ cache에 값을 저장하기 ② fnIdx가 fn.length와 일치하는지 확인 ]

2가지 작업을 한줄에 구현하려면 2번과 같이 구현해야 합니다.

   return cache[wIdx][fnIdx] = (fnIdx == fn.length); ==> 캐스팅에러 발생, 함수 리턴형이 boolean일때 가능

   return cache[wIdx][fnIdx] = (fnIdx == fn.length)? 1 : 0;

 

그리고 교재에서는 코드를 최적화 하기 위해 아래와 같이 if문에 재귀함수 호출이 여러개 들어가는 경우가 많은데

if (match(wIdx + 1, fnIdx) == 1 || (fnIdx < fn.length && match(wIdx, fnIdx + 1== 1))

저도 빨리 이런 효율적인 코드에 익숙해져야 겠군요...

 

 

posted By Message

Commit your way to the LORD, trust in him and he will do this. [PSALms 37:5]

redScreen.tistory.com Blog :( 

 

posted by Red_Message

안녕하세요. Message입니다.

오늘은 알고스팟의 JUMPGAME(외발뛰기) 문제를 Java로 풀이하는 포스팅입니다.

문제링크 : https://algospot.com/judge/problem/read/JUMPGAME

 

 

1. 재귀를 이용한 완전탐색 + 동적 계획법 

이 문제를 푸는 포인트는 재귀를 이용한 완전탐색을 구현한 뒤에

이미 탐색한 결과를 재활용하는 메모이제이션(Memoization)을 이용하여 탐색 횟수를 줄여야합니다.

만약 동적 계획법을 적용하지 않았을 경우에는 아래처럼 엄청난 차이가 발생하게 됩니다.

당연히 제한 시간인 2초를 넘겨버리기 때문에 '시간초과' 문구가 뜨게됩니다.

  

 

2. Java로 구현 

언제나 말로 풀어쓴 문제를 코드화 하는건 익숙해지기 전까지 참 어려운 일인듯 합니다.

어느정도 머리로 이해가 됐어도 막상 구현하려고 하면 손이 얼음이 되니까요.

아래 2가지를 먼저 구상하고 코드를 구현하면 어느정도 길이 잡힐 듯 싶네요.

1. 지속적으로 호출되는 다음 재귀 함수에 어떤 값을 넣어줄 것인가? -> 게임판 x, y 좌표값

2. 재귀함수를 탈출하는 조건은 무엇인가?

   - 목표가 달성되었을 경우 --> 함수의 인자로 받은 좌표값이 게임판의 끝인 경우

   - 예외가 발생한 경우 --> 함수의 인자로 받은 좌표값이 게임판을 벗어난 경우

3. 무엇을 리턴할 것인가? --> 목표에 도달했으면 True, 예외가 발생한 경우 False

 

이를 바탕으로 구현한 코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package Algospot;
import java.util.Arrays;
import java.util.Scanner;
 
 
public class Main {
    int[][] matrix;
    int[][] cache;
    int n;
    
    Main(int[][] matrix){
        n = matrix.length;
        this.matrix = matrix;
        this.cache = new int[n][n];
        for(int[] arr : this.cache)
            Arrays.fill(arr, -1);
    }
    
    public boolean solve(int x, int y){
        
        // 게임판의 범위를 벗어남
        if(x >= n || y >= n) return false;
        
        // 목표지점에 도달
        if(x==n-1 && y==n-1return true;
        
        // 메모이제이션
        if(cache[x][y] == 1return false;
        else cache[x][y] = 1;
        
        int jump = matrix[x][y];
        return solve(x+jump, y) || solve(x, y+jump);
    }
    
    
    public static void main(String[] args) {
        
        int[][] matrix;
        int n;
        
        Scanner sc = new Scanner(System.in);
        int numTestCase = sc.nextInt();
        
        for(int i = 0; i<numTestCase; i++){
            n = sc.nextInt();
            matrix = new int[n][n];
            for(int x=0; x<n; x++){
                for(int y=0; y<n; y++){
                    matrix[x][y] = sc.nextInt();
                }
            }
            
            Main main = new Main(matrix);
            boolean res = main.solve(0,0);
            System.out.println(res? "YES":"NO");
        }
    }
}
 
cs

 

개인적으로 구현한 코드와 알고리즘 문제해결 전략 책에 나온 답안이 얼추 비슷하긴 했지만

책에 소개된 리턴 방식이 좀더 간결해 보여서 적용했습니다.

==> return solve(x+jump, y) || solve(x, y+jump);

함수 리턴값이 Boolean 형이라면 이런 패턴을 기억하고 있다가 써먹으면 좋을 듯 싶네요.

아래 구문은 구글링을 하다보니 알게된 문법입니다. 알아두면 간결한 코드를 짜는게 도움이 될 것 같습니다.

System.out.println(res? "YES":"NO");

 

언제나 결과만 보면 뭐 이런걸로 고민을 했나 싶을 정도로 간단해보이네요..ㅜ

이만 포스팅을 마칩니다.

 

 

posted By Message

Commit your way to the LORD, trust in him and he will do this. [PSALms 37:5]

redScreen.tistory.com Blog :( 

 

posted by Red_Message

안녕하세요. Message입니다.

오늘은 순열 알고리즘을 Java로 구현하는 부분에 대해 간단히 포스팅 하고자 합니다.

개인적으로 조합보다 순열 알고리즘을 이해하는데 좀 더 시간이 걸렸고, 생각해야 될 부분이 다소 있다고 생각되네요.

 

 

1. n개 중에서 r개를 선택한다.

순열은 n개에서 r개를 선택하는 알고리즘입니다.

단순히 3~4개를 가지고 경우의 수를 만드는 케이스라면 알고리즘까지 구현할 필요성을 전혀 못느끼겠으나,

10개만 넘어가도 경우의 수가 엄청나게 불어나기 때문에 알고리즘 구현이 필수적입니다.

순열은 아래와 같이 2가지로 분류할 수 있습니다.

=====

중복을 허용하지 않고 n개중에 r개 뽑기

중복을 허용하여 n개중에 r개 뽑기

=====

 

중복 허용과 비허용이 그저 순열 알고리즘을 더 어렵게 만드는 부분이라고 생각될 수 있지만

실제로 선택해야 될 아이템들에 따라 개발자에게는 매우 중요한 요소가 될 수 있습니다.

 

 

2. 재귀함수 + ArrayList를 이용한 순열

순열의 점화식을 살펴보면 아래와 같습니다.

0 ≤ r ≥ n을 만족하는 정수 n, r이 있을 때, 순서를 고려하여 r개의 원소를 뽑음

=====

P(n, r) = n(n-1)(n-2) ... (n-r+1)

P(n, r) = n! / (n-r)!

=====

 

단순히 개수를 고려한다면 당장 n에 숫자를 대입하여 풀 수 있겠으나,

이를 코드로 구현하기 위해서는 집합에 있는 특정 아이템을 일일이 매칭해주어야 하므로

우리는 코딩의 관점에서 위의 점화식의 n을 자료구조 안에 들어있는 원소로 생각할 필요가 있습니다.

예를 들면 아래와 같이 생각을 확장할 수 있겠습니다.

=====

① P(5, 3) = 5 × 4 × 3

② P(5, 3) = 5개의 아이템중 1개를 선택 × 4개의 아이템중 1개를 선택 × 3개의 아이템중 한개를 선택

③ P(5, 3) =

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '2' 선택 × [ 3, 4, 5 ] 중에서 '3' 선택 = 123

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '2' 선택 × [ 3, 4, 5 ] 중에서 '4' 선택 = 124

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '2' 선택 × [ 3, 4, 5 ] 중에서 '5' 선택 = 125

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '3' 선택 × [ 2, 4, 5 ] 중에서 '2' 선택 = 132

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '3' 선택 × [ 2, 4, 5 ] 중에서 '4' 선택 = 134

[ 1, 2, 3, 4, 5 ] 중에서 '1' 선택 × [ 2, 3, 4, 5 ] 중에서 '3' 선택 × [ 2, 4, 5 ] 중에서 '5' 선택 = 135

....(모든 경우의 수를 구할때 까지 반복)

※ 선택된 아이템은 바로 출력하거나, 모든 아이템이 fix된 이후에 출력해도됨, 확정이라고 생각해도 무방함

=====

 

무언가 반복적인 패턴이 보인다면, 이를 재귀적으로 구현할 수 있을겁니다.

'주어진 자료구조에서 원소를 선택' 하는 기능을 구현하고,

다음 호출되는 재귀함수에 선택한 아이템을 뺀 나머지 집합을 넘겨주면 될 것 같습니다.

주의해야할 점은, 재귀함수가 끝난 이후에는 그 다음 계산을 위하여 remove한 원소를 다시 원래 index에 넣어줘야 하는 점입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.ArrayList;
import java.util.Arrays;
 
public class Permutation {
 
    private int n; // nPr의 n
    private int r; // nPr의 r
    private ArrayList<Integer> itemList;
    private int[] res;
    
    // 초기화
    public Permutation(int[] intArr, int r){
        this.r = r;             // nPr의 r
        this.n = intArr.length// nPr의 n
        this.res = new int[r];  // 결과값 배열
        
        // 아이템이 들어갈 리스트
        itemList = new ArrayList<Integer>();
        for(int item : intArr)
            itemList.add(item);
    }
    
    public void perm(int depth){
        perm(itemList, 0);
    }
 
    public void perm(ArrayList<Integer> itemList, int depth) {
        
        // depth가 0부터 시작했을 경우 depth == r에서 리턴
        if (depth == r) {
            System.out.println(Arrays.toString(res));
            return;
        }
        
        for (int i = 0; i < n-depth; i++){
            res[depth] = itemList.remove(i); // 아이템 선택 + 리스트에서 제거
            perm(itemList, depth+1);         // 재귀호출
            itemList.add(i, res[depth]);     // 제거된 아이템 복원
        }
    }
    
    public static void main(String[] args) {
 
        int r = 3;
        int[] arr = {1,2,3,4,5};
        
        Permutation main = new Permutation(arr, r);
        main.perm(0);
    }
}
cs

 

 

그렇다면 여기서 중복을 허용한 코드는 어떻게 해야할까요?

삭제하고 다시 복원해주는 부분을 없애고, i의 범위를 0부터 n까지로 수정해주면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public void perm(ArrayList<Integer> itemList, int depth) {
        
    // depth가 0부터 시작했을 경우 depth == r에서 리턴
    if (depth == r) {
        System.out.println(Arrays.toString(res));
        return;
    }
    
    for (int i = 0; i < n; i++){
        res[depth] = itemList.get(i); // 아이템 선택 + 리스트에서 제거
        perm(itemList, depth+1);            // 재귀호출
    }
}
cs

 

 

2. 재귀함수 + 위치변경(swap)을 이용한 순열

위의 코드는 제가 직관적으로 구현한 코드지만, 구글링을 해보면 swap 메소드를 이용한 순열 알고리즘이 많습니다.

요소와 요소를 자리 바꾸고, depth 변수를 이용하여 선택할 수 있는 요소의 범위를 조절합니다.

 

=====

P(4, 2) =

(첫째[depth]↔첫째(i) swap) [ 1, 2, 3, 4 ] 중에서 '1' 선택 × (둘째[depth]↔둘째(i) swap) [ 1, 2, 3, 4 ] 중에서 '2' 선택 = 12

(첫째[depth]↔첫째(i) swap) [ 1, 2, 3, 4 ] 중에서 '1' 선택 × (둘째[depth]↔셋째(i) swap) [ 1, 3, 2, 4 ] 중에서 '3' 선택 = 13

(첫째[depth]↔첫째(i) swap) [ 1, 2, 3, 4 ] 중에서 '1' 선택 × (둘째[depth]↔넷째(i) swap) [ 1, 4, 3, 2 ] 중에서 '4' 선택 = 14

(첫째[depth]↔둘째(i) swap) [ 2, 1, 3, 4 ] 중에서 '2' 선택 × (둘째[depth]↔둘째(i) swap) [ 2, 1, 3, 4 ] 중에서 '1' 선택 = 21

(첫째[depth]↔둘째(i) swap) [ 2, 1, 3, 4 ] 중에서 '2' 선택 × (둘째[depth]↔셋째(i) swap) [ 2, 3, 1, 4 ] 중에서 '3' 선택 = 23

(첫째[depth]↔둘째(i) swap) [ 2, 1, 3, 4 ] 중에서 '2' 선택 × (둘째[depth]↔넷째(i) swap) [ 2, 4, 31 ] 중에서 '4' 선택 = 24

(첫째[depth]↔셋째(i) swap) [ 3, 2, 1, 4 ] 중에서 '3' 선택 × (둘째[depth]↔둘째(i) swap) [ 3, 2, 1, 4 ] 중에서 '2' 선택 = 32

(첫째[depth]↔셋째(i) swap) [ 3, 2, 1, 4 ] 중에서 '3' 선택 × (둘째[depth]↔셋째(i) swap) [ 3, 1, 2, 4 ] 중에서 '1' 선택 = 31

(첫째[depth]↔셋째(i) swap) [ 3, 2, 1, 4 ] 중에서 '3' 선택 × (둘째[depth]↔넷째(i) swap) [ 3, 4, 12 ] 중에서 '4' 선택 = 34

※ 본인과 본인이 swap 하는 부분이 없으면, 최초 케이스가 누락됨 ex) [1, 2, 3, 4]에서 swap되어 [2, 1, 3, 4] 부터 계산 시작

※ depth가 0부터 시작했을 때, depth == r 이 되면 r개 만큼 출력함, 위에서는 r이 2이므로, index가 0~1까지인 원소 2개만 출력

=====

 

위와 같이 구현하기 위해서는

for문을 이용하여 순차적으로 현재 depth 원소와 i 번째 원소를 swap 한 뒤에 (자연스럽게 swap 되어 첫번째로 옮겨진 원소가 선택됨)

다음으로 호출되는 재귀 함수에는 증가된 인덱스(depth+1)를 넘겨주면 범위가 좁혀지면서 문제를 해결할 수 있습니다.

여기서도 주의해야할 점은, swap 메소드 때문에 원소들의 순서가 변경되기 때문에 이를 다시 되돌려 주어야 합니다.

이를 고려한 Java 코드는 아래와 같습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.Arrays;
 
public class Permutation {
 
    private int n; // nPr의 n
    private int r; // nPr의 r
    private int[] res;
    
    // 초기화
    public Permutation(int n, int r){
        this.n = n;
        this.r = r;
        res = new int[r];
    }
    
    public void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
 
    public void perm(int[] arr, int depth) {
        
        // depth가 0부터 시작했을 경우 depth == r에서 리턴
        if (depth == r) {
            System.out.println(Arrays.toString(res));
            return;
        }
        
        for (int i = depth; i < n; i++) {
            swap(arr, depth, i);     // 스왑
            res[depth] = arr[depth]; // 선택된 원소 저장
            perm(arr, depth +1);     // 재귀호출
            swap(arr, depth, i);     // 복원
        }
    }
    
    public static void main(String[] args) {
 
        int r = 3;
        int[] arr = {1,2,3,4,5};
        
        Permutation main = new Permutation(arr.length, r);
        main.perm(arr, 0);
    }
}
 
 
cs

 

 

그렇다면 중복을 허용하는 순열을 알아봅시다.

위에서는 for문 제어부에 있는 i 초기값이 depth 였기 때문에, 깊이가 증가할수록 선택할 수 있는 원소의 개수가 줄었습니다. 

==>  for(int i=depth; i<n; i++)

따라서 i의 초기값을 0으로 변경해주면 depth가 올라가도 선택할 수 있는 원소의 개수가 동일하기 때문에 중복을 허용하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void perm(int[] arr, int depth) {
        
    // depth가 0부터 시작했을 경우 depth == r에서 리턴
    if (depth == r) {
        System.out.println(Arrays.toString(res));
        return;
    }
        
    //for (int i = depth; i < n; i++)
    for (int i = 0; i < n; i++) {
        swap(arr, depth, i);     // 스왑
        res[depth] = arr[depth]; // 선택된 원소 저장
        perm(arr, depth +1);     // 재귀호출
        swap(arr, depth, i);     // 복원
    }
}
cs

 

출력해보면 swap 메소드의 영향으로 111, 112, 113... 으로 순서가 이쁘게 출력되진 않지만

중복을 허용하는 경우의 수가 모두 출력됩니다.

이만 포스팅을 마칩니다.

 

 

 

posted By Message

Commit your way to the LORD, trust in him and he will do this. [PSALms 37:5]

redScreen.tistory.com Blog :(

 

posted by Red_Message