Problem Solving/Programmers (14)

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/144854

 

풀이

작가 아이디가 일치하고 경제인 행에 대해서만 조회

 

코드

select BOOK_ID, AUTHOR_NAME, date_format(PUBLISHED_DATE,"%Y-%m-%d") as PUBLISHED_DATE
from BOOK as b, AUTHOR as a
where CATEGORY = "경제" and b.AUTHOR_ID = a.AUTHOR_ID
order by PUBLISHED_DATE;

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/133026

 

풀이

조인 실행 후 그룹함수를 적용한다.

그런데 문제 조건에 총주문량이 작은 순서대로 조회하는 SQL 문이라고 적혀있는데 해당 조건을 안 써도 통과가 된다.

그래도 정렬을 추가해서 다시 제출했다.

 

코드

select INGREDIENT_TYPE, SUM(TOTAL_ORDER) as TOTAL_ORDER
from FIRST_HALF as f join ICECREAM_INFO as i
on f.FLAVOR = i.FLAVOR
group by INGREDIENT_TYPE
order by TOTAL_ORDER;

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/131530

 

풀이

그룹화 하여 그룹화 한 대상과 개수를 표시한다

TRUNCATE(대상, 자리수)는 대상을 자리수만큼 보일 수 있게 내림을 한다.

TRUNCATE(12.345, 2) 12.34
TRUNCATE(12.345, 0) 12
TRUNCATE(12.345, -2) 0

마이너스가 붙으면 0이 1의 자리가 되어 12가 나타나고, -2인 경우 3의 자리가 없이 때문에 0이 된다. -1을 하면 10이 나오게 됨

 

truncate를 PRICE_GROUP라고 지정했기 때문에 이것을 기준으로 그룹 함수를 사용한다.

 

코드

select truncate(price, -4) as PRICE_GROUP, count(product_id) as PRODUCTS
from product
group by PRICE_GROUP
order by PRICE_GROUP;

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/131115

 

풀이

집계 함수 max를 사용하면 하나의 행만 리턴하기 때문에 최대 가격을 서브 쿼리로 가져오고 해당하는 가격의 상품 정보를 select 한다

 

코드

select product_id, product_name, product_cd, category, price
from food_product
where price = (
    select max(price)
    from food_product
);

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/131697

 

코드

select MAX(price) as MAX_PRICE
from product

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/164673

 

풀이

첫번째 방법 : 조인

두번째 방법 : 테이블 2개 두고 where로 조건 걸기

 

어떤 것이 더 효율적인 방법인지 생각해보고 싶다

조인의 경우는 where절에서 필터링을 걸기 때문에 조인을 수행하고 난 뒤면 데이터의 크기가 너무 커지지 않는지 궁금하다.

그리고 두번째 방법을 쓰는 경우 where에서 board_id가 같은 것만 선택하는 것 깜빡하지 않기!!

 

코드

# 조인
select
    TITLE, 
    b.BOARD_ID, 
    REPLY_ID, 
    r.WRITER_ID, 
    r.CONTENTS, 
    DATE_FORMAT(r.CREATED_DATE, "%Y-%m-%d") as CREATED_DATE
from USED_GOODS_BOARD as b join USED_GOODS_REPLY as r
on b.BOARD_ID = r.BOARD_ID
where YEAR(b.CREATED_DATE) = 2022 and MONTH(b.CREATED_DATE) = 10
order by r.CREATED_DATE, TITLE;

# where
select 
    title, 
    b.board_id, 
    reply_id, 
    r.writer_id, 
    r.contents, 
    date_format(r.created_date, "%Y-%m-%d") as CREATED_DATE
from used_goods_board as b, used_goods_reply as r
where b.board_id = r.board_id
and date_format(b.created_date, "%Y-%m") = '2022-10'
order by r.created_date, title;

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/133025

 

풀이

두가지 방법으로 풀었다.

첫번째는 서브쿼리를 사용하였고, 두번째는 join을 사용했다

 

코드

# 서브쿼리
select flavor
from first_half
where total_order > 3000 and flavor in (
    select flavor
    from icecream_info
    where ingredient_type = 'fruit_based'
)
order by total_order desc;

# 조인
select first_half.flavor
from first_half join icecream_info
on first_half.flavor = icecream_info.flavor
where total_order > 3000 and ingredient_type = 'fruit_based'
order by total_order desc;

 

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/131120

 

풀이

DATE 타입을 출력하면 1992-03-16 00:00:00으로 나오기 때문에 DATE_FORMAT(DATE_OF_BIRTH, "%Y-%m-%d") as DATE_OF_BIRTH를 사용했다.

%Y 4자리 연도 %y 2자리 연도
%M 영어 월 %m 2자리 월
%D 일+th %d 2자리 일

DATE 타입에서 월만 가져오려면 MONTH(컬럼명)을 사용한다. 이외에 YEAR(대상), MONTH(대상), DAY() 아니면 DAYOFMONTH()가 있다.

 

코드

SELECT MEMBER_ID, MEMBER_NAME, GENDER, DATE_FORMAT(DATE_OF_BIRTH, "%Y-%m-%d") as DATE_OF_BIRTH
FROM MEMBER_PROFILE
WHERE GENDER = 'W' 
    and MONTH(DATE_OF_BIRTH) = 3 
    and TLNO is not null
ORDER BY MEMBER_ID;

문제 링크

https://school.programmers.co.kr/learn/courses/30/lessons/150368

접근 방법

  1. 이모티콘 마다 할인율을 dfs로 구함
  2. 사용자 수만큼 반복
    1. 이모티콘 수만큼 반복
      • 이모티콘 할인율 ≥ 사용자 라면 할인율을 계산해서 지불할 금액에 더함
    2. 지불할 금액 ≥ 제한금액이면 이모티콘 플러스 가입
    3. 아니라면 판매액에 지불할 금액 더함
  3. 결과값이랑 비교
    1. 이모티콘 플러스 사용자가 많다면 갱신
    2. 이모티콘 플러스 사용자가 같다면 판매액이 더 많은 것 갱신

블라인드 코테에선 못 풀었는데 끝나고 반년 뒤에 푸니까 풀렸다...

할인율을 배열로 선언할 필요 없이 for문의 i를 40 30 20 10으로 줄어들게 해도 된다.

이렇게 작성하는게 더 깔끔하다는걸 우리의 스터디 알고신에게 배움

이모티콘 금액 계산하다가 이모티콘 플러스 가입 금액이 넘으면 반복문 탈출해도 된다. 여기서 시간을 단축시킬 수 있다고 한다. 이것 또한 알고신의 첨언...

코드

class Solution {
    static int len, resEmo, resSell;
    static int[] r = {40, 30, 20, 10};
    static int e[], u[][];
    public static int[] solution(int[][] users, int[] emoticons) {
        e = emoticons;
        u = users;
        len = emoticons.length;
        resEmo = 0;
        resSell = 0;

        //이모티콘마다 할인율 10, 20, 30, 40 중 선택
        dfs(0, emoticons.length, new int[emoticons.length]);

        System.out.println(resEmo+" "+resSell);

        return new int[]{resEmo, resSell};
    }
    private static void dfs(int dept, int len, int[] s){
        //종료조건
        if(dept == len){
            calcMember(s);
            return;
        }

        //이모티콘 선택
        for (int i = 0; i < 4; i++) {
            s[dept] = r[i]; //할인율 선택
            dfs(dept+1, len, s);
        }
    }

    private static void calcMember(int[] s) {

        int emoPlus = 0;
        int totalSell = 0;

        //사용자 수만큼 for, 구매할 사람 선택
        for(int[] user : u){
            int personRate = user[0];
            int limitMoney = user[1];

            //한 사람이 지불할 가격
            int money = 0;
            //살 이모티콘 선택
            for (int i = 0; i < len; i++) {
                if(s[i] >= personRate){ //일정 비율 이상 할인이라면
                    //살 돈 계산
                    money += e[i] * (100-s[i]) / 100;
                }
            }//살 이모티콘 계산 끝

            //이모티콘 플러스 가입 여부 확인
            if(money >= limitMoney){
                //이모티콘 플러스 가입
                emoPlus++;
            }else{
                totalSell += money; //판매 총액 갱신
            }
        }

        //최종 이모티콘 플러스 가입자, 판매총액 비교
        if(resEmo < emoPlus){
            resEmo = emoPlus;
            resSell = totalSell;
        }else if(resEmo == emoPlus && resSell < totalSell){
            resSell = totalSell;
        }
    }
}

[ 동작 순서 ]

  1. 맵 확장
    • 배열 생성
    • 자물쇠 위치
  2. 열쇠 위치 → 모든 위치 확인
    • 확장맵과 열쇠를 더해줌
    • 자물쇠 범위 확인
      • true → return true
      • false → 열쇠 회전
  3. 반복문 종료시 return false → 열쇠와 자물쇠가 맞지 않음

 

맵 확장

- 배열 생성

자물쇠는 N의 크기를 가지고 있다. 열쇠가 배열의 범위를 벗어나 확인이 가능하기 때문에 맵을 N + (M*2) - 2 만큼 확장해 준다.

자물쇠의 크기 + 열쇠 2개 - 겹치는 부분

 

 

- 배열 중심에 자물쇠 위치 시키기

맵의 중심에 자물쇠를 위치시킨다. 확장된 맵에서의 자물쇠의 위치는 열쇠길이-1 부터 시작위치에서 자물쇠의 길이만큼 더해준 값이다.

자물쇠를 확장맵에 위치시키고 나면 다음과 같이 맵이 나타난다.

 

열쇠 위치

2중 반복문을 사용한다. 인덱스의 범위는 (0,0) 부터 자물쇠의 마지막 위치인 (열쇠의 길이+자물쇠의 길이)-1 만큼이다.

 

확장맵 + 열쇠

확장된 맵에 현재 열쇠의 인덱스 (i, j) 부터 열쇠의 크기까지 맵에 더한다

이 결과로 맵에 저장되는 값은 0, 1, 2인데 문제에서 구하고자 하는 값은 1이 된다.

0 : 열쇠와 자물쇠의 홈끼리 만난 경우

1 : 열쇠의 돌기와 자물쇠의 홈이 만난 경우 → 정상

2 : 열쇠의 돌기와 자물쇠의 돌기가 만난 경우

 

자물쇠가 열리는지 확인

전체 맵을 확인 할 필요 없이 자물쇠가 있는 위치만 확인한다. 이 문제의 답은 자물쇠의 홈이 모두 채워지는 경우여야 하기 때문이다.

자물쇠의 범위는 (열쇠의 길이 - 1) ~ (열쇠의 길이 + 자물쇠의 길이 - 1) 이 된다.

해당 범위를 확인하는 check 메서드를 만들어 범위가 전부 1인지 확인한다. check 메서드가 true를 반환한다면 현재 열쇠는 맞는 열쇠가 되기 때문에 true를 return 하고 종료한다. false를 반환한다면 현재 열쇠가 맞지 않기 때문에 방향을 바꿔준다.

방향을 모두 바꿔도 맞지 않는다면 다음 위치로 넘어간다.

 

열쇠 회전

한번에 90도씩 회전시킨다. 90도를 회전시키면 배열의 크기가 3인 인덱스의 매칭은 다음과 같다.

[행][열]

rotate[0][0] = origin[2][0]

rotate[0][1] = origin[1][0]

rotate[0][2] = origin[0][0]

 

rotate[1][0] = origin[2][1]

rotate[1][1] = origin[1][1]

rotate[1][2] = origin[0][1]

 

rotate[2][0] = origin[2][2]

rotate[2][1] = origin[1][2]

rotate[2][2] = origin[0][2]

행과 열을 읽을 때 행을 아래에서부터 위로 읽은 것을 열에 저장한다. 복잡하게 설명하지만 직접 써보면서 풀면 이해가 쉬워진다.

 

전체 코드

public class Solution_자물쇠와열쇠 {

    static int M, N;

    public static boolean solution(int[][] key, int[][] lock) {
        N = lock.length; //자물쇠의 크기
        M = key.length; //열쇠의 크기

        //맵을 자물쇠의 크기에서 열쇠의 크기만큼 확장해줌
        int[][] map = new int[N + (M * 2) - 2][N + (M * 2) - 2];

        //중심에 자물쇠 위치시키기. i/j -> map에 대한 인덱스. k/l -> 자물쇠에 대한 인덱스
        for (int i = M - 1, k = 0; i < M - 1 + N; i++, k++) {
            for (int j = M - 1, l = 0; j < M - 1 + N; j++, l++) {
                map[i][j] = lock[k][l];
            }
        }

        //임시 복사 배열
        int[][] copyMap = new int[map.length][map.length];

        //0,0부터 N-1+M까지 열쇠 두기
        for (int i = 0; i < M + N - 1; i++) {
            for (int j = 0; j < M + N - 1; j++) {
                for (int d = 0; d < 4; d++) {
                    //맵 복사
                    MapCopy(copyMap, map);

                    //열쇠를 복사맵에 더함
                    for (int k = 0; k < M; k++) {
                        for (int l = 0; l < M; l++) {
                            copyMap[i + k][j + l] += key[k][l];
                        }
                    }

                    //자물쇠가 열리는지 확인
                    if (check(copyMap)) { //맞는다면 트루
                        return true;
                    }

                    //방향이 아니라면 돌려줌
                    key = rotate(key);
                }
            }//end j
        }//end i for check all index

        return false;
    }

    private static void MapCopy(int[][] copyMap, int[][] map) {
        //맵 복사함
        for (int k = 0; k < map.length; k++) {
            System.arraycopy(map[k], 0, copyMap[k], 0, map[k].length);
        }
    }

    private static int[][] rotate(int[][] key) {
        int[][] copyKey = new int[M][M];
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < M; j++) {
                copyKey[i][j] = key[M - 1 - j][i];
            }
        }
        return copyKey;
    }

    private static boolean check(int[][] copyMap) {
        //자물쇠 영역이 전체가 1인지 확인함
        for (int k = M - 1; k < M - 1 + N; k++) {
            for (int l = M - 1; l < M - 1 + N; l++) {
                if (copyMap[k][l] != 1) { //1이 아닌 값이 들어갔다면 돌기끼리 만났거나 홈이 안채워짐
                    return false;
                }
            }
        }
        return true;
    }
}
1 2