코딩 테스트 일기 - 08

윤수빈·2024년 8월 19일
0

오늘 코딩테스트를 진행하며 새로 배운 점과 힘들었던 부분에 대해 정리해본다.

1. SQL

자동차 대여 기록에서 장기/단기 대여 구분하기

문제 설명 :

다음은 어느 자동차 대여 회사의 자동차 대여 기록 정보를 담은 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블입니다. CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블은 아래와 같은 구조로 되어있으며, HISTORY_ID, CAR_ID, START_DATE, END_DATE 는 각각 자동차 대여 기록 ID, 자동차 ID, 대여 시작일, 대여 종료일을 나타냅니다.

문제 :

CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서 대여 시작일이 2022년 9월에 속하는 대여 기록에 대해서 대여 기간이 30일 이상이면 '장기 대여' 그렇지 않으면 '단기 대여' 로 표시하는 컬럼(컬럼명: RENT_TYPE)을 추가하여 대여기록을 출력하는 SQL문을 작성해주세요. 결과는 대여 기록 ID를 기준으로 내림차순 정렬해주세요.

문제 풀이 :

  1. 대여기록 ID, 자동차 ID, 대여 시작일, 대여 종료일, 대여 상태를 출력해야함(select)
  2. CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서 가져와야함 (from)
  3. 2022년 09월에 대여를 시작한 기록에 대해서만 가져와야함 (where)
  4. 대여 기록ID를 내림차 순으로 정렬해야함(order by)
  5. 대여 시작일과 대여 종료일은 (yyyy-mm-dd) 형태로 나타내야함
  6. 대여 상태의 경우 대여 기간이 30일 이상이면 '장기 대여', 그 외에는 '단기 대여'로 해야함

위 풀이를 토대로

(틀린 정답)
SELECT HISTORY_ID, CAR_ID, 
date_format(START_DATE, '%Y-%m-%d') START_DATE, 
date_format(END_DATE, '%Y-%m-%d') END_DATE,
    case when DATEDIFF(END_DATE, START_DATE)>=30 then '장기 대여'
         else '단기 대여' end RENT_TYPE
from CAR_RENTAL_COMPANY_RENTAL_HISTORY
where date_format(START_DATE, '%Y%m') = '202209'
order by HISTORY_ID desc

을 했는데 결과는 분명 맞는데 틀린 정답으로 나왔다..

무엇이 문제인지 찾아본 결과,
DATEDIFF 여기 부분이 문제였다.

DATEDIFF 함수의 경우 시작일과 종료일이 같아도 1일로 친다는 것이었다..

DATEDIFF(END_DATE, START_DATE)>=30 이 부분은 결국 31일 이상인 경우를 장기대여로 본 것이기 때문에 30일을 하려면 DATEDIFF(END_DATE, START_DATE)>=29 로 변경해주어야 한다.

(정답 코드)
SELECT HISTORY_ID, CAR_ID, 
date_format(START_DATE, '%Y-%m-%d') START_DATE, 
date_format(END_DATE, '%Y-%m-%d') END_DATE,
    case when DATEDIFF(END_DATE, START_DATE)>=29 then '장기 대여'
         else '단기 대여' end RENT_TYPE
from CAR_RENTAL_COMPANY_RENTAL_HISTORY
where date_format(START_DATE, '%Y%m') = '202209'
order by HISTORY_ID desc

다음부터 DATEDIFF를 사용할 때 유의해야겠다..


2. JavaScript

1. 로또의 최고 순위와 최저 순위

문제 링크

문제 풀이

변수 선언부

let best = 0;		// 최고 갯수를 저장하기 위한 변수
let worst = 0;		// 최하 갯수를 저장하기 위한 변수
let answer=[];		// 정답을 반환하기 위한 배열
lconst lottos_win = {	// 로또 번호가 맞은 갯수:등수로 key:value를 나눈 오브젝트
    6:1, 5:2, 4:3, 3:4, 2:5, 1:6, 0:6,
}					

로또 번호를 맞춘 최고, 최하갯수를 미리 정해진 틀에서 가져올 수 있도록 만들고 싶어서 lottos_win 객체와 best, worst를 변수로 선언했다.

그리고 로직 처리 부분은

    for(let i=0; i<lottos.length; i++){
        const index = win_nums.findIndex(function (win_num) {
            return lottos[i] === win_num;
            })
        if(index !== -1) {
            lottos.shift();
            i--;
            win_nums.splice(index,1);
            best++;
            worst++;
        }
        if(lottos[i] === 0) {
            best++;
        }
    }

    answer[0] = lottos_win[best];
    answer[1] = lottos_win[worst];
    
    return answer;
  1. lottos에 담긴 값을 반복하여 win_nums에서 findIndex()로 찾는다.
  2. 만약 값이 담겨있다면 lottos에서는 shift(), findIndex()로 반환된 값을 저장하여 win_nums 배열에 접근한다.
    2-1. win_nums에 담겨있던 값도 삭제해준다. (2개 이상인 경우를 생각)
    2-2. best와 worst의 값을 모두 증가시켜준다.
  3. lottos에 담긴 값을 반복한다.
    3-1. 만약 lottos에 담긴 값이 '0' 이라면 best를 증가시켜준다.
  4. answer[0]에 lottos_win[best-1], answer[1]에 lottos_win[worst-1]을 넣어준다.
  5. answer을 반환한다.

이런 방향으로 생각하게 되었다.

제한 사항 중

  • 0을 제외한 다른 숫자들은 lottos에 2개 이상 담겨있지 않습니다.

부분 때문에 중복문제가 있을 수 있어 splice() 메서드로 잘라내야겠다고 접근했던 것 같다.

2. 옹알이(2)

문제 링크

문제 풀이

어려웠던 부분은 동일한 패턴이 다음에 이어질 수 없다는 점이었다.
단순 반복이 아니라 이전의 패턴을 기억해서 처리해야 하는 로직이 있다보니 푸는데 시간이 조금 걸렸다..

function solution(babblings) {
    // 1. answer을 담아줄 변수, 바블링이 가능한 패턴의 상수를 선언
    let answer = 0;
    const CAN_BABBLING = ["aya", "ye", "woo", "ma"];
    
    // 2. 매개변수로 받은 바블링을 반복
    babblings.forEach(babbling => {
        // 3. 반복되는 값을 배열로 바블리스트에 담기
        let babble_list=[...babbling];
        
        // 4. 검증을 위한 바블과 이전에 사용했던 바블을 담기위한 변수 선언
        let unstableBabble = '';
        let prevBabble ='';

        // 5. 바블리스트의 길이가 1이상이면 반복
        while(babble_list.length) {
            
            // 6. 검증을 위한 바블에 바블리스트의 배열값을 하나씩 추가
            unstableBabble += babble_list.shift();
            // 7. 검증을 위한 바블이 가능한버블 패턴에 속하는지 확인
            const isPossible = CAN_BABBLING.includes(unstableBabble);
            
            // 8. 가능한 바블패턴이라면 이전 사용했던 바블과 동일한것인지 확인
            if(isPossible) {
                // 9. 동일하다면 넘기기, 동일하지 않다면 다음 로직 수행
                if(prevBabble === unstableBabble) {
                    continue;
                }
                // 10. 이전 사용한 바블에 현재 바블을 재할당 
                prevBabble = unstableBabble;
                // 11. 현재 검증된 바블을 초기화
                unstableBabble = '';
            }
        }
        
        // 12. 만약 현재 검증된 바블이 초기값이라면 정답을 증가
        if(unstableBabble === '') {
            answer++;
        }
    })
    
    return answer;
}

필요했던 변수가 많았던 것 같다.
1. 옹알이를 할 수 있는 패턴들을 const CAN_BABBLING = ["aya", "ye", "woo", "ma"]
2. 매개변수로 받은 옹알이들이 모인 배열을 반복하며 반복하면서 나오는 값들을 배열로 저장해 줄 let babble_list=[...babbling]으로 바로 배열로 반환받을 수 있도록 Spread Operator를 사용
3. 검증해야하는 바블을 let unstableBabble='';
4. 이전에 사용했던 바블 패턴을 let prevBabble ='';
5. 바블이 바블패턴에서 유효한지 확인하기 위해 const isPossible
을 선언해주었다..

상세 로직은 코드에 설명대로 동작하도록 했다.


3. 숫자 짝꿍

문제 풀이

숫자 짝꿍의 경우 제출에서 실패하여 코드를 변경했다.
기존 코드에서 11번~16번 테스트가 계속 초과되어 실패하는 상황이 있었고,
예상하는 것은 while문 안에서 findIndex() 메서드를 사용할때 중첩 반복으로 인해 자릿수가 큰 숫자의 경우 처리하기 어려운 것으로 판단되었다..

(기존 코드)
function solution(X, Y) {
    var answer;
    let arrX = [...X];
    let arrY = [...Y];
    let possibleNumber ='';

    
    while(arrX.length){
        const index = arrY.findIndex(e=>e===arrX[0]);
        if(index !== -1) {
            possibleNumber += arrX[0];
            arrY.splice(index, 1);
        }
        arrX.shift();
    }
    
    answer = possibleNumber.split('').sort((a, b) => {
        return a > b ? -1 : 1;
    }).join('');
    
     
    if(answer==='') {
        answer = '-1';
    }
    else if(parseInt(answer) === 0) {
        answer = '0';
    }
    
    return answer;
}

위 코드는 간략하게 설명하면 arrX와 arrY를 배열로써 새로 반환받고,
arrX를 기준으로 arrY에 동일한 번호가 있는지 확인하여 possibleNumber에 저장, arrY에서 값을 삭제한다.
그리고 possibleNumber에 담긴 값을 정렬하고 answer에 담아 값을 확인하여 반환하는 로직이었다.

(변경 코드)
function solution(X, Y) {
    let answer='';
    let arrX=[];
    let arrY=[];
    for(let i=9; i>=0; i--) {
        const filterX = X.split('').filter(function(value) {
            return value == i;
        });
        const filterY = Y.split('').filter(function(value) {
            return value == i;
        });
        arrX.push(filterX);
        arrY.push(filterY);
    }
    
    for(let i=0; i<10; i++) {
        if((arrX[i].length>=1) && (arrY[i].length>=1)) {
            const count = Math.min(arrX[i].length, arrY[i].length);
            for(let j=0; j<count; j++) {
                answer += (9-i).toString();
            }
        }
    }
    
    if(answer==='') {
        answer = '-1';
    }
    else if(parseInt(answer) === 0) {
        answer = '0';
    }
    
    return answer;
}

변경된 코드의 경우일일이 다 비교하는 것이 아니라 어쨋든 숫자는 0~9까지 표현되는 점을 생각하여 이 부분을 활용해서 구현했다.
1. filter를 통해 각 숫자를 가지고 있는 수만큼 해당 숫자를 별도 배열(arrX, arrY)에 담았다.
2. arrX와 arrY는 이때 9부터 0까지 순서대로 저장되어 있다.
3. 이제 arrX와 arrY의 0 인덱스부터 순서대로 반복하여 조건을 물었다.
3-1. 만약 arrX와 arrY의 길이가 0보다 큰 경우 (숫자가 있다는 말) Math.Min() 메서드를 통해 가장 작은 길이를 저장했다. => 공통된 부분 찾는 것
4. 가장 작은 길이만큼 반복하여 answer에 9-인덱스번호(arrX와 arrY 값들이 내림차순으로 정렬되어 있기 때문)로 문자열로 변환하여 더해주었다.
5. 만약 answer 일치하는게 없어 '' 이면 '-1'
5-1. 0이 여러개 있을 경우를 대비하여 parseInt() 함수를 통해 '00000' 이어도 0이 되도록 만들고 answer에 '0'
5-2. 아니라면 그냥 answer를 반환함으로써 11~16 테스트가 무사히 통과되었다..

단순 반복으로 처리하다보면 시간복잡도가 데이터 크기 + 반복하는 수에 따라 과부하가 걸릴 수 있다는 점, 시간이 오래걸릴 수 있다는 점을 느낄 수 있는 문제였다..

자릿수가 3,000,000까지 였으니 기존코드는 얼마나 반복해야할지 감당이 안된다...

다음부터는 시간복잡도를 고려하여 조금 더 최적화된 알고리즘을 구현할 수 있도록 해야겠다.

profile
정의로운 사회운동가

0개의 댓글