[프로그래머스] 1. 체육복(Lv1), 2. 괄호 회전하기(Lv2), 3. 행렬의 곱셈(Lv2)

손규성·2022년 10월 14일
0

alogrithm

목록 보기
9/22
post-thumbnail

Lv. 1: 체육복✍️


문제 설명

점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.

전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • 전체 학생의 수는 2명 이상 30명 이하입니다.
  • 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
  • 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
  • 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
  • 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.

나의 답안

function solution(n, lost, reserve) {
    let answer = n - lost.length;

    lost.sort((a,b) => a-b);
    reserve.sort((a,b) => a-b);
    
    for(let i = 0; i < lost.length; i++) {
        for(let j = 0; j < reserve.length; j++) {
            if(lost[i] === reserve[j]) {
                answer++;
                lost.splice(i, 1);
                reserve.splice(j, 1);
            }
        }
    }
    
    let len = lost.length;
    for(let i = 0; i < len; i++) {
        let x = lost[0];
        lost.splice(0, 1);
        for(let j = 0; j < reserve.length; j++) {
            if(x === reserve[j] || reserve[j] + 1 === x || reserve[j] - 1 === x) {
                answer++; 
                reserve.splice(j, 1);
                break;
            }
        }
    }
    return answer;
}

접근 방식

  • 총 학생수에서 체육복을 도난당한 학생의 수를 빼서 여벌 체육복 없이 수업에 참여할 수 있는 학생 수를 구한다.
  • 반복문을 통해 lostreserve 배열을 순회하며 체육복을 도난당한 학생에게 여벌 체육복을 빌려줄 수 있는지 확인한다
  • 여벌 체육복이 있는 경우, 단 한 명의 학생에게만 빌려줄 수 있기 때문에 reserve.splice(j, 1)을 통해 빌려준 학생의 번호는 reserve에서 제거한다.
  • 마찬가지로 체육복을 이미 빌린 학생이 다시 빌리는 경우를 막기 위해 한번 순회할 때마다 x라는 변수에 lost[0]값을 선언하고 lost.splice(0, 1)을 통해 lost를 업데이트 한다.
  • 또한, 도난당한 학생 중 이미 여벌 체육복을 가지고 있는 학생, 즉 lost에도 reserve에도 포함되어 있는 학생이 있을 수 있다. 그렇기 때문에 우선 lostreserve를 오름차순 정렬한 뒤, 두 배열에서 공통적으로 등장하는 경우를 미리 카운트해주고, 해당 값을 두 배열에서 제거한다.

아쉬웠던 점

  • 처음 문제를 읽고 너무 쉬운 것 같다는 생각에 마음 편히 접근했다가 생각보다 풀이하는 데 오래 걸렸다. 위에 언급했듯이 한 학생이 lostreserve에 둘 다 포함되어 있는 경우를 전혀 생각하지 못해서 계속 맞추지 못하다가, 문제 설명을 다시 한번 천천히 읽어보고 나서야 무엇이 잘못되었는지 깨달았다.
  • 이후에도 sort를 통해 오름차순 정렬을 하지 않고 계속 문제풀이를 진행해서 테스트 2-3개를 반복적으로 틀렸다. 결국 VS Code에서 여러 테스트 케이스를 만들어보고 풀어보다 정렬하는 것이 중요하다는 점을 발견했다.
  • 이중반복문을 두번 돌리는 게 과연 최선의 선택일지 의문이 많이 들었다.


Lv. 2: 행렬의 곱셈✍️


문제 설명

2차원 행렬 arr1과 arr2를 입력받아, arr1에 arr2를 곱한 결과를 반환하는 함수, solution을 완성해주세요.

제한사항

  • 행렬 arr1, arr2의 행과 열의 길이는 2 이상 100 이하입니다.
  • 행렬 arr1, arr2의 원소는 -10 이상 20 이하인 자연수입니다.
  • 곱할 수 있는 배열만 주어집니다.

나의 답안

function solution(arr1, arr2) {
  let sum = 0, temp = [], answer = [];

  for(let i = 0; i < arr1.length; i++) {
    for(let j = 0; j < arr2[0].length; j++) {
      for(let k = 0; k < arr2.length; k++) {
        sum += arr1[i][k] * arr2[k][j];
      }
      temp.push(sum);
      sum = 0;
    }
    answer.push(temp);
    temp = [];
  }
  return answer;
}

접근 방식

  • 이 문제의 핵심 포인트는 행렬의 곱셈이 어떻게 진행되는 것인지 이해하는 것이다.
    (나처럼 행렬의 곱셈을 정확히 어떻게 하는건지 헷갈리시거나 모르시는 분은 꼼수학님의 영상 참고)
  • arr1의 행을 arr2의 모든 열에 곱하기 위해서 3개의 for-loop을 사용하는데, 이때
    1. iarr1의 길이, 즉 첫 번째 행렬의 행인 셈이고,
    2. jarr2에 들어있는 있는 배열의 길이, 즉 두 번째 행렬의 열이고,
    3. karr2의 길이, 즉 두 번째 행렬의 열의 길인 셈이다.
  • 이 세개의 인덱스를 사용해서 두 행렬을 순서대로 탐색하며 sum값을 만든다.
  • 세 번째 반복문이 끝나면 temp라는 배열에 값을 넣어주고 sum값을 초기화 한다.
  • 두 번째 반복문도 끝나면 temp값이 한번 완성되었다는 뜻이기 때문에 배열 형태 그대로 answer라는 배열에 넣어줘서 이중배열을 만들고, temp값은 초기화해준다.
  • 모든 for-loop이 끝나면 answer을 반환해준다.

아쉬웠던 점

  • 삼중for문이다 보니 코드를 작성하다가 잠시 전화를 받고 오니까 i, j, k가 각자 무엇이었는지 헷갈렸다... 처음부터 확실히 어떤 인덱스가 무엇을 상징하고 있는지 이해하고 접근하면 문제 자체의 난이도는 낮은 편이다.


Lv. 2: 괄호 회전하기✍️


문제 설명

다음 규칙을 지키는 문자열을 올바른 괄호 문자열이라고 정의합니다.

  • (), [], {} 는 모두 올바른 괄호 문자열입니다.
  • 만약 A가 올바른 괄호 문자열이라면, (A), [A], {A} 도 올바른 괄호 문자열입니다. 예를 들어, [] 가 올바른 괄호 문자열이므로, ([]) 도 올바른 괄호 문자열입니다.
  • 만약 A, B가 올바른 괄호 문자열이라면, AB 도 올바른 괄호 문자열입니다. 예를 들어, {} 와 ([]) 가 올바른 괄호 문자열이므로, {}([]) 도 올바른 괄호 문자열입니다.

대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s가 매개변수로 주어집니다. 이 s를 왼쪽으로 x (0 ≤ x < (s의 길이)) 칸만큼 회전시켰을 때 s가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.

제한사항

  • s의 길이는 1 이상 1,000 이하입니다.

나의 답안

function parenthCheck(arr) {
    let temp = [];

    for(let j = 0; j < arr.length; j++) {        
        if(arr[0] === ')' || arr[0] === ']' || arr[0] === '}') return false;
      
        if(arr[j] === '(' || arr[j] === '[' || arr[j] === '{') temp.push(arr[j]); 
      
        if(arr[j] === '}' && temp[temp.length - 1] === '{') temp.pop();
        else if(arr[j] === ']' && temp[temp.length - 1] === '[') temp.pop();
        else if(arr[j] === ')' && temp[temp.length - 1] === '(') temp.pop();
    }

    return temp.length === 0 ? true : false; 
}

function solution(s) {
    let arr = [], count = 0;
    
    for(let i = 0; i < s.length; i++) {
        arr.push(s[i]);
    }

    if(parenthCheck(arr) === true) count++;

    for(let i = 1; i < arr.length; i++) {
        arr.push(arr[0]);
        arr.shift();
        
        if(parenthCheck(arr) === true) count++;
    }
    return count;
}

접근 방식

  • s라는 문자열이 매개변수로 주어졌을 때,
    1. s의 순서를 왼쪽으로 회전하고 (원상복귀 될 때까지 회전),
    2. 회전 후 상태가 올바른 괄호 문자인 경우의 수를 세면 된다.
  • 두 로직이 너무 선명하게 보여서 함수를 두 개 나눠서 작성했다.
  • 첫 번째 parenthCheck 함수에서는 매개변수 문자열의 올바른 괄호여부를 파악하고 boolean값을 반환한다. 이는 다음과 같은 조건으로 판단한다:
    1. 문자열이 닫힌 괄호로 시작하면 바로 false.
    2. 반대인 경우 배열temp에 넣어 준다.
    3. 닫힌 괄호인데 temp의 마지막 값이 현재 값과 올바른 괄호를 만드는 조합이라면 temp에서 마지막 값을 pop해준다.
    4. 마지막에 temp의 길이가 0이면 true, 아니면 false 반환한다.
  • 두 번째 함수에서는 s라는 문자열이 주어졌을 때,
    1. 우선 회전하기 전 s가 올바른 괄호인지 판단하고, 맞으면 count를 증가한다.
    2. 이후 for문을 통해 spushshift를 반복하고(왼쪽 회전), 한번 회전할 때마다 parenthCheck을 통해 올바른 괄호인지 판단한다. (맞으면 count증가)
  • 마지막에 count값을 반환한다.
profile
블로그 이사 → https://sqsung.tistory.com/

0개의 댓글