문제
: 소문자 알파벳으로 구성된 문자열이 입력되면, 해당 문자열에서 중복된 알파벳을 제거한 문자열을 출력하기.
(단 중복이 제거된 문자열의 각 문자는 , 원래 문자열의 순서를 유지해야 함)
이 문제의 요구사항은 결국 중복된 문자를 인식할 수 있냐 이고,
이에 대한 나의 해결 로직을 아래와 같이 생각하였다.
[빈도수 배열 활용]
- 빈도수 배열이란 0번부터 122번의 index를 갖는 int형 배열
- 이중 97번부터 122번까지의 element만을 사용하는데,
- 이때 각 element 값은 "해당 아스키코드의 소문자가 사용된 횟수"를 의미
EX) "aabbcc"가 입력된다면 ,
* 이 문자열을 순차탐색 하면서 - 빈도수 배열에 frequency[charArr[i]]++ 식으로 사용된 빈도수를 기록하면 됨- 이러한 빈도수 배열을 활용하기 위해서는 char 값을 ,
배열의 index자리에 넣었을 때 "아스키코드 값"으로 인식되어야만 했는데, 다행이도 아스키코드값으로 인식되었다.
[위와 같은 빈도수 배열 활용한 해결 로직]
- 이러한 빈도수 배열을 활용하여 입력한 문자열을 만날 때 마다 빈도수를 1씩 증가시킨다면
- 한번도 만나지 않은 , 즉 빈도수 배열 값이 0인 문자인 경우에 대해서만 결과 배열에 담고
- 이미 1번 이상 만난, 즉 빈도수 배열 값이 1이상인 문자인 경우는 결과배열에 담지 않는 방식으로 , 중복을 제거하면서도 순서를 유지시킬 수 있다.
이에 따른 나의 코드는 아래와 같다.
import java.util.Scanner;
public class Main {
public static String solution(String str){
/**
* 핵심은 문자열을 구성하는 각 문자의 아스키코드값을 index로 하여 - frequency 배열의 각 element에 접근한다는 점!
* 그러려면 char값이 index로 사용될 때 - 알아서 아스키코드 값으로써 인식되어야 하는데 - 다행이 그렇게 동작함!
* */
//0. 먼저 각 소문자 문자열 별 사용 횟수를 기록하는 frequency배열 초기화
int[] frequency = new int[123]; // 이중에서 97부터 122까지의 element만 사용할 것
//1. 이후 String 을 char 배열로 변환 + 결과 문자를 담을 문자열 배열도 함께 생성
char[] charArr = str.toCharArray();
char[] resultArr = new char[charArr.length];
int idx = 0;
//2. 이후 변환된 char배열을 순차탐색 하면서 , check 배열 값을 이용하여 , 한번도 사용되지 않은 문자만 순서대로 결과 배열에 담음
for(char c : charArr){
if(frequency[c] == 0){
resultArr[idx++] = c;
}
frequency[c]++;
}
/**
* 여기서 resultArr을 그냥 String으로 변환하면 , 값이 채워지지 않은 char값은 이상하게 변환되므로, 값이 채워진 char값 까지만 String으로 변환해야함
* */
//3. 결과 배열을 String 으로 변환하여 리턴 -> 이때 문자열에 이어붙이는 방법 사용 (비효율적이긴 함)
String resultStr = "";
for(int i=0; i<idx; i++){
resultStr += resultArr[i];
}
return resultStr;
}
public static void main(String[] args){
//0. Scanner 준비
Scanner sc = new Scanner(System.in);
//1. 문자열 입력
String str = sc.next();
//2. solution()의 인자로 넘겨 - 중복을 제거한 채 순서를 유지하는 문자열을 리턴받음
String result = solution(str);
//3. 리턴받은 문자열을 출력
System.out.println(result);
}
}
이렇게 문제를 해결한 후 강의를 들었는데, 선생님 께서는 String 클래스의 인스턴스 메서드 indexOf()를 사용하여 훨씬 더 직관적인 코드로 이 문제를 해결하셨다.
[indexOf() 메서드]
: 해당 문자열을 구성하는 각 문자에 대해서, 각 문자가 가장 처음으로 사용된 인덱스를 반환
EX) "hello".indexOf('l') -> "hello"문자열에서 'ㅣ' 문자가 가장 처음으로 사용된 인덱스인 2를 리턴
[indexOf() 메서드의 활용 방식]
- 문자열을 구성하는 문자 중, 처음으로 사용된 문자라면 , 인덱스와 indexOf()가 반환한 인덱스 값이 일치할 것
- 그렇지 않고 두 번째 이상으로 중복 사용된 문자라면, 인덱스 값이 indexOf()가 반환한 인덱스 값 보다 클것
EX) "aabbcc" 의 문자열에 대해서
- 인덱스 값은 012345
- indexOf 값은 002244
- 따라서 두 값을 비교하여 , 두 값이 같아서 중복 사용된게 아니라 처음 사용된 경우라면 - 결과 배열에 담는다.
- 그렇지 않고 두 값이 다르면, 이전에 사용된 문자라는 의미이므로 결과 배열에 담지 않는다.
이러한 indexOf() 사용 방식은 결국 기존 문자의 순서를 유지해야 한다는 조건이 존재하기 때문에 사용할 수 있는 방식이지만,
덕분에 굉장히 간결한 코드로 문제를 해결할 수 있었다.
이를 활용하여 문제를 해결한 코드는 아래와 같다.
import java.util.Scanner;
public class Main2 {
public static String solution(String str){
String resultStr = "";
//인자로 넘어온 문자열을 순자 탐색 하면서 -> 현재 인덱스와 - 최초로 사용된 인덱스가 일치하여
// 현재 그 문자가 , 이 문자열에서 최초로 사용된 경우에 한하여 -> 그 문자를 결과 문자열에 담는다.
for(int i=0; i<str.length(); i++){
if(i == str.indexOf(str.charAt(i)))
resultStr += str.charAt(i);
}
return resultStr;
}
public static void main(String[] args){
//0. Scanner 준비
Scanner sc = new Scanner(System.in);
//1. 문자열 입력
String str = sc.next();
//2. solution()을 호출하여 중복을 제거한 문자열 반환
String resultStr = solution(str);
//3. 출력
System.out.println(resultStr);
}
}