본문 바로가기
내가 배운 것들/알고리즘

항해99 11/8(월) 알고리즘

by Zabee52 2021. 11. 8.

 

나와 같은 조의 다른 사람들이 푼 문제 보기

고성범 님(https://velog.io/@davidko)

서유리 님(https://yuricoding.tistory.com/)

김우진 님(https://blog.naver.com/woojin126)

 

 

 

 

 

 

5. 문자열을 정수로 치환하기

 

코딩테스트 연습 - 문자열을 정수로 바꾸기

문자열 s를 숫자로 변환한 결과를 반환하는 함수, solution을 완성하세요. 제한 조건 s의 길이는 1 이상 5이하입니다. s의 맨앞에는 부호(+, -)가 올 수 있습니다. s는 부호와 숫자로만 이루어져있습니

programmers.co.kr

class Solution {
    public int solution(String s) {
        int answer = Integer.parseInt(s);
        
        return answer;
    }
}

문제의 요구사항을 보니 그냥 Integer.parseInt()를 알고 있는지 물어본 문제였던 것 같다.

만약 parseInt 쓰지 말고 하라고 한다면

class Solution {
    public int solution(String s) {
        int answer = 0;
        // 곱해줄 자릿수
        int digits = 1;

        // 일의자리부터 연산 시작
        for(int i = s.length()-1; i >= 0; i--){
            char c = s.charAt(i);

			// 맨 앞글자가 부호면 탈출
            if(c == '-' || c == '+'){
                break;
            }
            
            // char은 아스키코드표에 기반하여 값을 보관하기 때문에
            // 정수로 치환할 때는 그 값만큼 빼줘야함.
            // 숫자 0이 아스키코드표 기준 48인가 그런데 그냥 '0'으로 빼버려도 됨.
            answer += (c - '0') * digits;
            
            // 자릿수 up
            digits *= 10;
        }

        // 마이너스 부호일 경우 부호 전환
        if(s.charAt(0) == '-'){
            answer *= -1;
        }

        return answer;
    }
}

이렇게 하면 될 것이다.

 

 

9. 핸드폰 번호 가리기

 

코딩테스트 연습 - 핸드폰 번호 가리기

프로그래머스 모바일은 개인정보 보호를 위해 고지서를 보낼 때 고객들의 전화번호의 일부를 가립니다. 전화번호가 문자열 phone_number로 주어졌을 때, 전화번호의 뒷 4자리를 제외한 나머지 숫자

programmers.co.kr

class Solution {
    public String solution(String phone_number) {
        String answer = "";
        int v = 4;
        int pointer = phone_number.length() - v;
        
        for(int i = 0; i < pointer; i++){
            answer += "*";
        }
        
        answer += phone_number.substring(pointer, pointer + v);
        
        return answer;
    }
}

할 말이 없다. 자유롭게 풀었다.

아래는 가장 추천이 많았던 코드다.

class Solution {
  public String solution(String phone_number) {
     // String to char
     char[] ch = phone_number.toCharArray();
     // 뒤에 네 글자 제외하고 전부 *로 바꾸기
     for(int i = 0; i < ch.length - 4; i ++){
         ch[i] = '*';
     }
     return String.valueOf(ch);
  }
}

String을 char형으로 변경한 뒤 그 자리를 치환해버린다는 발상이 인상적이었다. 게다가 문자열이 4글자보다 작을 때 에러가 발생할 가능성이 생기는 내 코드보다 대처가 잘 되어있었고, 코드 자체가 직관적이다. 전체적으로 훨씬 좋은 코드인 것 같다.

다음으로 regexp 사용한 사례가 있던데, 써본적이 없어서 이해하지 못 했다. 정규식을 알기 전까지는 쓰지 않는 것으로.

 

 

13. 2016년

 

코딩테스트 연습 - 2016년

2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까

programmers.co.kr

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

class Solution {
    public String solution(int a, int b) {
        String answer = "";
        LocalDate targetDate = LocalDate.of(2016,a,b);
        // .ofPattern("E") : 한국일경우 월, 화, 수, ... 형식으로 출력
        // .withLocale(Locale.forLanguageTag("en") : 언어를 영어로 설정. Mon, Tue, Wed, ... 형식으로 출력
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("E").withLocale(Locale.forLanguageTag("en"));
        
        // 문제의 조건은 MON, TUE, WED, ... 형식으로 출력하는 것이었기 때문에 toUpperCase 적용
        answer = dateTimeFormatter.format(targetDate).toUpperCase();
        
        return answer;
    }
}

지금까지는 Date 클래스를 써왔는데, JDK 버전이 몇인데 아직도 안 쓸 수는 없을 것 같아서 LocalDate를 이용해서 적용해봤다. 어떤것이 비교적 우월해서 이렇게 새로 나온것인지는 잘 모르겠지만, 새로 나온건 일단 써 봐야지.

DateTimeFormatter는 그대로 먹혔다.

날짜 관련 클래스의 메소드들은 굳이 외울 필요 없다. 어떤 클래스를 쓸 지만 알아놓으면 될 것 같다. 그게 객체지향이지.

 

 

17. 문자열 다루기 기본

 

코딩테스트 연습 - 문자열 다루기 기본

문자열 s의 길이가 4 혹은 6이고, 숫자로만 구성돼있는지 확인해주는 함수, solution을 완성하세요. 예를 들어 s가 "a234"이면 False를 리턴하고 "1234"라면 True를 리턴하면 됩니다. 제한 사항 s는 길이 1

programmers.co.kr

class Solution {
    public boolean d(String s){
        boolean result = false;
        
        if(s.length() == 4 || s.length() == 6){
               try{
                   int test = Integer.parseInt(s);
                   result = true;
               }catch(NumberFormatException e){
                   
               }
        }
        
        return result;
    }
    
    public boolean solution(String s) {
        boolean answer = d(s);
        
        return answer;
    }
}

try ~ catch 구문을 이용해서 풀어봤다. 특별할 건 없었던 것 같다. 근데 이렇게 푸는게 바람직한지는 잘 모르겠다. 이게 생각나서 그냥 이렇게 해봤다.

 

 

21. 이상한 문자 만들기

 

코딩테스트 연습 - 이상한 문자 만들기

문자열 s는 한 개 이상의 단어로 구성되어 있습니다. 각 단어는 하나 이상의 공백문자로 구분되어 있습니다. 각 단어의 짝수번째 알파벳은 대문자로, 홀수번째 알파벳은 소문자로 바꾼 문자열을

programmers.co.kr

class Solution {
    public String solution(String s) {
        String answer = "";
        String[] str = s.split("");
        int cnt = 0;
        
        for (String ss : str) {
            if (ss.contains(" ")) {
                cnt = 0;
            } else {
                cnt++;
            }

            if (cnt % 2 == 1) {
                answer += ss.toUpperCase();
            }else{
                answer += ss.toLowerCase();
            }
        }
        
//         이게 왜 안 될까.....?        
//         String[] strs = s.split(" ");

//         for(int i = 0; i < strs.length; i++){
//             for(int k = 0; k < strs[i].length(); k++){
//                 if(k % 2 == 0) {
//                     answer += Character.toUpperCase(strs[i].charAt(k));
//                 }else{
//                     answer += Character.toLowerCase(strs[i].charAt(k));
//                 }
//             }
//             if(i != strs.length-1){
//                 answer += " ";
//             }
//         }

        return answer;
    }
}

처음에 시도한게 주석의 내용이었다. 근데 작동이 제대로 안 돼서 카운터를 집어넣어서 주먹구구식으로 풀었다. 왜 아직도 주석한 게 안 되는지 모르겠다. 생각을 많이 해봐야 할 것 같다.

 

 

25. 정수 제곱근 판별

 

코딩테스트 연습 - 정수 제곱근 판별

임의의 양의 정수 n에 대해, n이 어떤 양의 정수 x의 제곱인지 아닌지 판단하려 합니다. n이 양의 정수 x의 제곱이라면 x+1의 제곱을 리턴하고, n이 양의 정수 x의 제곱이 아니라면 -1을 리턴하는 함

programmers.co.kr

class Solution {
    public long solution(long n) {
        long answer = 0;
        long sqrt = (long)Math.sqrt(n);

        if(Math.pow(sqrt, 2) == n){
            answer = (long)Math.pow(sqrt+1, 2);
        }else{
            answer = -1;
        }

        return answer;
    }
}

솔직하게 말해서 이 문제는 Math 클래스 사용하지 말라고하면 어떻게 풀어야할지 잘 모르겠다. 제곱근을 구하는 특별한 방법이 있는걸까? 물론 이렇게 가혹하게 문제를 설정할 것 같진 않지만.....

 

 

29. 3진법 뒤집기

 

코딩테스트 연습 - 3진법 뒤집기

자연수 n이 매개변수로 주어집니다. n을 3진법 상에서 앞뒤로 뒤집은 후, 이를 다시 10진법으로 표현한 수를 return 하도록 solution 함수를 완성해주세요. 제한사항 n은 1 이상 100,000,000 이하인 자연수

programmers.co.kr

class Solution {
    public int solution(int n) {
        int answer = 0;
        String str = "";
        int digit = 3;

        // 3으로 나눈 나머지 = 첫번째 자리
        // 값을 3으로 나눔
        // 3으로 나눈 나머지 = 두 번째 자리
        // 값을 3으로 나눔
        // 3으로 나눈 나머지 = n 번째 자리
        // 값을 3으로 나눔
        // ...
        // 0이면 종료
        while(n != 0){
            str += String.valueOf(n%digit);
            n /= digit;
        }
        str = str.substring(0, str.length());

        // answer = Integer.parseInt(str, 3); 하면 끝남
        long num = Long.parseLong(str);

        for(int i = 1; num != 0; i*=digit){
            answer += num % 10 * i;
            num /= 10;
        }

        return answer;
    }
}

3진수 변환을 어떻게 할지 고민한 흔적이 보인다.

str 처리같은경우 방법이 여러가지가 있는데,

1. str +=를 이용해 처음부터 역순으로 값을 받아온 뒤 맨 마지막값(0)을 지워주는 방법

2. str = ... + str 을 이용해 뒤에 붙여주면서 값을 받아온 뒤 str = new StringBuilder(str).reverse().toString() 사용하기

등등 방식으로 처리하면 된다.

3진법으로 다시 변환하는건 parse 시리즈 메소드에 다 달려있기 때문에 그냥 Integer.parseInt(str, 3)하면 끝나지만 한 번 직접 손으로 짜봤다. 3진수로 바꾸면 자릿수가 워낙 길어져 long 변수를 사용했다.

 

 

33. 로또의 순위

 

코딩테스트 연습 - 로또의 최고 순위와 최저 순위

로또 6/45(이하 '로또'로 표기)는 1부터 45까지의 숫자 중 6개를 찍어서 맞히는 대표적인 복권입니다. 아래는 로또의 순위를 정하는 방식입니다. 1 순위 당첨 내용 1 6개 번호가 모두 일치 2 5개 번호

programmers.co.kr

class Solution {
    // 로또의 순위를 출력해주는 메소드
    public int getRank(int num) {
        int result = 6;

        switch (num) {
            case 6:
                result = 1;
                break;
            case 5:
                result = 2;
                break;
            case 4:
                result = 3;
                break;
            case 3:
                result = 4;
                break;
            case 2:
                result = 5;
                break;
        }

        return result;
    }

    public int[] solution(int[] lottos, int[] win_nums) {
        int[] answer = {};
        int win_cnt = 0;
        int zero_cnt = 0;
		
        // 1. 로또 숫자가 0일경우 zero_cnt를 증가시킨다.
        // 2. 로또 숫자가 당첨 숫자와 일치할 경우 win_cnt를 증가시킨다.
        // 3. 당첨된 숫자의 개수 + 0인 숫자의 개수로 나온 등수를 알아낸다.
        // 4. 당첨된 숫자의 개수로만 나온 등수를 알아낸다.
        // 5. 알아낸 두 개의 값을 answer 배열에 넣어준다.
        for (int i = 0; i < lottos.length; i++) {
            if (lottos[i] == 0) {
                zero_cnt++;
                continue;
            }
            for (int k = 0; k < win_nums.length; k++) {
                if (lottos[i] == win_nums[k]) {
                    win_cnt++;
                }
            }
        }

        answer = new int[]{getRank(win_cnt + zero_cnt), getRank(win_cnt)};

        return answer;
    }
}

너무 오랜만에 자바를 써서 배열 사용법을 다 까먹었다. 그래서 꽤나 애를 먹었다. 그래도 결국 해냈다. 문제 자체는 어렵지 않았다.

 

아래는 추천을 많이 받은 코드이다.

import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] solution(int[] lottos, int[] win_nums) {
        Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();
        int zeroCount = 0;

        for(int lotto : lottos) {
            if(lotto == 0) {
                zeroCount++;
                continue;
            }
            map.put(lotto, true);
        }


        int sameCount = 0;
        for(int winNum : win_nums) {
            if(map.containsKey(winNum)) sameCount++;
        }

        int maxRank = 7 - (sameCount + zeroCount);
        int minRank = 7 - sameCount;
        if(maxRank > 6) maxRank = 6;
        if(minRank > 6) minRank = 6;

        return new int[] {maxRank, minRank};
    }
}

코드 모양새 자체가 정말 예쁘다. 0 자체가 맵에 들어가는 경우가 없고 그 외엔 중복값을 허용하지 않으니 대신 Set을 써줘도 문제가 없을 것 같다.

속도 자체는 내가 작성한 코드가 더 빠르긴 하지만 그 차이가 매우 작기 때문에 읽기 훨씬 편하면서 중첩 반복문으로 인해 내재된 속도상의 변수가 존재하지 않는 이 코드가 낫다는 생각이 든다.

 

 

37. 소수 만들기

 

코딩테스트 연습 - 소수 만들기

주어진 숫자 중 3개의 수를 더했을 때 소수가 되는 경우의 개수를 구하려고 합니다. 숫자들이 들어있는 배열 nums가 매개변수로 주어질 때, nums에 있는 숫자들 중 서로 다른 3개를 골라 더했을 때

programmers.co.kr

class Solution {
    public boolean isPrime(int num){
        boolean result = true;
		
        // 소수 : 2 ~ (자기자신-1) 까지 나눈 나머지가 0인 경우가 없으면 소수
        for(int i = 2; i < num; i++){
            if(num % i == 0){
                result = false;
                break;
            }
        }

        return result;
    }

    public int solution(int[] nums) {
        int answer = 0;

        // 삼중 for문을 보니 마음이 너무나도 불편하다.....
        for (int i = 0; i < nums.length - 2; i++) {
            for (int k = i + 1; k < nums.length - 1; k++) {
                for (int m = k + 1; m < nums.length; m++) {
                    int sum = nums[i] + nums[k] + nums[m];
					
                    // 소수일 경우 개수 증가
                    if(isPrime(sum)){
                        answer++;
                    }
                }
            }
        }

        return answer;
    }
}

로직이 전체적으로 많이 비효율적인 것 같다. 소수를 알아오는데 필요한 로직이나, 반복문을 3중첩한 것이나... 별로 마음에 들지 않는 코드다. 당장은 개선 방안이 별로 떠오르지 않아서 생각나는 방법대로 한 건데 이렇게 하면 안 될 것 같다. 개선이 많이 필요해보인다.

..... 프로그래머스에 제출한 다른 사람들의 답을 봐도 같은 모양인걸로 봐서 개선은 어려워보인다. 사실 문제 모양새 자체가 그렇게 생기긴 했다.

 

 

40. 신규 아이디 추천

 

코딩테스트 연습 - 신규 아이디 추천

카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로

programmers.co.kr

import java.util.ArrayList;
import java.util.List;

class Solution {
    public void removeFullStop(List<Character> list) {
        if(list.size() > 0 && list.get(0) == '.'){
            list.remove(0);
        }
        if(list.size() > 0 && list.get(list.size()-1) == '.'){
            list.remove(list.size() - 1);
        }
    }

    public String solution(String new_id) {
        String answer = new_id;
        List<Character> list = new ArrayList<>();
        StringBuilder builder = new StringBuilder();

        // 1단계: new_id의 모든 대문쟈를 대응되는 소문자로 치환
        answer = answer.toLowerCase();
        // 2단계: 알파벳 소문자, 숫자, 하이픈, 언더바, 마침표를 제외한 모든 문자 제거
        char[] chars = answer.toCharArray();
        for (char c : chars) {
            if ((c >= 'a' && c <= 'z') ||
                    (c >= '0' && c <= '9') ||
                    c == '-' || c == '_' || c == '.') {
                list.add(c);
            }
        }

        // 3단계: 마침표가 두 번 이상 연속되면 하나의 마침표로 치환
        for (int i = 0; i < list.size() - 1; i++) {
            if (list.get(i) == '.') {
                if (list.get(i) == list.get(i + 1)) {
                    list.remove(i);
                    i = -1;
                }
            }
        }

        // 4단계: 마침표가 처음이나 끝에 있으면 제거
        removeFullStop(list);

        // 5단계: 빈 문자열일 경우 new_id에 a를 대입
        if (list.size() == 0) {
            list.add('a');
        }

        // 6단계: 문자열의 길이가 16자 이상일 경우 15문자를 제외하고 전부 제거
        // 이후 5단계 한 번 더 시행
        if(list.size() >= 16){
            list = list.subList(0, 15);
            removeFullStop(list);
        }

        // 7단계: 길이가 2자 이하인 경우 마지막 문자를 길이가 3이 될 때까지 반복
        if(list.size() <= 2){
            while(list.size() < 3){
                list.add(list.get(list.size()-1));
            }
        }

        for (Character ch : list) {
            builder.append(ch);
        }

        return builder.toString();
    }
}

원래 내 담당이 아닌 문제였는데, 문제가 너무 흥미로워서 직접 해봤다. list를 조금 많이 썼는데, char 배열로도 할 수 있을 것 같다. 하지만 내가 보기엔 list가 더 깔끔해보여서 이게 나은 것 같다. 난이도는 평범한 수준이었다.

 

아래 코드는 다른 사람이 작업한건데 너무나도 마음에 들어서 가져와봤다.

import java.util.stream.Collectors;
import java.util.stream.IntStream;

class Solution {
    public String solution(String new_id) {
        return NewIdFactory.createFrom(new_id)
                .getNewId();
    }

    private static class NewIdFactory {
        public static NewId createFrom(String newId) {
            // 모든 기능을 수행한 인스턴스 생성 후 return
            return new NewId(newId)
                    .toLowerCase() // 1단계
                    .removeInvalidLetters() // 2단계
                    .replaceMultipleDots() // 3단계
                    .removeSideDotsIfPresent() // 4단계
                    .replaceIfEmpty() // 5단계
                    .removeOverloads() // 6단계-1
                    .removeBackDotsIfPresent() // 6단계-2
                    .appendInsufficientLetters(); // 7단계
        }
    }

    private static class NewId {
        private static final String REMOVAL_REGEX = "[^a-z0-9\\-_.]";
        private static final String DEFAULT_ID = "a";
        private static final Integer MAX_LENGTH = 15;
        private static final Integer MIN_LENGTH = 3;
        private final String newId;

        public NewId(String newId) {
            this.newId = newId;
        }

        public NewId toLowerCase() {
            return new NewId(newId.toLowerCase());
        }

        public NewId removeInvalidLetters() {
            return new NewId(newId.replaceAll(REMOVAL_REGEX, ""));
        }

        public NewId replaceMultipleDots() {
            StringBuilder builder = new StringBuilder();

            for (int i = 0, length = newId.length(); i < length; i++) {
                if (newId.charAt(i) == '.') {
                    while (i < length && newId.charAt(i) == '.') {
                        i++;
                    }

                    builder.append(".");
                    i--;
                    continue;
                }

                builder.append(newId.charAt(i));
            }

            return new NewId(builder.toString());
        }

        public NewId removeSideDotsIfPresent() {
            return removeFrontDotsIfPresent().removeBackDotsIfPresent();
        }

        public NewId removeFrontDotsIfPresent() {
            if (newId.startsWith(".")) {
                return new NewId(newId.substring(1));
            }

            return this;
        }

        public NewId removeBackDotsIfPresent() {
            if (newId.endsWith(".")) {
                return new NewId(newId.substring(0, newId.length() - 1));
            }

            return this;
        }

        public NewId replaceIfEmpty() {
            if (newId.isEmpty()) {
                return new NewId(DEFAULT_ID);
            }

            return this;
        }

        public NewId removeOverloads() {
            if (newId.length() > MAX_LENGTH) {
                return new NewId(newId.substring(0, MAX_LENGTH));
            }

            return this;
        }

        public NewId appendInsufficientLetters() {
            if (newId.length() < MIN_LENGTH) {
                String appendedId = IntStream.range(0, MIN_LENGTH - newId.length())
                        .mapToObj(i -> String.valueOf(newId.charAt(newId.length() - 1)))
                        .collect(Collectors.joining());

                return new NewId(newId.concat(appendedId));
            }

            return this;
        }

        public String getNewId() {
            return newId;
        }
    }
}

메소드명의 직관성, 내용의 간략함, 객체지향스러움까지 모두 가지고 있는 모습이 너무 예뻤다. 이게 마치 자바프로그래밍이다 라는 느낌이 드는 좋은 코드인 것 같다. 이렇게 작성하는 습관을 들여놔야 자바 개발자라는 말을 할 수 있지 않을까 싶다.

댓글