코플릿 - 알고리즘 문제

LEE JI YOUNG·2021년 12월 14일
0

01_[Greedy] 짐 나르기

//<레퍼런스>
function movingStuff(stuff, limit) {
  let twoStuff = 0;
  let sortedStuff = stuff.sort((a, b) => a - b);

  let leftIdx = 0;
  let rightIdx = sortedStuff.length - 1;

  while(leftIdx < rightIdx) {
      if(sortedStuff[leftIdx] + sortedStuff[rightIdx] <= limit) {
          leftIdx++;
          rightIdx--;
          twoStuff++;
      } else {
          rightIdx--;
      }
  }
  return stuff.length - twoStuff;
}

02_[Greedy] 편의점 알바

// <레퍼런스>
function partTimeJob(k) {
  let result = 0;
  const wallet = [500, 100, 50, 10, 5, 1];
  for(let i = 0; i < wallet.length; i++) {
    if(k > 0) {
      const sum = Math.floor(k / wallet[i]);
      result += sum;
      k = k - (wallet[i] * sum);
    }
  }
  return result;
}
// <내 풀이>
function partTimeJob(k) {
  // TODO: 여기에 코드를 작성하세요.
  let arr = [500, 100, 50, 10, 5, 1];
  let count = 0;
  let result = k;
  for(let i = 0; i < arr.length; i++){
    if( result >= arr[i]){
      count = count + (Math.floor(result / arr[i])); 
      result = k % arr[i];
    }
  }
  return count;
}

03_[구현] 보드 게임

// <내 풀이>
function boardGame(board, operation) {
  // TODO: 여기에 코드를 작성하세요.
  const M = board.length, N = board[0].length;
  // const isValid = (row, col) => row < M && row >= 0 && col < N && col >= 0
  let row = 0, col = 0;
  let result = 0;
  
  for(let i = 0; i < operation.length; i++){
    if(operation[i] === 'R' && col+1 < M){
      col = col + 1;
      result = result + board[row][col]
    } else if(operation[i] === 'L' && col-1 >= 0){
      col = col - 1;
      result = result + board[row][col]
    } else if(operation[i] === 'D' && row+1 < N){
      row = row + 1;
      result = result + board[row][col]
    } else if(operation[i] === 'U' && row-1 >= 0){
      row = row - 1;
      result = result + board[row][col]
    } else return 'OUT'
  }

  return result;
};
// <레퍼런스>
// LOOK UP TABLE을 사용한다면 조건문을 추상화시킬 수 있습니다.
function boardGame(board, operation) {
  // TODO: 여기에 코드를 작성하세요.
  const DIR = {
    'U': [-1, 0],
    'D': [1, 0],
    'L': [0, -1],
    'R': [0, 1]
  }
  const LEN = board.length;
  const isValid = (y, x) => 0 <= y && y < LEN && 0 <= x && x < LEN;

  let Y = 0;
  let X = 0;
  let score = 0;
  for (let i = 0; i < operation.length; i++) {
    const [dY, dX] = DIR[operation[i]];
    Y += dY;
    X += dX;
    if (isValid(Y, X) === false) return 'OUT';
    score += board[Y][X];
  }
  return score;
};

04_(Advanced) [DP] 금고를 털어라

function ocean(target, type) {
  let bag = [1]; 
  for(let i = 0; i <= target; i++) bag.push(0)
  for(let i = 0; i < type.length; i++){
    for(let j = 1; j <= target; j++){
      if( type[i] <= j){
        bag[j] = bag[j] + bag[j - type[i]]
      }
    }
  }
  return bag[target];
}
// <주석있는 레퍼런스>
function ocean(target, type) {
  // bag 이라는 배열에 금액을 만들 수 있는 경우의 수를 기록
  // 각 인덱스 no# = 만드려는 금액 을 의미
  // ex) target = 5, type = [1, 2, 5] 면
  // bag[3] = 2  => 3을 만드는 경우의 수 = 1만 사용 & 1,2 함께 사용
  // bag[4] = 2  => 4를 만드는 경우의 수 = 1만 사용 & 1,2 함께 사용
  // bag[5] = 4  => 5를 만드는 경우의 수 = 1*5 , 1*3 + 2, 1 + 2*2, 5*1
  // 0 을 만들 수 있는 경우는 아무것도 선택하지 않으면 되기 때문에 bag[0] = 1 로 초기값 설정
  let bag = [1];

  // 인덱스 no# = 만드려는 금액 이기 때문에
  // bag 을 target 금액만큼의 길이를 가진 배열을 만들어 주고,
  // 경우의 수를 저장하기 위해 초기값은 모두 0으로 만들어 준다
  for(let i = 1; i <= target; i++)
    bag[i] = 0;
  // 돈의 종류가 담겨있는 배열을 순차적으로 탐색   
  for(let i = 0; i < type.length; i++) {
  // target 금액까지 순차적으로 1씩 증가하면서    
    for(let j = 1; j <= target; j++)
  // bag의 인덱스가 type[i] 보다 큰 구간만
  // (작은 구간은 type[i]로 만들 수 없는 금액이기 때문에 탐색할 필요가 없다)    
      if(type[i] <= j)
  // 기존 경우의 수에 type[i]를 뺀 금액을 만들 수 있는 경우의 수를 더해준다       
        bag[j] += bag[j-type[i]];
  }
  // bag 의 target 인덱스에 target 금액을 훔칠 수 있는 경우의 수가 쌓이므로
  // 해당 값을 리턴해 준다
  return bag[target];
}

05_[중복순열] 가위바위보

function  rockPaperScissors(selectNumber) {

  if(selectNumber === undefined) selectNumber = 3;

  const result = [];
  let lookup = ['rock', 'paper', 'scissors'];

  function recursion(count, bucket){
    if(count === 0){
      result.push(bucket);
      return;
    }
    for(let i = 0; i < 3; i++){
      const pick = lookup[i];
      recursion(count-1, bucket.concat(pick));
    }
  }
  recursion(selectNumber, []);

  return result; 
}
  // if (selectNumber === 1) return arr.map((el) => [el]); 
  // arr.forEach((fixed, index, origin) => {
  //   // const rest = origin
  //   // 일반 순열알고리즘에서 위의 rest 만 origin로 바뀐다
  //   const permutations = rockPaperScissors(selectNumber - 1); 
  //   const attached = permutations.map((el) => [fixed, ...el]); 
  //   results.push(...attached); 
  // });
// < 내 풀이>
function rockPaperScissors (rounds) {
  // TODO: 여기에 코드를 작성합니다.
  rounds = rounds || 3;
  let result = [];
  const lookup = ['rock', 'paper', 'scissors'];

  function recursion(count, bucket){
    if(count === 0){
      result.push(bucket);
      return;
    }
    for(let i = 0; i < 3; i++){
      const pick = lookup[i];
      recursion(count-1, bucket.concat(pick));
    }
  }

  recursion(rounds, []);
  return result;
};
// <레퍼런스>
// Advanced가 포함된 레퍼런스 코드입니다.
const rockPaperScissors = function (rounds) {

  // rounds 매개변수를 임의로 넣습니다.
  // rounds 변수가 있을 경우 그대로 사용하고, 없다면 3(기본)을 사용합니다.
  rounds = rounds || 3;
  const rps = ['rock', 'paper', 'scissors'];

  // 결과를 담을 배열 선언
  const outcomes = [];

  // 재귀를 사용할 함수 선언
  // rounds를 넣을 변수 roundsToGo, 일회용 배열인 playedSoFar 변수를 선언합니다.

  // 재귀를 사용하는 이유는, 배열의 모든 요소의 경우의 수를 훑기 위한 적절한 방법이기 때문입니다.
  // 간단히 말하자면, 이 함수는 rounds 숫자를 기준으로, 일회용 배열에 rps 요소를 rounds 숫자만큼 넣게 됩니다.
  // 이 로직을 잘 이해할 수 있을 때까지 하단의 함수 로직을 연구해야 합니다.
  let permutate = function (roundsToGo, playedSoFar) {

    // rounds가 0일 경우 outcones 배열에 삽입하고, 재귀에서 빠져나옵니다.
    if (roundsToGo === 0) {
      outcomes.push(playedSoFar);
      return;
    }

    // rps 배열을 한 번씩 순회합니다.
    for (let i = 0; i < rps.length; i++) {
      // rps의 i번째 요소를 변수에 담아서
      let currentPlay = rps[i];
      // permutate(본인)에 기존 rounds에서 하나 뺀 숫자와, 일회용 배열 playedSoFar에 currentPlay를 삽입하여 재귀를 실행합니다.
      // rounds에서 하나를 빼는 이유는, 일회용 배열의 크기를 rounds만큼 맞춰주기 위함입니다. [rock, rock, rock]

      // Q. playedSoFar.push(currentPlay)로 할 수 있을 텐데, 왜 concat을 사용할까요?
      permutate(roundsToGo - 1, playedSoFar.concat(currentPlay));
      /**
       * 이 재귀의 로직은 이렇습니다. 처음 실행된 반복문은 rps를 전부 순회해야 끝이 납니다.
       * 그러나 한 번 반복할 때마다 permutate 함수가 실행되고, rounds의 숫자는 짧아지며, playedSoFar에 요소가 계속 쌓일 것입니다.
       * 결국, roundsToGo가 0이 될 때까지 이 반복문은 rps[i]가 0일 것이며, playedSoFar에는 [rock, rock, rock]이 되어 outcones에 Push하고, 종료하게 됩니다.
       * return이 되었으니, 한 번의 재귀 호출이 끝났습니다. 마지막 호출 바로 전으로 돌아가,
       * for문은 i = 1을 가리키게 될 것이고, [rock, rock, paper]을 삽입한 뒤 호출을 하게 됩니다.
       * roundsToGo가 0이 되어, 해당 배열은 outcomes 배열에 삽입됩니다.
       * 이런 식으로 모든 호출의 반복문이 끝날 때까지 반복하며 outcomes에 경우의 수 요소들이 담기게 됩니다.
       */
    }
  };

  // 함수를 실행합니다.
  permutate(rounds, []);

  // outcomes를 반환합니다.
  return outcomes;
};

06_[순열] 새로운 치킨 소스 레시피

function newChickenRecipe(stuffArr, choiceNum) {
  // TODO: 여기에 코드를 작성하세요.

  // stuffArr : 0이 3개인 재료는 탈락
  // [1, 10, 11000, 1111]
  stuffArr = stuffArr.filter((el) => {
    let count = 0;
    let strArr = el+''; //[1, 0]
    for(let i = 0; i < strArr.length; i++){
      if(strArr[i] === '0') count++;
    }
    if(count < 3) return el; 
  })
  //
  if(stuffArr.length < choiceNum) return [];
  //
  let result = [];
  function recursion(count, bucket){
    if(count === 0){
      result.push(bucket);
      return;
    }
    for(let i = 0; i < stuffArr.length; i++){
      if(bucket.includes(stuffArr[i])) continue;
      const pick = stuffArr[i];
      recursion(count-1, bucket.concat(pick));
    }
  }
  recursion(choiceNum, []);
  return result;
}

// if (choiceNum === 1) return stuffArr.map((v) => [v]);

//   stuffArr.forEach((v, idx, stuffArr) => {
//     const fixer = v;
//     const restArr = stuffArr.filter((_, index) => index !== idx);
//     const permuationArr = newChickenRecipe(restArr, choiceNum - 1);
//     const combineFixer = permuationArr.map((v) => [fixer, ...v]);
//     result.push(...combineFixer);
//   });
//   return result;
// <레퍼런스>
function newChickenRecipe(stuffArr, choiceNum) {
  // stuffArr에서 0이 3개 이상이라면 전부 필터로 거르기.
  let freshArr = [];

  for (let i = 0; i < stuffArr.length; i++) {
    const element = String(stuffArr[i])
      .split('')
      .filter((e) => e === '0');
    if (element.length <= 2) {
      freshArr.push(stuffArr[i]);
    }
  }

  // 정렬
  freshArr.sort((a, b) => a - b);

  // 엣지 케이스 처리
  if (freshArr.length === 0 || freshArr.length < choiceNum) return [];

  // 레시피 초기화
  let result = [];

  // freshArr를 상대로 순열 구하기
  const permutation = (arr, bucket, n) => {
    if (n === 0) {
      result.push(bucket);
      return;
    }

    for (let i = 0; i < arr.length; i++) {
      // 하나를 초이스함
      const choice = arr[i];
      // 배열을 슬라이스함
      const sliceArr = arr.slice();
      // 초이스만 뺀다
      sliceArr.splice(i, 1);
      // 재귀
      permutation(sliceArr, bucket.concat(choice), n - 1);
    }
  };

  // 실행
  permutation(freshArr, [], choiceNum);
  
  // 순열의 길이 반환
  return result;
}
// <내 풀이>
function newChickenRecipe(stuffArr, choiceNum) {
  // TODO: 여기에 코드를 작성하세요.

  stuffArr = stuffArr.filter(el => {
    let count = 0;
    let str = `${el}`;
    for(let i = 0; i < str.length; i++){
      if(str[i] === '0') count++
      if(count >= 3) break;
    }
    return count < 3
  })
  stuffArr.sort((a, b) => a - b)

  if(stuffArr.length < choiceNum || stuffArr.length === 0) return [];

  let result = [];
  function aux(count, bucket, arr) {
    if(count <= 0){
      result.push(bucket)
      return;
    }
    for(let i = 0; i < arr.length; i++){
      let pickup = arr[i]
      let newBucket = bucket.concat(pickup)
      // bucket = bucket.concat(pickup)하면 오류난다.. 왜 ? 
      const sliceArr = arr.slice();
      sliceArr.splice(i, 1)
      aux(count-1, newBucket, sliceArr);
    }
  }

  aux(choiceNum, [], stuffArr)
  return result;
}

07_[조합] 블랙잭은 지겨워

// <내 풀이>
function boringBlackjack(cards) {
  // TODO: 여기에 코드를 작성합니다.
  
  let count = 0;
  let len = cards.length;
  for(let i = 0; i < len; i++){
    for(let j = i+1; j < len; j++){
      for(let k = j+1; k < len; k++){
        const num = cards[i] + cards[j] + cards[k];
        if(isPrime(num)) count++;
      }
    }
  }


  function isPrime(num) {
    if(num === 2) {
      return true;
    }
    for(let i = 2; i <= Math.floor(Math.sqrt(num)); i++){
      if(num % i === 0){
        return false; 
      }
    }
    return true; 
  }

  return count;
}
// <레퍼런스>
function boringBlackjack(cards) {
  let count = 0;

    // 1. cards 에서 카드 3장 뽑기
    let length = cards.length;
    // 카드 뽑는 방식은 첫 카드를 cards 의 0번 index 부터 고정해 놓고 1씩 뒤로 옮겨간다
    for (let i = 0; i < length; i++) {
    // 두 번째 카드의 인덱스는 첫 카드 + 1에서 시작해야 하고
      for (let j = i + 1; j < length; j++) {
    // 세 번째 카드의 인덱스는 두 번째 카드 + 1에서 시작해야 한다 
        for (let k = j + 1; k < length; k++) {
          const number = cards[i] + cards[j] + cards[k];
          // 세 카드의 합이 소수이면 경우의 수 + 1
          if (isPrime(number)) count++;
        }
      }
    }
  
    //2. 소수 판별
    function isPrime(number) {
    // 2부터 비교하기 시작해서 나누어 떨어지는 수가 있으면 소수가 아니다
    // for 문 조건을 number/2 로 해도 되는 이유는 i > number/2 가 되면 몫이 절대 0이 될수 없기 때문에
    // number/2 까지만 비교해 보아도 소수 판별이 가능하다
      for (let i = 2; i < number/2; i++) {
        if (number % i === 0) return false;
      }
      return true;
    }
  
    return count;
}
function boringBlackjack(cards) {
  let newArr = [];
  
  function recursion(count, bucket, k){
    if(count === 0){
      let sum = bucket.reduce((pre, cur) => pre + cur);
      newArr.push(sum);
      return;
    }
    for(let i = k; i < cards.length; i++){
      const pick = cards[i];
      recursion(count-1, bucket.concat(pick),++k);
    }
  }
  recursion(3, [], 0);
  
  const notprime = newArr.filter(el => {
    for(let i = 2; i < el/2; i++){
      if(el % i === 0 ) return el;
    }
  })
  console.log(newArr.length)
console.log(notprime.length)
  let num = newArr.length - notprime.length;
  return num;

}

  // TODO: 여기에 코드를 작성합니다.
  // 조합
  // 카드 여러장 받아서
  // 3장 뽑아서 더한다
  // 더한값을 배열로 저장

  // 그리고 저장된 배열이 소수인지 확인해서
  // 소수인 값의 개수를 리턴

  // for (let i = 0; i< cards.length; i++){
  //   for (let n = i +1; n < cards.length; n++){
  //     for (let m = n +1; m < cards.length; m++){
  //       const number = cards[i] + cards[n] + cards[m];
  //       newArr.push(number);
  //     }
  //   }
  // }
 

08_[GCD] 빼빼로 데이

// <내풀이>
function divideChocolateStick(M, N) {
  // TODO: 여기에 코드를 작성합니다.
  // 공약수 구하기.
  function GCD(M, N){
    if( M % N === 0 ) return N;
    return GCD( N,  M % N );
  }
  let gcdNum = GCD(M,N)

  let arr = [];
  for(let i = 1; i <= Math.floor(Math.sqrt(gcdNum)); i++){
    if( gcdNum % i === 0 && gcdNum / i !== Math.sqrt(gcdNum)) arr.push(i, gcdNum/i);
    if( gcdNum / i === Math.sqrt(gcdNum)) arr.push(Math.sqrt(gcdNum));
  }
  arr.sort((a, b) => a-b);

  arr = arr.map(el => {
    return [el, M / el, N / el]
  })

  return arr;
}
// <레퍼런스>
// 최대 공약수(유클리드 호제법: Euclidean algorithm)
function gcd(m, n) {
  if (m % n === 0) return n;
  return gcd(n, m % n);
}

function divideChocolateStick(M, N) {
  const result = [];
  // 최대공약수를 구한다.
  // M, N의 순서는 상관없다.
  const GCD = gcd(M, N);
  let temp = 0; //

  // 약수는 대칭적이므로 제곱근까지만 반복해도 된다.
  // 예) 36의 약수는 1, 2, 3, 4, 6, 9, 12, 18, 36이다.
  // 제곱근을 기준으로 양쪽의 값 하나씩 곱했을 때 36이 되기 때문에
  // 제곱근 보다 큰 약수는 제곱근보다 작은 약수에서 구할 수 있다.
  const sqrt = Math.floor(Math.sqrt(GCD));
  for (let left = 1; left <= sqrt; left++) {
    if (GCD % left === 0) {
      // 최대공약수의 약수인 경우 중 제곱근 보다 작은 약수의 경우
      result.push([left, M / left, N / left]);
      if (left * left < GCD) {
        // 제곱근이 아닌 경우(제곱근 보다 작은)
        right = GCD / left; // 최대 공약수를 제곱근이 아닌 수로 나누면 제곱근 보다 큰 약수를 구할 수 있다.
        result.push([right, M / right, N / right]);
      }
    }
  }

  // '빼빼로를 받게 되는 직원의 수'를 기준으로 오름차순으로 정렬
  result.sort((op1, op2) => op1[0] - op2[0]);

  return result;
}

09_(Advanced) [멱집합] 집밥이 그리워

// <내풀이>
function missHouseMeal(sideDishes) {
  // TODO: 여기에 코드를 작성합니다.
  // []에 sideDishes[0] 추가 
  // result = [[], [sideDishes[0]]]
  // result = result + (result에 sideDishes[1] 추가). 
  // ... sideDishes.length-1 까지 반복
  sideDishes.sort()
  let result = [[]];
  let addResult = [];
  for(let i = 0; i < sideDishes.length; i++){
    for(let j = 0; j < result.length; j++){
      addResult.push(result[j].concat(sideDishes[i]))
    }
    result.push(...addResult)
    addResult = []
  }
  return result.sort();
}
// <레퍼런스>
function missHouseMeal(sideDishes) {

  // 결과를 담을 배열을 선언합니다.
  let result = [];
  // sideDishes를 사전식 순서로 정렬합니다.
  sideDishes.sort();

  // 모든 조합을 검사하는 재귀 함수를 작성합니다.
  const sidePowerSet = (idx, sideDish) => {

    // 재귀 함수이기 때문에 탈출 조건을 만듭니다.
    if (idx === sideDishes.length) {
      // 만약, idx와 sideDishes의 길이가 같다면(마지막까지 검토한 경우) result에 sideDish를 삽입하고 push합니다.
      result.push(sideDish);
      return;
    }

    // idx번째 요소가 포함되지 않는 경우
    sidePowerSet(idx + 1, sideDish);

    // idx번째 요소가 포함되는 경우
    sidePowerSet(idx + 1, [...sideDish, sideDishes[idx]]);
  };

  // 0 번째 인덱스와 빈 배열을 인자로 받는 재귀 함수를 실행합니다.
  sidePowerSet(0, []);
 // sidePowerSet(1, []);
// sidePowerSet(1, [sideDishes[0]])

//->sidePowerSet(2, []);
//->sidePowerSet(2, [sideDishes[1]])
//->sidePowerSet(2, [sideDishes[0], [sideDishes[1]])

//->sidePowerSet(3, []);
//->sidePowerSet(3, [sideDishes[1]])
//->sidePowerSet(3, [sideDishes[0], [sideDishes[1]])
//->sidePowerSet(3, [sideDishes[2]);
//->sidePowerSet(3, [sideDishes[1], [sideDishes[2]])
//->sidePowerSet(3, [sideDishes[0], [sideDishes[1], [sideDishes[2]])

  // 결과를 사전식 순서로 정렬합니다.
  return result.sort();
}
profile
프론트엔드 개발자

0개의 댓글