Algorithm

[CodeKata] 프로그래머스(Lv3) : 스티커 모으기(2)

ttaeng_99 2022. 2. 5. 18:43
반응형

🥋 Oooth More!! (Level 3) 

 

 

🧮 풀이

먼저, sticker 배열의 홀수 요소들만의 합(oSum), 짝수 요소들만의 합(eSum) 중 최대값을 max에 저장했다.

그리고, 2가지 출발점(s1 = 최대값, s2 = 최대값의 인접값 중 큰 값) 부터 1칸을 건너뛰고 두 번째, 세 번째 중 최대값을 누적하면서 스티커를 뜯은 값과 최대값(max)를 비교해 큰 값을 저장했다.

 

이처럼 풀이했을 때, 예외 케이스가 발생할 수 있는 방법이었기에 정확성도 50% 미만이고, 반복적인 순회로 효율성을 통과하지 못했다.

function solution(sticker) {
  let oSum = 0, eSum = 0;
  for (let i in sticker) {
    if (+i % 2 === 0) {
      eSum += sticker[i]
    }
    else {
      oSum += sticker[i]
    }
  }

  let max = Math.max(oSum, eSum);
  
  const e = sticker.length-1
  const s1 = sticker.indexOf(Math.max(...sticker))
  const s2 = sticker.indexOf(Math.max((sticker[s1-1] ?? sticker[e]), (sticker[s1+1] ?? sticker[0])))
  
  for (let s of [s1,s2]) {
    let arr = [...sticker];
    arr = [...arr.splice(s-1 < 0 ? e : s-1), ...arr.splice(0,(s-1 < 0 ? e : s-1))]

    let i = 1;
    let nowMax = 0;
    while (i <= e) {
      nowMax += arr[i];
      i = arr[i+2] < arr[i+3] ? i+3 : i+2
    }
    
    max = Math.max(max, nowMax)
  }
  
  return max
}

 

 

🖇 리뷰

모범답안을 보니 생각보다 풀이는 간단했다.

 

하지만 DP(Dynamic Programming)을 통해 특정 인덱스에서 가질 수 있는 최대 누적값을 저장해나가는 방법,

그리고 이를 첫 번째 스티커를 뜯는 경우 / 안뜯는 경우 2가지를 고려해서 순회한다는 아이디어를 발상하는 초기과정이 매우 어려웠을 것이다.

* DP(Dynamic Programming) 참고링크 : https://velog.io/@chelsea/1-%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95Dynamic-Programming-DP  

 

function solution(sticker) {
  const len = sticker.length + 2;
  const dp1 = new Array(len).fill(0);
  const dp2 = new Array(len).fill(0);
  
  if(sticker.length === 1) return sticker[0];
  
  for(let i = 2; i < len-1; i++)
    dp1[i] = Math.max(dp1[i-1], dp1[i-2] + sticker[i-2]);
  
  for(let i = 3; i < len; i++) 
    dp2[i] = Math.max(dp2[i-1], dp2[i-2] + sticker[i-2]);
  
  return Math.max(dp1[len-2], dp2[len-1]);
}

* 출처 : https://velog.io/@longroadhome/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-LV.3-%EC%8A%A4%ED%8B%B0%EC%BB%A4-%EB%AA%A8%EC%9C%BC%EA%B8%B0-JS  

 

  • 먼저 len 길이를 설정해준다. DP를 실행할 때, 현재 인덱스의 전값, 전전값 을 확인하므로 i = 2를 시작점으로 잡기 위해 sticker.length에 2를 더해준 것이다.
  • dp1, dp2 는 각각 DP를 실행하기 위한 배열로 0으로 초기화한다. 각각 첫 번째 스티커를 뜯는 / 뜯지 않는 경우에 해당한다.
  • sticker가 1개인 경우, sticker[0] 만 뜯을 수 있으므로 이를 반환한다.
  • 먼저, 첫 번째 스티커를 뜯는 경우(dp1)을 연산한다. dp1[2]가 sticker[0]을 뜯는 경우에 해당하며, 이 경우는 마지막 스티커를 뜯지 못하기 때문에 i가 len-1 미만까지만 확인한다.
  • DP는 현재 차례가, 1) 이전 스티커를 뜯어 현재는 뜯지 않는 경우, 2) 전전 스티커까지 뜯어 현재까지 뜯는 경우, 중 최대값을 계산한다.
  • 마찬가지로, 첫 번째 스티커를 뜯지 않는 경우(dp2)를 연산한다. 이 땐 마지막 스티커를 뜯을 수 있어서 i가 len 미만까지 확인한다.
  • dp1, dp2의 끝값이 첫 번째 스티커를 뜯는 / 뜯지 않는 각각의 경우에 대한 최대값에 해당한다. 이 중, 큰 값을 최종 반환한다.

 

동적 프로그래밍의 원리 자체는 어렵지 않다. 전과 전전의 연산값을 참고해, 현재의 연산값을 누적해나가는 방법이다. (ex) 피보나치 수열)

 

하지만, 이 문제의 경우, 첫 번째 스티커를 뜯는지 여부에 따라 2가지 연산을 해야 한다는 점, 그리고 여기에 따라 어디까지 탐색을 하는지(len) 설정하는 과정 등이 매우 어려운 문제였다!

반응형