본문 바로가기

🧑🏻‍💻 Dev/알고리즘

[프로그래머스] 레벨 1 문제 모두 풀고난 후 배운점

계속 새로운 문제가 나오고 있지만 2023년 3월 16일 기준으로 레벨 1 문제를 모두 해결해 봤다. 

 

언어는 모두 Java를 사용해서 풀었다.

 

원래 레벨 1을 조금 풀다가 이정도면 2 레벨로 들어가도 되겠다는 어리섞은 생각을 하게 되었다. 쉽게 풀렸던 몇몇 레벨 2문제를 만나서 그동안 몰랐던 거 같다. 

 

그래서 적어도 레벨 1 문제를 빠르게 다 풀어보자는 생각으로 문제를 풀기 시작했다. 레벨 1문제를 풀며 느낀 점을 정리해보려고 한다.

 

 

🔎 무조건 List! 무조건 Map!

내가 처음에 레벨 2를 풀기 시작했을 때 했었던 생각이다. 배열 보다는 역시 리스트를 써야지 이런 생각을 했었고, 실제로도 그렇게 풀었었다. 물론 List, Map, Set 모두 훌륭한 자료구조이다. 하지만 레벨 1문제를 풀면서는 먼저 문제에 접근할 때 배열로 해결할 수 있지 않을까라는 생각으로 접근을 하는 연습을 많이 했다.

 

그 이유는 분명 List와 Map이 효율적인 문제에서는 속도도 빠르고, 오히려 배열보다 효율성도 좋을 때가 많다. 하지만 대부분의 레벨 1 문제는 배열을 사용해서 해결할 수 있는 문제가 대부분이었고, 속도도 배열이 대부분 빨랐다. 이건 무조건 배열을 쓰라는 소리가 아니다. 배열을 써서 해결할 수 있는 문제라면 굳이 List나 Map을 쓰지 않아도 된다는 말이다.

 

 

🔎 StringBuilder를 활용하자

문제를 풀다보면 String 타입의 결과를 도출해 내는 문제가 종종 있다. 풀었던 문제 중에서 예시를 들자면 어떤 String 타입의 배열이 주어졌을 때, "Kim"이라는 문자열이 존재하는 index를 "김서방은 {index}에 있다"라는 형태의 문자열로 반환하는 문제였다.

 

배열에서 해당 문자열의 위치를 찾는 건 반복문을 돌려서 찾거나 List 타입으로 변환해서 indexOf() 메소드를 사용해 주면 쉽게 얻을 수 있다. 이제 이렇게 얻은 index를 "김서방은 "과 "에 있다"라는 문자열 사이에 넣어야 한다. 선택은 여러 가지가 있다.

 

# 그냥 더해서 반환하기

return "김서방은 " + index + "에 있다";

String은 이렇게 직접 더하는 연산을 사용하면 속도가 많이 느려진다. 레벨 1짜리 문제여서 별 차이는 느끼지 못할 정도이지만 피하는 것이 좋다.

 

 

# 포매팅을 사용하기

return String.format("김서방은 %d에 있다", index);

정수형 변수인 index를 포매팅(%d)을 이용해서 반환해 줄 수 있다. 실제 속도 차이로 봤을 때는 위에서 String을 그대로 더한 결과보다 빨랐다.

 

 

# StringBuilder 사용하기

return new StringBuilder("김서방은 ").append(index).append("에 있다").toString();

오늘의 핵심이다. StringBuilder는 append()라는 메소드로 계속 이어서 문자를 이어 붙일 수 있다. 그리고 마지막에 toString()을 써주면 만들어진 문자열을 String 타입으로 반환해 준다. 속도도 위에서 사용했던 두 가지 방법보다 빨랐다.

 

 

🔎 char을 잘 활용하자

사실 이전까지 char 배열은 잘 사용하지 않았다. 어떻게 보면 char 자료형 자체를 많이 안 썼던 거 같다. 하지만 레벨 1 문제를 74문제나 푼 나의 생각은 바꼈다. char[]를 잘 사용하면...엄청난 결과를 얻을 것이니..

 

# char[]은 문자열 치환이 가능하다

char[] ch = new char[]{'안', '녕', '하', '세', '요'};

String str1 = new String(ch); // "안녕하세요"
String str2 = String.valueOf(ch); // "안녕하세요"

이 기능은 필자는 문제 풀 때 정말 많이 사용했던 기능이다. 쓰이는 곳이 정말 많고, 유용하다. char[]을 문자열 생성자에 넣어주면 그대로 문자열을 반환해 준다니... 흥미롭지 않은가...? 나만 흥미로울 수도 있다. 👀

 

 

# char는 숫자로도 쉽게 사용이 가능하다

char[] ch = new char[]{'0', '1', '2', '3', '4', '5'};

int zero = ch[0] - '0';  // 숫자 0
int one = ch[1] - '0';   // 숫자 1
int two = ch[2] - '0';   // 숫자 2
int three = ch[3] - '0'; // 숫자 3

아스키 코드를 사용한 방법인데, '0'의 아스키코드 번호는 48이고, '1'은 49 '2'는 50 이렇게 증가한다. 즉 문자인 '1'에서 숫자 1을 얻기 위해서는 '1' - '0'을 해주면 된다. 49 - 48 이기 때문에 가능한 것이다. 

 

그냥 Integer.parseInt()나 Integer.valueOf()를 쓰면 되는 거 아닌가라고 생각하는 사람이 있으면 큰일이다. 한번 넣어보면 결과를 알 수 있을 것이다.

int zero = Integer.valueOf('0');
System.out.println(zero);

위에 있는 코드의 출력 결과가 뭐가 나올까? 0이 아닌 '0'의 아스키코드 번호인 48이 나온다. 그럼 parseInt()는? 이건 "java: incompatible types: char cannot be converted to java.lang.String" 이런 오류와 함께 컴파일 오류가 발생한다. parseInt()의 매개변수로 들어갈 수 있는 건 String 타입이기 때문이다. 그러니 아스키코드 활용을 잘 알아두면 좋을 것이다.

 

 

# 대문자와 소문자를 판별

char lowerValue = 'a';
char upperValue = 'A';

if (Character.isLowerCase(lowerValue)) {
    System.out.println("소문자 입니다."); // 출력
}

if (Character.isUpperCase(upperValue)) {
    System.out.println("대문자 입니다."); // 출력
}

Character에는 대문자와 소문자를 판별해주는 isLowerCase()와 isUpperCase() 메소드가 존재하기 때문에 이거도 잘 활용하면 코드를 대폭 줄일 수 있다. 숫자인지 판별하는 isDigit()도 있으니 잘 사용해 보기를 바랍니다.

 

 

🔎 코드를 줄이자!!! 스트림!!!

이라고 생각하면 큰일이다. Stream으로 해결한 몇몇 코드를 보면 정말 한 눈에 반할 정도로 멋지고, 나도 쓰고 싶어질 때가 있다. 심지어...한 줄로 해결해 버리는 문제도 많이 있었다.

 

하지만 스트림으로 푸는 문제들은 코드를 돌려보면 알겠지만 속도가 많이 느리다. 예를 들어 단순 반복문과 배열을 사용했을 때는 0.03ms에서 0.09ms가 나오는 반면에 스트림으로 해결한 문제를 돌려보면 1ms을 넘어가는 경우도 다반사이다. 아름다움에 현혹되서는 안 된다.

 

그렇다고 스트림이 좋지 않다라는 말은 아니지만 쓸 때와 쓰지 않을 때를 잘 구분해야 된다는 말이다. 알고리즘 문제를 풀 때는 이왕이면 스트림은 피하는 것이 좋다고 생각된다. 나같은 초짜는 언제 스트림을 쓰고 안 써야 하는지 구분을 못하기 때문에... 🙂

 

생각나는 부분은 일단 여기 까지이다. 이제는 레벨 2를 풀러 떠나야겠다.