코테

99클럽 코테 스터디 1일차 TIL - 문자열 내 p와 y의 개수

오형스톤 2024. 10. 28. 22:01

오늘의 문제

프로그래머스 - 문자열 내 p와 y의 개수

코딩테스트 연습 - 문자열 내 p와 y의 개수 | 프로그래머스 스쿨

문제내용과 풀이

문제 설명

대문자와 소문자가 섞여있는 문자열 s가 주어집니다. s에 'p'의 개수와 'y'의 개수를 비교해 같으면 True, 다르면 False를 return 하는 solution를 완성하세요. 'p', 'y' 모두 하나도 없는 경우는 항상 True를 리턴합니다. 단, 개수를 비교할 때 대문자와 소문자는 구별하지 않습니다. 예를 들어 s가 "pPoooyY"면 true를 return하고 "Pyy"라면 false를 return합니다.

 

제한사항 

  • 문자열 s의 길이 : 50 이하의 자연수 
  • 문자열 s는 알파벳으로만 이루어져 있습니다.

입출력 예 

"pPoooyY" true 
"Pyy" false

 

나는 원래도 알고리즘 문제를 잘 풀지 못했고,

오랜만에 다시 알고리즘 문제를 푸는거라 난이도가 제일 낮은 과정을 선택했다.

 

1. for

class Solution {
    boolean solution(String s) {
        int pCount = 0;
        int yCount = 0;
        
        for (int i = 0; i < s.length(); i++) {
            char c = Character.toLowerCase(s.charAt(i));
            if (c == 'p') {
                pCount++;
            } else if (c == 'y') {
                yCount++;
            }
        }
        
        return pCount == yCount;
    }
}

 

회사에서 맨날 스크립트만 만지다가 정말 오랜만에 자바 알고리즘 문제를 푸니까 charAt도 잘 안떠올랐고,

문자열 변환은 해봤지만 char형으로 하나하나 변환했던 적은 없었던 것 같다.

검색을 통해 character 자료형은 Character.toLowerCase(), Character.toUpperCase() 로 하나하나씩 대소문자로 변환 시킬 수 있음을 알았고, for문으로 풀 수 있었다.

하지만 뭔가 바보같이 푼 것 같았고, 다른 방법들이 있는지 찾아봤다.

 

2. replace

class Solution {
    boolean solution(String s) {
        String lowerCaseStr = s.toLowerCase();
        
        int pCount = lowerCaseStr.length() - lowerCaseStr.replace("p", "").length();
        int yCount = lowerCaseStr.length() - lowerCaseStr.replace("y", "").length();
        
        return pCount == yCount;
    }
}

 

우선 문자열 자체를 그냥 toLowerCase나 toUpperCase 로 바꾸면 더 간단해졌다.

그리고 replace를 이용하면, 해당 문자열에서 내가 정한 문자가 빠진 문자열이 생기게 된다.

ex) "hello" -> replace('l') -> "heo"

해서 총 문자열 길이 - replace 된 문자열 길이 로 p의 개수, y의 개수를 간단하게 구할 수 있었다.

 

3. lambda

class Solution {
    boolean solution(String s) {
        long pCount = s.chars().filter(ch -> ch == 'p' || ch == 'P').count();
        long yCount = s.chars().filter(ch -> ch == 'y' || ch == 'Y').count();
        
        return pCount == yCount;
    }
}

 

람다식을 이용한 방법도 있었다. 

string의 chars() 라는 메서드는 처음봤는데, 문자열의 각 문자를 유니코드 값(정수)로 변환해

intStream 타입의 스트림을 반환한다. 스트림은 배열이 아니고, 연속된 데이터 흐름정도로 생각할 수 있다고 한다.

예를 들어, "abc"라는 문자열이 있다면, chars()는 [97, 98, 99]와 같은 유니코드 값의 스트림을 반환한다.

간단하게 말하면 for문에서 문자열을 charAt(i)으로 바꾸는 것과 거의 같은데, 코드가 훨씬 줄어든다.

 

자바 람다식의 filter, map은 그게 스트림인 것일 뿐이지  자바스크립트의 filter, map과 거의 같다고 생각했다. 

filter를 통해 내부반복하며 스트림을 필터링하고, ch는 스트림 하나하나를 의미한다.

원래 char 자료형은 유니코드 값으로 비교가 되기때문에 저렇게 비교가 가능하고 각각 count 해서 비교하는 방법도 있었다.

class Solution {
    boolean solution(String s) {
        // 모든 문자를 소문자로 변환한 후 'p'와 'y' 개수 세기
        long pCount = s.chars()
                       .map(Character::toLowerCase)
                       .filter(ch -> ch == 'p')
                       .count();
                       
        long yCount = s.chars()
                       .map(Character::toLowerCase)
                       .filter(ch -> ch == 'y')
                       .count();
        
        return pCount == yCount;
    }
}

 

대소문자 처리를 먼저 한다면 map을 이용해서 새로운 스트림을 생성하고, filter를 걸어서 count 하는 방법도 있었다.

하지만 오히려 코드가 좀 복잡해지는 것 같았다.

boolean solution(String s) {
        long count = s.chars()
                      .map(Character::toLowerCase)
                      .filter(ch -> ch == 'p' || ch == 'y')
                      .map(ch -> ch == 'p' ? 1 : -1)
                      .sum();
        
        return count == 0;
}

 

해서 변수를 하나로 둔 뒤 p,y 만을 남긴 다음 p를 만나면 1, y를 만나면 -1을 반환하게 하여 개수를 비교할 수도 있었다.

boolean solution(String s) {
    int count = s.chars()
                 .map(Character::toLowerCase)
                 .reduce(0, (acc, ch) -> {
                     if (ch == 'p') return acc + 1;
                     else if (ch == 'y') return acc - 1;
                     else return acc;
                 });
    
    return count == 0;
}

 

reduce를 이용해서 스트림을 순회하며 값을 누적시키는 방법도 있었다.

acc는 누적된 값이고, ch는 현재 스트림의 문자이다.

ch가 p면 acc 에 1을 더하고, ch가 y면 1을 빼며 개수를 비교할 수 있다.

 

reduce가 스트림을 한번만 순회하기 때문에 스트림을 사용 하는 것 중에는 시간복잡도가 최적이라고 하는데..

for문과 비교하면, 오히려 for문이 오버헤드가 적어 reduce문 보다 빠를 수 있다고 한다.

이 부분은 지식이 없어서 잘 모르겠지만.. 문제를 풀다 보면 알 수 있을 것 같다.

 

자바 기본 메서드들에 대해 더 공부해야겠다.
문제만 어떻게든 풀고 끄기 보다는, 다른사람들이 어떤 다양한 방법으로 풀었는지 봐야겠다.