주노 님의 블로그

String, StringBuilder을 사용한 간단한 코딩테스트 문제풀이 본문

공부/코딩테스트(java)

String, StringBuilder을 사용한 간단한 코딩테스트 문제풀이

juno0432 2024. 8. 19. 12:30

String을 사용한 문제들과 풀이방법을 알아보자.

 

목차

  • toLowerCase() / toUpperCase()
  • isLowerCase() / iseUpperCase()
  • split()
  • StringBuilder
  • StringBuilder의 reverse()사용법
  • while을 이용해 문자열 뒤집기
  • indexOf()
  • replaceAll()

 

toLowerCase() / toUpperCase() 사용법

toUpperCase()는 해당 스트링을 모두 대문자로 변경하는 메서드이다. 

    public static void main(String[] args)
    {
        String str = "ABCdefGHI";
        System.out.println("바꾸기 전 str = " + str);
        str = str.toUpperCase();
        System.out.println("바꾼 후 str = " + str);
    }

 

위 코드를 보자 기본 str은 대문자와 소문자가 섞여있는 모습을 보인다.
그리고 toUpperCase를 사용해 대문자로 변경해보았다

 

바꾸기 전 str = ABCdefGHI
바꾼 후 str = ABCDEFGHI

 

결과는 위와 같다

 

toLowerCase()는 해당 스트링을 모두 소문자로 변경하는 메서드이다.

 

    public static void main(String[] args)
    {
        String str = "ABCdefGHI";
        System.out.println("바꾸기 전 str = " + str);
        str = str.toLowerCase();
        System.out.println("바꾼 후 str = " + str);
    }

 

바꾸기 전 str = ABCdefGHI
바꾼 후 str = abcdefghi

 

결과는 위와 같다

 

이걸 이용해서 아래의 문제를 풀어보자

 

문제 설명:

사용자는 문자열과 특정 문자를 입력합니다. 프로그램은 문자열 내에서 대소문자를 구분하지 않고 해당 문자가 몇 번 등장하는지 계산하여 출력해야 합니다.

입력 예시:

  • 문자열: "Hello World"
  • 문자: "o"

출력 예시:

  • 결과: "2"

 

위의 문제를 보면 특정 문자를 카운트하는것이며

대소문자를 구분하지 않고. 라고 하였다. 따라서 toUpperCase()나, toLowerCase()를 사용하는게 좋다.

 

일단 구현해보자

 

  public static void main(String[] args)
    {
        //문제 정의
        String target = "Hello WOrld";
        String input =  "o";

        //소문자 변환 (대문자 변환도 마음대로)
        target = target.toLowerCase();
        input = input.toLowerCase();

        //문자 수 세기
        int count = 0;
        for(int i = 0; i < target.length(); i++)
        {
            if(target.charAt(i) == input.charAt(0))
            {
                count++;
            }
        }
        System.out.println(count); //2
    }

 

위와 같이 풀이하면 될것이다

소문자, 대문자로 일괄 변환한다음 charAt(index)로 동등성을 비교하기!

 

isLowerCase() / isUpperCase() 사용법

isLowerCase()와 isUpperCase()는 해당 char가 대문자인지 소문자인지 판단한다. 반환값은 boolean으로.

 

public static void main(String[] args)
{
    String input =  "o";

    System.out.println(Character.isLowerCase(input.charAt(0))); //true
    System.out.println(Character.isUpperCase(input.charAt(0))); //false
}

 

위처럼 사용한다

Character 클래스를 사용하며 매개변수는 검사할 값이다.

 

 

문서를 확인해보면 유니코드가 특정 범위에 속해있는지 판단하는것이다

유니코드랑 아스키코드의 영문자 위치는 같다

간단하게 65 97로 외운편

 

아래는 아스키코드표

 

그럼 아래의 문제를 풀어보자

 

문제 설명:

주어진 문자열에서 대문자와 소문자의 개수를 각각 계산하여 출력하는 프로그램을 작성하세요.

입력 예시:

  • 문자열: "Hello World!"

출력 예시:

  • 소문자 개수: 8
  • 대문자 개수: 2
public static void main(String[] args)
    {
        //문제 정의
        String target = "Hello World";

        //대문자 소문자를 카운트할 변수 선언
        int lowerCase = 0;
        int upperCase = 0;

        //문자열을 순회하며 대문자와 소문자 수 세기
        for (int i = 0; i < target.length(); i++)
        {
            char c = target.charAt(i);
            if(Character.isUpperCase(c))
            {
                upperCase++;
            }
            else if(Character.isLowerCase(c))
            {
                lowerCase++;
            }
        }
        System.out.println("대문자 수 : " + upperCase + ", 소문자 수 : " + lowerCase);
    }
   
   //결과 : 대문자 수 : 2, 소문자 수 : 8

 

위 코드대로 풀면된다.

isUpperCase()와 LowerCase는 Character 클래스에서 사용해야한다.

 

split(delimiter) 사용법

split는 구획 문자(delimiter) 로 문자열을 자르는 메서드이다.

딜리미터, 구획문자가 말이 어려운데... 뭐 매개변수를 기준으로 자른다고 생각하면 된다.

 

당연히 잘라서 뭔가에 넣어야하니 배열에 들어가야한다

 

    public static void main(String[] args)
    {
        String str = "Hello World this is Java";
        String [] words = str.split(" ");
        
        //1을 기준으로 split
        //String str = "Hello1World1this1is1Java";
        //String [] words = str.split("1");
		
        //정규식을 사용한 split
        //String str = "Hello   World\tthis  is\nJava";
        //String[] words = str.split("\\s+");
        
        System.out.println(words[0]); //결과 : Hello
    }

 

위 코드를 보자 words는 str을 자르는데 공백 (" ")을 기준으로 자른다. 여기서 공백이 딜리미터고, 구획문자인것이다.

물론 정규식과 숫자등이 가능하다

 

아래 문제를 보자

문제 설명:

사용자는 하나의 문장을 입력합니다. 이 문장 속에 포함된 단어들의 개수를 세는 프로그램을 작성하세요. 단어들은 공백으로 구분됩니다.

입력 예시:

  • 입력: "Hello world this is Java"

출력 예시:

  • 출력: 단어의 개수: 5
public static void main(String[] args)
{
    String str = "Hello World this is Java";
    String [] words = str.split(" ");

    System.out.println(words.length);
}

 

str을 공백으로 잘라서 단어의 수를 세면 되는것이다

words.length로 배열의 길이를 반환해주면 되는것!

 

stringBuilder사용법

stringBuilder과 string의 차이점은 String은 불변객체, StringBuilder은 가변객체기때문이다

아래는 간단한 요약이다 (간단하지않음)

 

더보기

 

 

class Solution {
    public String solution(String s, int n) {
        String answer = "";
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i<s.length(); i++)
        {
           char ch = (char)(s.charAt(i)+n);
            if(Character.isWhitespace(s.charAt(i))){
                sb.append(" ");
            }
            else if (Character.isLowerCase(s.charAt(i)))
            {
                if (ch > 'z')
                {
                    sb.append((char)(ch-26));
                }
                else
                    sb.append(ch);
            }
            else {
                if (ch > 'Z')
                {
                    sb.append((char)(ch-26));
                }
                else
                    sb.append(ch);
            }
        }
        answer=sb.toString();
        return answer;
    }
}

 

이렇게 구현했다

스트링빌더를 사용하여 공백값을 받았을때는

Character클래스의 isWhiteSpacse를 사용하고,

isLowerCase는 소문자인가???에 대한 답을 반환한다

isUpperCase는 대문자인가?? 이다

따로 uppercase를 쓰지않은 이유는 

s는 알파벳 소문자, 대문자, 공백으로만 이루어져 있습니다.라는 제한사항이 있어서 그럼 ㅇㅇ..

 

 

만약 스트링빌더를 사용하지않고 스트링을 사용하였을때는 

class Solution {
    public String solution(String s, int n) {
        String answer = "";
        for(int i = 0; i<s.length(); i++)
        {
           char ch = (char)(s.charAt(i)+n);
            if(Character.isWhitespace(s.charAt(i))){
                answer+=" ";
            }
            else if (Character.isLowerCase(s.charAt(i)))
            {
                if (ch > 'z')
                {
                    answer+=(char)(ch-26);
                }
                else
                    answer+=ch;
            }
            else {
                if (ch > 'Z')
                {
                    answer+=(char)(ch-26);
                }
                else
                    answer+=ch;
            }
        }
        return answer;
    }
}

 

최대 140배까지 차이나는걸 볼 수 있다

 

그 이유는

str = ""; 로 정의하고

str += a; 라고 정의하면 값이 바뀐다고 생각되겠지만

 

String은 불변객체기때문에 더할때마다 새로운 문자열 객체가 생성됨.

 

따라서 위 str은 +를 추가하는 즉시 다른 주소로 객체를 생성하여 만들어준다.

public class Main {
    public static void main(String[] args) {
        String str = "hello";
        System.out.println("Original hash: " + System.identityHashCode(str));

        str += " world";
        System.out.println("New hash: " + System.identityHashCode(str));

        StringBuilder sb = new StringBuilder("hello");
        System.out.println("StringBuilder original hash: " + System.identityHashCode(sb));

        sb.append(" world");
        System.out.println("StringBuilder modified hash: " + System.identityHashCode(sb));
    }
}

위 코드는 str과 stringBuilder를 사용하였을때의 해시코드를 확인하는 코드이다

해시코드는 값이 변경되면 변경되는것 아닌가? 싶었지만, 위 identityHashCode함수는 객체의 식별자를 제공한다,

해시코드가 다르다면 서로 다른 객체라고 생각하면된다..

 따라서 실행결과를 확인하면

3번째의 str과 world를 추가한 str은 서로 해시값이 다른것을 확인할 수있다.

String이 불볍 객체기때문에 world를 추가함으로써 str은 기존의 값을 복사한 내용과 world를 추가한 새로운 객체를 만든것이다

 

StringBuilder는 내부 상태를 수정해도 동일한 객체의 주소를 유지하는것을 볼 수 있다.

 

즉 코테테의 문제에서 문자 크기만큼 str은 객체를 복사, 생성하는 과정이 포함된것이다

 

 stringBuilder은 가변 문자열 객체로 문자열을 더하거나 빼도 기존 배열에서 변경된다

고로 추가하든, 삭제하든 똑같은 주소를 가르키며, 새로운 객체의 생성은 없기때문에 빠르다.

세줄요약

string은 불변객체 stringBuilder은 가변객체

string은 새로운 문자열을 추가하면 새로운 주소로 할당함 시간오래걸림

stringBuilder은 추가를 자유자제로 할 수 있음

아래 문제를 보자

문제 설명:

다음 문자열 배열이 주어졌을 때, 모든 문자열을 하나의 문자열로 합치는 프로그램을 작성하세요. 이 과정에서 StringBuilder를 사용하여 효율적으로 문자열을 연결하세요.

문제 예시:

  • 입력: ["Hello", "World", "this", "is", "Java"]
  • 출력: "Hello World this is Java"

요구사항:

  • StringBuilder를 사용하여 문자열을 합친 후, 최종적으로 하나의 String 객체로 변환하여 출력합니다.
  • 각 단어는 공백(" ")으로 구분되며, 마지막 단어 뒤에는 공백이 없어야 합니다.

바로 예시를 보자

 

public static void main(String[] args) {
    //문제 정의
    String[] words = {"Hello", "World", "this", "is", "Java"};

    //측정 시간 계산
    long startTime = System.nanoTime();

    //스트링 빌더 사용
    StringBuilder sb = new StringBuilder();

    //배열을 돌아 단어를 추가해주고, 배열의 마지막이아닌경우 한칸띄어쓰기를 해준다.
    for (int i = 0; i < words.length; i++) {
        sb.append(words[i]);
        if (i < words.length - 1) {
            sb.append(" ");
        }
    }

    //결과를 다시 string으로 바꿔준다
    String result = sb.toString();

    //끝나는 시간을 측정한다
    long endTime = System.nanoTime();

    //최종 시간을 계산한다
    long executionTime = endTime - startTime;

    //결과값과 실행시간을 측정해준다
    System.out.println(result);
    System.out.println("실행 시간: " + executionTime + " 나노초");
}

//결과
//Hello World this is Java
//실행 시간: 11100 나노초

 

StringBuilder은 append를 이용해 끝부분에 추가해준다.

 

 

실행시간은 나노초(컴퓨터 마다 다름)

 

만약 위 코드를 string += "문자"를 하면 어떻게될까?

 

public static void main(String[] args) {
        // 문제 정의
        String[] words = {"Hello", "World", "this", "is", "Java"};

        // 측정 시간 계산
        long startTime = System.nanoTime();

        // String 사용하여 문자열 합치기
        String result = "";

        // 배열을 돌아 단어를 추가해주고, 배열의 마지막이 아닌 경우 한 칸 띄어쓰기를 해준다.
        for (int i = 0; i < words.length; i++) {
            result += words[i];
            if (i < words.length - 1) {
                result += " ";
            }
        }

        // 끝나는 시간을 측정한다
        long endTime = System.nanoTime();

        // 최종 시간을 계산한다
        long executionTime = endTime - startTime;

        // 결과값과 실행시간을 출력해준다
        System.out.println(result);
        System.out.println("실행 시간: " + executionTime + " 나노초");
    }
    
    //결과 :
    //Hello World this is Java
    //실행 시간: 935200 나노초

 

나노초라서 솔직히 0초안에 끝나버린다

그래도 차이는 큰편!

숙지해놓자

 

StringBuilder의 reverse사용법

StringBuilder의 reverse() 메서드가 있다

말그대로 StringBuilder를 뒤집는것

 

 

아래 예시를 보자

    public static void main(String[] args)
    {
        String str = "Hello";
        StringBuilder sb = new StringBuilder(str);
        System.out.println("반전하기 전 : " + sb);
        System.out.println("반전 후 : " + sb.reverse());
    }
    //결과
    //반전하기 전 : Hello
    //반전 후 : olleH

 

 

다만 위는 반환타입이 StringBuilder이기때문에 문제에따라 StringBuilder에서 string으로 바꿔줘야한다

 

바꾸는 방법은 toString()

 

아래 문제를 보자

  • 사용자로부터 입력받은 단어가 팰린드롬인지 확인하는 프로그램을 작성하십시오.
    팰린드롬이란, 앞에서 읽으나 뒤에서 읽으나 동일한 문자열을 말합니다.
    • 입력: "racecar"
    • 입력: "hello"
     
    • 출력: true
    • 출력: false
    조건:
    • 대소문자를 구분합니다. 예를 들어 "RaceCar"는 팰린드롬이 아닙니다.
public static void main(String[] args)
    {
        //문제 정의
        String target = "raceCar";

        StringBuilder sb = new StringBuilder(target);

        String reverse = sb.reverse().toString();
        
        //만약 reverse한것이 target과 같으면 true / 아니면 false
        if(reverse.equals(target))
        {
            System.out.println("true");
        }
        else
        {
            System.out.println("false");
        }
    }

 

간단하게 반전을사용해서 팰린드롬인지 검사한다.

 

while을 이용해 문자열 뒤집기

문자열을 뒤집는데. 중간에 이상한 문자를 제외하고 영문만 뒤집으라고 하면 어떻게 풀어야할까?

 

while과 two pointer를 이용해서 계산을 한다

two pointer란? while을 이용해서 각각의 인덱스를 계산하는 방법이다

 

물론 이중 for문을 사용할 수 있지만

그럴경우에는 시간복잡도는 o(n^2)이 되어버린다

while을 이용하면 o(n)이 될수있는것

 

two pointer방법은 여러 문제에 쓰인다.

1. 연속된 배열이나, 연속된 숫자 계산문제.

2. 회문 검사

3. 배열 내 두 수 선택.

4. 공통 원소 구하기

등등이 있다.

 

아래 문제를 보자

 

문제 설명:

주어진 문자열에서 알파벳 문자만을 뒤집고, 알파벳이 아닌 다른 문자는 그 자리에 그대로 두는 프로그램을 작성하세요. 이때, while 문과 two-pointer 기법을 사용하여 효율적으로 문제를 해결하세요.

문제 예시:

문자는 영문자만 주어집니다.

  • 입력: "a,b$c"
  • 출력: "c,b$a"
  • 입력: "Ab,c,de!$"
  • 출력: "ed,c,bA!$"
public static void main(String[] args)
    {
        //문제 정의
        String str = "a,b$c";
        char [] arr = str.toCharArray();

        //two pointer 정의
        int left = 0;
        int right = str.length()-1;

        //두개의 포인터가 중간에 위치할때까지 반복한다
        while(left < right)
        {
            //만약 영문자가 아니라면.
            if(!Character.isLetter(arr[left]))
            {
                left++;
            }
            else if(!Character.isLetter(arr[right]))
            {
                right--;
            }
            //둘다 영문자가 맞다면 temp를 이용해 swap해준다.
            else
            {
                char temp = arr[left];
                arr[left] = arr[right];
                arr[right] = temp;
                left++;
                right--;
            }
        }
        System.out.println("결과 : " + new String(arr));
    }
    //결과 : c,b$a

 

 

코드의 풀이를 보자 str을 배열로 만든다음

왼쪽에서 점점 중간으로가는 left, 오른쪽에서 중간으로가는 right를 선언하는 두개의 포인터를 만들어준다

 

while문에서 조건은 left와 right가 만나는곳에서 정지하게 되고

isLetter를 사용해 영문자인지 판단한다.

isAlphabetic을 사용해도 된다

다만 isLetter과 isAlphabetic은 영문자뿐만아니라 다른 문자들도 포함한다.

따라서 위 조건에서 영문자만 뒤집는다고 한다면 

 

//만약 영문자가 아니라면.
            if (!(arr[left] >= 'A' && arr[left] <= 'Z') && !(arr[left] >= 'a' && arr[left] <= 'z'))
            {
                left++;
            } else if (!(arr[right] >= 'A' && arr[right] <= 'Z') && !(arr[right] >= 'a' && arr[right] <= 'z'))
            {
                right--;
            }

 

이렇게 수정하면 된다.

 

indexOf() 사용방법

indexOf()는 해당 문자가 처음 나타나는 위치를 반환해준다

 

아래 예를 보자

public static void main(String[] args)
    {
        //문제 정의
        String str = "Hello World 1";
        System.out.println("o가 처음 나타나는 위치 " + str.indexOf('o')); //4
        System.out.println("o가 처음 나타나는 위치 " + str.indexOf("o")); //4
        System.out.println("k가 처음 나타나는 위치 " + str.indexOf("k")); //-1
    }

 

위 코드를 보면 indexOf(문자열)이 처음 나타나는 위치를 반환해준다

만약 없다면 -1을 반환해준다

 

replaceAll(원래 문자, 바꿀 문자열) 사용법

특정 문자를 다른 문자로 치환하는 메서드이다

 

public static void main(String[] args)
{
    String target = "raceCar";

    target = target.replaceAll("a", "_");

    System.out.println(target);
}
// 결과 : r_ceC_r

 

위 코드를 보면 replaceAll을 사용해서 "a"를 "_"로 바꾸고 있다.

정규표현식을 사용해서 바꿀수도 있다

 

public static void main(String[] args)
{
    String target = "r1a2ce C!a3r";

    target = target.replaceAll("[^A-Za-z]", "");

    System.out.println(target);
}

 

영문자가 아닌경우 교체를한다.

아래 문제를 보자

문제 설명:

사용자로부터 입력받은 문자열이 팰린드롬인지 확인하는 프로그램을 작성하십시오. 팰린드롬이란, 앞에서 읽으나 뒤에서 읽으나 동일한 문자열을 말합니다.

입력 예시:

  • 입력: "A m7an!@# a p!lan a c#anal Pa###nam1a"
  • 입력: "hello"

출력 예시:

  • 출력: true
  • 출력: false

조건:

  • 회문을 검사할때는 알파벳만 가지고 회문을 검사하며, 대소문자를 구분하지 않습니다.

 

public static void main(String[] args)
    {
        //문제 정의
        String target = "A m7an!@# a p!lan a c#anal Pa###nam1a";
        
        //target을 소문자로 변경하고, replaceAll을 사용한 문자열 변경
        target = target.toLowerCase().replaceAll("[^a-z]", "");

        //reverse를 하기위한 stringbuilder로 변경
        StringBuilder sb = new StringBuilder(target);

        //reverse
        String reverse = sb.reverse().toString();
        
        if(target.equals(reverse))
        {
            System.out.println("true");
        }
        else
        {
            System.out.println("false");
        }
    }
    //결과 : true

 

public static void main(String[] args)
    {
        //문제 정의
        String target = "Samsung galaxy";

        //target을 소문자로 변경하고, replaceAll을 사용한 문자열 변경
        target = target.toLowerCase().replaceAll("[^a-z]", "");

        //reverse를 하기위한 stringbuilder로 변경
        StringBuilder sb = new StringBuilder(target);

        //reverse
        String reverse = sb.reverse().toString();

        if(target.equals(reverse))
        {
            System.out.println("true");
        }
        else
        {
            System.out.println("false");
        }
    }
    //결과 :false

 

이다.

위 코드는 받아온 값을 소문자나 대문자로 변경 한 후, 소문자가 아닌 모든 문자를 제거한다

StringBuilder를 사용해 reverse를 한다

위 target은 팰린드롬이며

아래 target은 팰린드롬이 아니다.