본문 바로가기

Java/알고리즘

[PCCP 대비] ch 4. 문자열 (1)

1. 문자열이란? 

 

1) 문자열의 특징 

 

문자의 배열 

 

- 자바에서 String 클래스로 표현되는 문자열은 내부적으로 배열을 이용해서 표현한다. 이 문자열 내 문자를 가져오는 방법은 두가지 이다. 

 

String.charAt(int index) 주어진 인덱스에 있는 문자를 char 형식으로 반환
String.toCharArray()  모든 문자가 들어 있는 char[ ] 형식의 데이터를 반환

 

 

아스키 코드 

 

- 문자는 내부적으로 정수로 취급되고 연산된다. 

- 아스키코드는 기본적인 128개의 문자가 가지는 정수코드이다. 

- 숫자를 표현하는 문자에서 '0'의 아스키 코드를 빼면 문자가 표현하는 정수 값이 나온다. 

 

char digit = '9';
int digitToInt = digit -'0';

 

- 내장 라이브러리를 이용하면 다음과 같이 구현할 수 있다. 

char digit = '9';
int digitToInt = Character.getNumericValue(digit);

 

- 대문자 'A'~'Z'는 65~90의 값, 소문자 'a'~'z'는 97~122의 값을 갖는다. 이 값들을 살펴보면 소문자와 대문자 사이에는 'a'-'A'만큼 차이가 있음을 알 수 있다. 이를 이용하면 다음과 같이 변환할 수 있다. 

char lower = 'e';
char upper = (char)(lower+('a'-'A'));

 

- char끼리 연산하면 정수형으로 취급되어 int형으로 형 변환이 일어난다. 따라서 다시 문자로 취급하기 위해서는 char형으로 강제 형 변환을 시켜 주어야 한다. 

char lower = 'e';
char upper = Character.toUpperCase(lower);

 

- 반대로 대문자를 소문자로 변환하는 코드도 다음과 같이 작성할 수 있다. 

Char upper = 'G';
char lower = (char)(upper-('a'-'A'));

 

- 내장 라이브러리를 이용하면 다음과 같다. 

char upper = 'G';
char lower = Character.toLowerCase(upper);

 

 

StringBuilder

 

- 자바에서는 문자열을 쉽고 효율적으로 구성하고자 StringBuilder 클래스를 제공한다. 

 

ex) 반복문을 이용하여 String을 구성

String az = "";
for(char c = 'a'; c<='z'; c++) {
	az+=c;
}
System.out.println(az);

 

- 매 반복마다 새로운 문자열 객체가 생성되고, 새로운 문자의 배열을 복사한다. 배열을 복사하는 데 배열의 모든 원소를 참조해야 하므로, 배열 길이 N에 대해 O(N)의 시간이 소요된다. 문자열도 내부적으로 배열을 사용하므로 길이가 N인 새로운 문자열을 만드는데 O(N)이 소요된다. 

- 이 과정을 빈 문자열부터 길이를 하나씩 늘려가며 반복하므로 O(N^2)의 시간이 걸린다. 

 

- StringBuilder 클래스는 문자를 이어붙이거나 빼는등 수정할 때마다 새로운 문자열을 만들지 않고 내부 배열에서 직접 수정한다. 문자를 이어 붙이는 것은 원소 하나를 집어넣는 것이니 상수 시간 O(1)이 기대 시간 복잡도가 된다. 

 

 

자주 사용되는 StringBuilder의 메서드 

메서드 역할 시간 복잡도
StringBuilder.toString() 지금까지 구성한 문자열을 String 형식으로 반환한다.  O(N)
StringBuilder.append(char c) 문자 c를 문자열 끝에 이어 붙인다. O(1)
StringBuilder.length() 지금까지 구성한 문자열 길이를 반환한다. O(1)
StringBuilder.reverse() 지금까지 구성한 문자열을 뒤집는다. O(N)

 

 

ex) StringBuilder을 이용한 반복문 

 

public static void main(String[] args) throws Exception {
		StringBuilder sb = new StringBuilder();
		
		for(char c ='a'; c<='z'; c++) {
			sb.append(c);
		}
		
		String str = sb.toString();
		System.out.println(str);
		
	}

 

 

2) 문자열을 다른 방식으로 생각해보기 

 

문제 5) 자연수 뒤집어 배열로 만들기 

 

코딩테스트 연습 - 자연수 뒤집어 배열로 만들기 | 프로그래머스 스쿨

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

● 문제 풀이 

 

문제 풀이 흐름

 

1. 입력받은 숫자를 문자열로 변환한다. 

2. 문자열을 뒤집는다. 

3.뒤집한 문자열을 문자의 배열로 변환한다. 

4. 배열의 각 문자를 정수로 변환한다. 

 

전체 코드 

class Solution {
    public int[] solution(long n) {
       String str = Long.toString(n); //문자열로 변환
       String reversed = new StringBuilder(str).reverse().toString(); //문자열 뒤집기 
       char arr[] = reversed.toCharArray(); //문자 배열로 변환 
        
       int answer[] = new int[arr.length];
    
       for(int i=0; i<answer.length; i++) {
           answer[i] = arr[i]-'0';
       }
        
       return answer;
    
    }
}

 

 

문제 6) 시저 암호 

코딩테스트 연습 - 시저 암호 | 프로그래머스 스쿨

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

● 문제 풀이 

 

- 알파벳을 n만큼 미는 것은 아스키 코드 값을 n만큼 증가시키는 것과 같다. 한 가지 주의할 점은 알파벳의 마지막 아스키 코드 값을 가지는 'Z'나 'z'에서 아스키 코드 값을 더 증가시키면 알파벳 범위 밖으로 나가 버린다는 것이다. 따라서 알파벳의 마지막에 도달하면 다시 처음부터 시작하도록 설정해야 한다. 

 

문제 풀이 흐름 

 

1. 입력 문자열의 모든 문자에 대해 반복 

    A. 알파벳이 아닌 경우 문자를 그대로 이어 붙이기

    B. 알파벳인 경우 n만큼 밀어 이어 붙이기 

 

코드 작성 

 

1. 입력 문자열의 모든 문자에 대해 반복 

 

- 입력받은 문자열을 순회하며 문자를 하나씩 이어 붙여야 하므로 StringBuilder를 사용하여 다음과 같이 구성 

StringBuilder builder = new StringBuilder();
for(char c : s.toCharArray()) {
	//c를 n만큼 민 문자를 builder에 이어 붙이기
}
return builder.toString();

 

- 문자 c를 n만큼 민 새로운 문자를 계산하기 위해 메서드를 분리해준다. 다음과 같이 입력 문자를 n만큼 민 문자를 반환하는 push() 메서드를 선언한다. 

private char push(char c, int n) {
	//c를 n만큼 밀어 반환
}

 

 

1-A. 알파벳이 아닌 경우 문자를 그대로 이어 붙이기 

 

- 알파벳이 아닌 문자는 시저 암호에 영향을 받지 않기 때문에 Character클래스의 isAlphabetic() 메서드를 사용하여 알파벳이 아닌 문자는 그대로 반환될 수 있게 한다. 

 

private char push(char c, int n) {
    if(!Character.isAlphabetic(c)) return c;
    
    //c를 n만큼 밀어 반환 
}

 

 

1-B. 알파벳인 경우 n만큼 밀어 이어 붙이기 

 

- 알파벳을 0~25로 변환할 수 있으면 n만큼 미는 것은 같은 로직을 적용시킬 수 있다. 

- 알파벳을 0~25로 변환하는 것은 숫자를 표현하는 문자를 정수로 변환하는 것과 같다. 대문자는 'A', 소문자는 'a'부터 시작하므로 이를 offset 변수에 저장하고, 이 변수를 사용하여 알파벳의 변환된 값 position을 다음과 같이 계산할 수 있다. 

int offset = Character.isUpperCase(c)?'A':'a';
int position = c - offset;

 

 

- 알파벳은 a부터 z까지 있으므로 가능한 position은 0부터 ('Z'-'A')까지이다. 이 범위를 벗어날 때 0부터 시작되게 하는 것은 나머지 연산자로 다음과 같이 간단하게 구현할 수 있다. 

position = (position+n)%('Z'-'A'+1);

 

- 처음에 구해 두었던 offset과 방금 계산한 position을 이용하여 n만큼 밀린 문자를 만들어 반환한다. 

return (char)(offset+position);

 

- 완성된 push() 메서드를 사용하여 solution() 메서드의 반복문을 완성시켜 준다. 

for(char c : s.toCharArray()) {
	builder.append(push(c,n));
}

 

 

전체 코드 

 

public class Solution {
    private char push(char c, int n) {
        if (!Character.isAlphabetic(c)) return c;

        int offset = Character.isUpperCase(c) ? 'A' : 'a';
        int position = c - offset;
        position = (position + n) % ('Z' - 'A' + 1);
        return (char) (offset + position);
    }

    public String solution(String s, int n) {
        StringBuilder builder = new StringBuilder();
        for (char c : s.toCharArray()) {
            builder.append(push(c, n));
        }
        return builder.toString();
    }
}

 

 

문제 7) 이상한 문자 만들기 

 

코딩테스트 연습 - 이상한 문자 만들기 | 프로그래머스 스쿨

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

● 문제 풀이 

 

문제 풀이 흐름 

 

1. 문자열의 모든 문자에 대해 반복

 

   A. 문자가 공백 문자일 경우 

      1) 그대로 이어 붙이기

      2) 다음 등장하는 알파벳은 대문자 

 

   B. 공백 문자가 아닌 경우 

      1) 대소문자 변환하여 이어 붙이기

      2) 다음 등장하는 알파벳의 대소문자는 현재 변환하는 문자와 반대 

 

2. 구성한 문자열 반환 

 

코드 작성 

class Solution {
    public String solution(String s) {
       StringBuilder sb = new StringBuilder();
       boolean toUpper = true; //대소문자 상태 
        
       for(char c : s.toCharArray()) {
           //공백일 때
           if(!Character.isAlphabetic(c)) {
               sb.append(c); //그대로 이어 붙이기 
               //공백 문자가 등장했다면 다음에 등장하는 알파벳은 단어의 시작이므로 무조건 대문자로 변환되어야 함 
               toUpper = true; 
           }else {
               if(toUpper){
                   sb.append(Character.toUpperCase(c));
               }else{
                   sb.append(Character.toLowerCase(c));
               }
               toUpper = !toUpper;
           }
       }
        
        return sb.toString();
    }
}