문제

https://www.acmicpc.net/problem/9251

 

LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다.

 

예를 들어, ACAYKPCAPCAK의 LCS는 ACAK가 된다.

풀이

const fs = require("fs");

// 문제 링크 : https://www.acmicpc.net/problem/9251
// 제출 시 path : /dev/stdin

const input = fs.readFileSync("/dev/stdin").toString().trim().split("\n");

const LCS = (str1, str2) => {
  const m = str1.length;
  const n = str2.length;

  const dp = new Array(m + 1).fill(null).map(() => new Array(n + 1).fill(0));

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (str1[i - 1] === str2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }

  let result = "";
  let i = m,
    j = n;

  while (i > 0 && j > 0) {
    if (str1[i - 1] === str2[j - 1]) {
      result = str1[i - 1] + result;
      i--;
      j--;
    } else if (dp[i - 1][j] > dp[i][j - 1]) {
      i--;
    } else {
      j--;
    }
  }

  return result;
}

const solution = (input) => {
  const [firstArr, secondArr] = input;
  const answer = LCS(firstArr, secondArr);
  return answer.length;
};

console.log(solution(input));

해설

for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (str1[i - 1] === str2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }

str1 = ACAYKP

str2 = CAPCAK

dp[i][j] j=0 j=1 j=2 j=3 j=4 j=5 j=6
i=0 0 0 0 0 0 0 0
i=1 0 0 1 1 1 1 1
i=2 0 1 1 1 2 2 2
i=3 0 1 2 2 2 3 3
i=4 0 1 2 2 2 3 3
i=5 0 1 2 2 2 3 4
i=6 0 1 2 3 3 3 4

i=0 -> j=0,1,2,3,4,5,6 다 비교. 

 

dp[1][1]:

ACAYKP 과 CAPCAK 비교

A !== C 이므로 

dp[1][1]은dp[0][1]과 dp[1][0] 중 최댓값 

dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) 

 

dp[1][2]:

ACAYKP 과 CAPCAK 비교

A === A 이므로 

dp[1][2]은 dp[0][1]+1 

dp[i][j] = dp[i - 1][j - 1] + 1; 

 

'Board > 알고리즘' 카테고리의 다른 글

백준 1463 자바스크립트  (0) 2023.02.15
백준 9663 N-Queen  (0) 2023.02.08
백준 15652 N과 M(4)  (0) 2023.02.05
백준 15651 N과 M(3)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05

문제

https://www.acmicpc.net/problem/1463

접근

1을 빼거나 2나 3을 나누어 1로 만드는데 걸리는 연산 횟수의 최솟값을 구하는 문제입니다.

첫째줄는 1이상 10^6이하의 정수 N이 주어집니다. 수의 범위가 크기 때문에 계산가능한 모든 수를 구하는 완전탐색 등은 이 문제에 어울리지 않은 접근법입니다. 한 번 연산한 값을 기억해 두고 문제를 풀 수 있는 DP가 적절해 보입니다.

 

DP의 인덱스는 숫자를 뜻합니다.

D[N-1]: N에서 1을 빼서 1을 만드는 연산 횟수

D[N/2]: N에서 2를 나눠 1을 만드는 연산 횟수

D[N/3]: N에서 3을 나눠 1을 만드는 연산 횟수

 

D[N] 숫자 N이 1이 되는데 걸리는 최소한의 연산 횟수를 저장합니다.

N이 2로 나눠진다면 D[N] = D[N-1] + 1 과 D[N/2] + 1 중 더 작은 값, N이 3으로 나눠진다면 D[N] = D[N-1] + 1 과 D[N/3] + 1 중 더 작은 값이 됩니다.

각 경우의 수 뒤에 +1 이 되는 이유는 D[N-1],D[N/2],D[N/3] 이 각 N을 구하기 이전의 연산횟수를 나타내기 때문입니다.

 

예를 한 번 들어볼까요?

D[N-1] + 1 :

4를 1을 빼서 1로 만드는 연산 횟수는 3입니다. 4-1-1-1=1

3을 1을 빼서 1로 만드는 연산 횟수는 2입니다. 3-1-1=1

2를 1을 빼서 1로 만드는 연산 횟수는 1입니다. 2-1=1

이를 뒤집어보면 숫자 N을 1을 만들 때까지 1을 빼는 연산의 횟수는 N-1을 1을 만들 때까지 1을 빼는 연산의 횟수 +1 임을 알 수 있습니다. 

D[N/2] + 1

2를 2로 나눠서 1로 만드는 연산 횟수는 1입니다. 2/2 = 1

4를 2로 나눠서 1로 만드는 연산 횟수는 2입니다. 4/2 = 2, 2/2 =1 

8을 2로 나눠서 1로 만드는 연산 횟수는 3입니다. 8/2 = 4, 4/2 = 2, 2/2 = 1

이를 뒤집어보면 숫자 N을 1을 만들 때까지 2를 나누는 연산의 횟수는 N-1을 1을 만들 때까지 N을 나누는 연산의 횟수 +1 임을 알 수 있습니다.

D[N/3] + 1도 위와 D[N/2] + 1의 경우와 마찬가지입니다.

 

문제를 한 번 풀어볼까요? N=4 로 예를 들어봅시다.

우선 D[0]은 문제 범위 밖이므로 0으로 처리해 줍니다. 

D[1] = 0 입니다. 1을 1로 만들 수 있는 연산의 횟수는 0회이기 때문입니다.

D[4] = Math.min( D[3] + 1 , D[4/2] +1) = Math.min( 1 + 1, 2 + 1 ) = Math.min(2, 3) = 2 가 됩니다.

 

이를 코드로 풀어보면 아래와 같습니다.

풀이

const input = fs
  .readFileSync("/dev/stdin")
  .toString()
  .trim()
  .split("\n")
  .map((v) => v.split(" ").map((v) => parseInt(v)));
const solution = (input) => {
  const N = +input;
  const DP = Array.from({ length: N + 1 }, () => 0);

  for (let i = 2; i < N + 1; i++) {
    DP[i] = DP[i - 1] + 1; // 1을 빼서 1로 만드는 연산의 횟수로 우선 설정

    if (i % 2 === 0) {
      // 2로 나눠진다면,
      DP[i] = Math.min(DP[i], DP[i / 2] + 1); // 1을 빼서 1로 만드는 연산의 횟수와  2로 나눠 1로 만드는 횟수 중 더 작은 경우로 선택.
    }

    if (i % 3 === 0) {
      DP[i] = Math.min(DP[i], DP[i / 3] + 1);
    }
  }

  return DP[N];
};

console.log(solution(input)); // 3

 

'Board > 알고리즘' 카테고리의 다른 글

백준 9251 LCS [JavaScript / Node.js]  (0) 2023.02.23
백준 9663 N-Queen  (0) 2023.02.08
백준 15652 N과 M(4)  (0) 2023.02.05
백준 15651 N과 M(3)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05

문제

https://www.acmicpc.net/problem/9663

 

9663번: N-Queen

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다. N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.

www.acmicpc.net

접근

2가지 방법이 있다: 

- 각 스퀘어를 전부 탐색하며 조건을 찾는다. => n^n 

- 한 행에 하나의 퀸이 온다는 것을 이용해 행별로 탐색 => n (풀이 참조)

 

2번째의 경우가 훨씬 시간복잡도가 줄기 때문에 두 번째 방법으로 간다

즉, 행이나 열 하나를 잡고 그 행에 첫 퀸을 두었을 때 다음 행이나 열을 돌면서 경우의 수를 탐색한다. 

 

풀이

const input = require("fs").readFileSync("/dev/stdin").toString();

const solution = (input) => {
  const N = +input;
  const row = Array(N).fill(0);
  let cnt = 0;

  const isPossible = (x) => {
    for (let i = 0; i < x; i++) {
      if (row[x] === row[i]) return false;
      if (x - i === Math.abs(row[x] - row[i])) return false;
    }
    return true;
  };

  const dfs = (x) => {
    if (x === N) {
      cnt++;
      return;
    }

    for (let y = 0; y < N; y++) {
      row[x] = y; 
      if (isPossible(x)) {
        dfs(x + 1); 
      }
    }
  };

  dfs(0);

  return cnt;
};

console.log(solution(input));

'Board > 알고리즘' 카테고리의 다른 글

백준 9251 LCS [JavaScript / Node.js]  (0) 2023.02.23
백준 1463 자바스크립트  (0) 2023.02.15
백준 15652 N과 M(4)  (0) 2023.02.05
백준 15651 N과 M(3)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05

문제

https://www.acmicpc.net/problem/15652

 

15652번: N과 M (4)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

접근

자연수 N과 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오.

  • 1부터 N까지 자연수 중에서 M개를 고른 수열
  • 같은 수를 여러 번 골라도 된다.
  • 고른 수열은 비내림차순이어야 한다.

➡️ 어렵게 생각할 것 없이 중복을 허용하는 순서가 상관없는 경우의 수를 구하라는 것이다: 중복순열. 순열 문제였던 백준 15650 N과 N(2) 문제에서 중복체크하는 checked 배열만 없애주면 된다.

풀이

const input = require("fs")
  .readFileSync("/dev/stdin")
  .toString()
  .trim()
  .split(" ")
  .map(Number);

const [N, M] = input;
const output = [];
let result = "";

function dfs(level, start) {
  if (level === M) {
    result += `${output.join(" ")}\n`;
    return;
  }

  for (let i = start; i < N; i++) {
    output.push(i + 1);
    dfs(level + 1, i);
    output.pop();
  }
}

dfs(0, 0);

console.log(result);
 

'Board > 알고리즘' 카테고리의 다른 글

백준 1463 자바스크립트  (0) 2023.02.15
백준 9663 N-Queen  (0) 2023.02.08
백준 15651 N과 M(3)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05
백준 15649 N과 M(1)  (0) 2023.02.05

문제

https://www.acmicpc.net/problem/15651

 

15651번: N과 M (3)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

접근

백준 15651 N과 M(1) 과 같이 순열 문제이다.

단, 중복을 허용하기에 check 배열의 기능을 삭제해 주었다.

풀이

const input = require("fs")
  .readFileSync("/dev/stdin")
  .toString()
  .trim()
  .split(" ")
  .map(Number);

const [N, M] = input;
const output = [];
let result = "";
const checked = Array(N).fill(false);

function dfs(level, start) {

  if (level === M) {
    result += `${output.join(" ")}\n`;
    return;
  }
  
  for (let i = start; i < N; i++) {
    if (checked[i]) continue; // backtracking
    checked[i] = true;
    output.push(i + 1);
    dfs(level + 1, i);
    output.pop();
    checked[i] = false;
  }
}

dfs(0, 0);

console.log(result);

'Board > 알고리즘' 카테고리의 다른 글

백준 9663 N-Queen  (0) 2023.02.08
백준 15652 N과 M(4)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05
백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26

문제

https://www.acmicpc.net/problem/15650

 

15650번: N과 M (2)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

접근

문제 15649 N과 M(1) 과 비교했을 때,

  • 고른 수열은 오름차순이어야 한다.

 

라는 조건이 추가됐다. 즉, [4,3] 의 경우 [3,4] 로 정렬되어 경우의 수 [4,3]와 [3,4]는 같은 것으로 간주된다.

 

N과 M (1)는 재귀를 위한 for loop에서 index를 항상 맨 처음부터 다시 돌았다면(모든 경우의 수를 위해), 이 문제에서는 지난 숫자는 제외하고 for loop을 진행한다(중복을 피하기 위해)

풀이

const input = require("fs")
  .readFileSync("/dev/stdin")
  .toString()
  .trim()
  .split(" ")
  .map(Number);

const [N, M] = input;
const output = [];
let result = "";
const checked = Array(N).fill(false);

function dfs(level, start) {

  if (level === M) {
    result += `${output.join(" ")}\n`;
    return;
  }

  for (let i = start; i < N; i++) {
    if (checked[i]) continue;
    
    checked[i] = true;
    output.push(i + 1);
    dfs(level + 1, i);
    output.pop();
    checked[i] = false;
  }
}

dfs(0, 0);

console.log(result);

'Board > 알고리즘' 카테고리의 다른 글

백준 15652 N과 M(4)  (0) 2023.02.05
백준 15651 N과 M(3)  (0) 2023.02.05
백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 14888 자바스크립트  (0) 2023.01.22

[공부정리] 순열 vs 조합 vs 중복순열 vs 중복조합 (feat. 백준 N과 M 문제 시리즈)


문제

https://www.acmicpc.net/problem/15649

 

15649번: N과 M (1)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

접근

첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 8)

➡️ 수의 범위가 작아 완전탐색 으로 접근

 

중복 없는 수열

➡️ 백트래킹

➡️ 수열 문제니까 조합(combination) 또는 순열(permutation) 인데, 중복이 없다고 했으므로 순열 문제.

풀이

const input = require("fs").readFileSync('/dev/stdin').toString().trim().split(" ").map(Number);

const [n, m] = input;
let result = "";
const output = [];
const checked = Array(n).fill(false);

function dfs(cnt) {
	
    if (cnt === m) {
        result += `${output.join(" ")}\n`;
        return;
    }

    for (let i = 0; i < n; i++) {
        if (checked[i] === true) continue;
        checked[i] = true;
        output.push(i + 1);
        dfs(cnt + 1);
        output.pop();
        checked[i] = false;
    }
}

dfs(0);

console.log(result);

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15651 N과 M(3)  (0) 2023.02.05
백준 15650 N과 M(2)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 14888 자바스크립트  (0) 2023.01.22
백준 2661 자바스크립트  (0) 2023.01.21

문제

https://www.acmicpc.net/problem/2606

 

2606번: 바이러스

첫째 줄에는 컴퓨터의 수가 주어진다. 컴퓨터의 수는 100 이하이고 각 컴퓨터에는 1번 부터 차례대로 번호가 매겨진다. 둘째 줄에는 네트워크 상에서 직접 연결되어 있는 컴퓨터 쌍의 수가 주어

www.acmicpc.net

접근 

기본적인 그래프 탐색 문제. 

연결된 노드가 몇 개인지를 구하는.

 

단, 맨 처음 노드 시작을 인덱스 0이 아닌 1로 시작해줘야 한다.

풀이

const fs = require("fs");
const input = fs.readFileSync("./input.txt").toString().trim().split("\n");

function solution(input) {
  const N = +input.shift(); // 노드 수
  const M = +input.shift(); // 간선 수
  const edges = input.map((v) => v.split(" ").map(Number));

  // 그래프 그리기
  const vertices = Array(N + 1)
    .fill(0)
    .map(() => []);
  for (let edge of edges) {
    const [v0, v1] = edge;
    vertices[v0].push(v1);
    vertices[v1].push(v0);
  }

  // 실행
  const seen = new Set();
  let count = 0;
  const dfs = (start) => {
    for (let end of vertices[start]) {
      if (!seen.has(end)) {
        seen.add(end);
        count++;
        dfs(end);
      }
    }
  };
  seen.add(1);
  dfs(1);
  console.log(seen);
  return count;
}

console.log(solution(input));

참조: https://www.acmicpc.net/source/54379165

 

로그인

 

www.acmicpc.net

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15650 N과 M(2)  (0) 2023.02.05
백준 15649 N과 M(1)  (0) 2023.02.05
백준 14888 자바스크립트  (0) 2023.01.22
백준 2661 자바스크립트  (0) 2023.01.21
백준 14889 자바스크립트  (0) 2023.01.19

문제

https://www.acmicpc.net/problem/14888

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 

www.acmicpc.net

N개의 수로 이루어진 수열 A1, A2, ..., AN이 주어진다. 또, 수와 수 사이에 끼워넣을 수 있는 N-1개의 연산자가 주어진다. 연산자는 덧셈(+), 뺄셈(-), 곱셈(×), 나눗셈(÷)으로만 이루어져 있다.

 

식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다. 이에 따라서, 위의 식 4개의 결과를 계산해보면 아래와 같다.

  • 1+2+3-4×5÷6 = 1
  • 1÷2+3+4-5×6 = 12
  • 1+2÷3×4-5+6 = 5
  • 1÷2×3-4+5+6 = 7

N개의 수N-1개의 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

 

접근

3    //N
3 4 5 // 수열
1 0 1 0 // 각각 +,-,*,/ 연산자의 갯수

이라면

만들 수 있는 식

3+4*5 = 35

3*4+5 = 17

 

- 첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 이므로 -> 완전탐색 

- 수열은 이미 주어졌으니 따로 구할 필요 x

- 연산자의 순서에 따라 값이 달라지니 순열(permutation) 문제겠구나.

-- 1. 들어온 수열과 연산자를 각각 다른 배열에 넣는다. 

-- 2. 수열.join("연산자")로 반복문을 돌린다.

--- 2-1.결과를 array 에 푸시한다. 

--- 2-2.연산자 배열의 위치를 바꾼다.

-- 3. 결과 배열에서 가장 작은 수와 가장 큰 수를 찾아 리턴한다.

 

1. 들어온 수열과 연산자를 각각 다른 배열에 넣는다. 

const input = require("fs")
  .readFileSync(address)
  .toString()
  .trim()
  .split("\n")
  .map((v) => v.split(" ").map(Number));

function getOperations(arr) {
  const operations = ["+", "-", "*", "/"];

  operations.forEach((v, idx) => {
    let amount = arr[idx];
    for (let j = 0; j < amount; j++) {
      operations.push(operations[idx]);
    }
  });

  return operations;
}

function solution(input) {
  const [N, A, amount] = input;
  const operations = getOperations(amount);

  return operations;
}

 

 2-1. 재귀를 이용해 결과를 array 에 푸시한다.

// 백준 제출시
// const address="/dev/stdin"

// VSC 테스트시
const address = "./input.txt";

const input = require("fs")
  .readFileSync(address)
  .toString()
  .trim()
  .split("\n")
  .map((v) => v.split(" ").map(Number));

const getOperations = (arr) => {
  const operations = ["+", "-", "*", "/"];

  operations.forEach((v, idx) => {
    let amount = arr[idx];
    for (let j = 0; j < amount; j++) {
      operations.push(operations[idx]);
    }
  });

  return operations;
};

function solution(input) {
  const [N, A, amount] = input;
  const operations = getOperations(amount);

  const results = [];
  let result = Number.MAX_SAFE_INTEGER;
  let check = Array(N).fill(0);

  const recursion = (idx) => {
    if (idx === N - 1) return;
    const copy = [...A];
    let tmpOp = operations.slice(0, 2);
    copy.splice(idx + 1, 0, tmpOp[0]);
    copy.splice(N, 0, tmpOp[1]);
    let tmp = operations.shift();
    operations.push(tmp);
    const test = eval(copy.join(""));
    results.push(test);
  };

  recursion(0);

  let min = Math.min(...results);
  let max = Math.max(...results);
  let answer = `${min}\n${max}`;

  return answer;
}

console.log(solution(input));

 

2-2.연산자 배열의 위치를 바꾼다.

const changeArrayOrder = function(list, targetIdx, moveValue) {
  // 배열값이 없는 경우 나가기
  if (list.length < 0) return;

  // 이동할 index 값을 변수에 선언
  const newPosition = targetIdx + moveValue;

  // 이동할 값이 0보다 작거나 최대값을 벗어나는 경우 종료
  if (newPosition < 0 || newPosition >= list.length) return;

  // 임의의 변수를 하나 만들고 배열 값 저장
  const tempList = JSON.parse(JSON.stringify(list));

  // 옮길 대상을 target 변수에 저장하기
  const target = tempList.splice(targetIdx, 1)[0];

  // 새로운 위치에 옮길 대상을 추가하기
  tempList.splice(newPosition, 0, target);
  return tempList;
};

 

풀이

시도 끝에 결국 실패. 여전히 재귀로 구현하는 것은 아직 어렵다.

// 백준 제출시
// const address="/dev/stdin"

// VSC 테스트시
const address = "./input.txt";

const input = require("fs")
  .readFileSync(address)
  .toString()
  .trim()
  .split("\n")
  .map((v) => v.split(" ").map(Number));

const getOperations = (arr) => {
  const operations = ["+", "-", "*", "/"];

  operations.forEach((v, idx) => {
    let amount = arr[idx];
    for (let j = 0; j < amount; j++) {
      operations.push(operations[idx]);
    }
  });

  return operations;
};

function solution(input) {
  const [N, A, amount] = input;
  const operations = getOperations(amount);

  const results = [];
  let result = Number.MAX_SAFE_INTEGER;
  let check = Array(N).fill(0);

  const recursion = (idx) => {
    if (idx === N - 1) return;
    const copy = [...A];
    let tmpOp = operations.slice(0, 2);
    copy.splice(idx + 1, 0, tmpOp[0]);
    copy.splice(N, 0, tmpOp[1]);
    let tmp = operations.shift();
    operations.push(tmp);
    const test = eval(copy.join(""));
    results.push(test);
  };

  recursion(0);

  let min = Math.min(...results);
  let max = Math.max(...results);
  let answer = `${min}\n${max}`;

  return answer;
}

console.log(solution(input));

 


참조풀이

https://gobae.tistory.com/49

https://junghyeonsu.tistory.com/196

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 2661 자바스크립트  (0) 2023.01.21
백준 14889 자바스크립트  (0) 2023.01.19
[알고리즘 공부] 20230115 2주차 회고  (0) 2023.01.16

문제

https://www.acmicpc.net/problem/2661

 

2661번: 좋은수열

첫 번째 줄에 1, 2, 3으로만 이루어져 있는 길이가 N인 좋은 수열들 중에서 가장 작은 수를 나타내는 수열만 출력한다. 수열을 이루는 1, 2, 3들 사이에는 빈칸을 두지 않는다.

www.acmicpc.net

1,2,3 으로만 이루어진 N자리 숫자를 만든다. 이 중 가장 작은 수를 리턴한다.

단, 같은 순서가 연속해서 반복될 수 없다. ex) 33, 123123321

 

접근

- 12131213 처럼 딱히 규칙이 있지도 않고고, 1 <= N <= 80 으로 범위가 큰 수가 아니므로 완전탐색이 가능하다.

- 우선 수열이 좋은 수열인지(같은 순서가 연속해서 반복되지 않는지) 판단해야 한다. 해당 블로그를 참조하였다.

1211 => 길이 4, 1자리 비교, 나쁜 수열

1212 => 길이 4, 1자리 비교, 좋은 수열
1212 => 길이 4, 2자리 비교, 나쁜 수열

123123 => 길이 6, 1자리 비교, 좋은 수열
123123 => 길이 6, 2자리 비교, 좋은 수열
123123 => 길이 6, 3자리 비교, 나쁜 수열

- 해당 접근 방식을 코드로 짠다. 재귀를 이용해 전체 탐색을 했다.

 

 

 

시도

1차 시도

결과: 틀렸습니다

// 백준 제출시
// const address="/dev/stdin"

// VSC 테스트시
const address = "./input.txt";
const input = require("fs").readFileSync(address).toString().trim().split("\n");

const solution = (input) => {
  const N = +input;
  let stop = false;
  let min = Number.MAX_SAFE_INTEGER;

  const isGoodSequence = (str) => {
    let len = str.length;
    let loop = len / 2;
    let start = len - 1;
    let end = len;

    for (let i = 1; i <= loop; i++) {
      if (str.substring(start - i, end - i) === str.substring(start, end))
        return false;
      start -= 1;
    }

    return true;
  };

  const recursion = (len, str) => {
    if (stop) return;
    if (len === N) {
      stop = true;
      console.log(str);
      min = min > Number(str) ? Number(str) : min;
    }
    // 수열은 1,2,3 중 하나로 이루어져 있다.
    for (let i = 1; i <= 3; i++) {
      if (isGoodSequence(str + i)) recursion(len + 1, str + i);
    }
  };

  // 가장 작은 수를 구하는 것이므로 첫번째 자리는 항상 "1"이 되어야하고, 따라서 따로 탐색할 필요 x
  recursion(1, "1");

  return min;
};

console.log(solution(input));

 

2차 시도

결과: 맞았습니다!!

const address="/dev/stdin"
const input = require("fs").readFileSync(address).toString().trim().split("\n");

const solution = (input) => {
  const N = +input;
  let stop = false;

  const isGoodSequence = (str) => {
    let len = str.length;
    let loop = len / 2;
    let start = len - 1;
    let end = len;

    for (let i = 1; i <= loop; i++) {
      if (str.substring(start - i, end - i) === str.substring(start, end))
        return false;
      start -= 1;
    }

    return true;
  };

  const recursion = (len, str) => {
    if (stop) return;
    if (len === N) {
      stop = true;
      console.log(str);
    }
    // 수열은 1,2,3 중 하나로 이루어져 있다.
    for (let i = 1; i <= 3; i++) {
      if (isGoodSequence(str + i)) recursion(len + 1, str + i);
    }
  };

  // 가장 작은 수를 구하는 것이므로 첫번째 자리는 항상 "1"이 되어야하고, 따라서 따로 탐색할 필요 x
  recursion(1, "1");
};

solution(input);

 

 

분석

왜 min 부분을 삭제하니 통과한 걸까?

 우선 굳이 min 이 필요 없었다. 차례대로 작은수부터 대입하니까 반복문이 끝나기전 제일 먼저이자 마지막으로 찍힌 수가 어차피 가장 작은 수이기 때문이다.

 1차 풀이도 틀리지는 않았는데 틀렸던 이유는 정답(min)의 출력타입이 number 였기 때문이다. 정답으로 제출한 코드 마지막 부분만 console.log(Number(string)) 으로 쳤더니 틀렸다고 나온다. 출력 타입도 str 이어야 하는 구나.

 

나보다 속도가 적게 나온 다른 사람 코드 분석

1. 좋은 수열인지 판별하는 함수에서 substring 메소드가 아닌 slice 메서드를 사용

2. 재귀 함수에서 정답을 도출할 때 string 대신 array 를 사용

3. 재귀 함수에서 백트래킹시 조건문을 좋은 수열일 경우에만 진행이 아닌 나쁜 수열일 경우 반복문을 패스하도록 설정

const fs = require('fs');
// /dev/stdin
const input = fs.readFileSync("/dev/stdin").toString().trim().split("\n").map((v)=> v.split(" ").map((v)=> parseInt(v)))


// 숫자 1, 2, 3으로만 이루어지는 수열이 있다. 
// 임의의 길이의 인접한 두 개의 부분 수열이 동일한 것이 있으면, 
// 그 수열을 나쁜 수열이라고 부른다. 그렇지 않은 수열은 좋은 수열이다.
// 첫 번째 줄에 1, 2, 3으로만 이루어져 있는 길이가 N인 좋은 수열들 중에서 가장 작은 수를 나타내는 수열만 출력한다.

function solution (N){
    const arr = [1,2,3]
    const result = []
    const dfs = (depth, number = '') => {
        if(result.length > 0) return
        if (depth === N) {
            result.push(number)
            return
        }
        for(let i = 0; i < arr.length; i++) {
            const nextArray = number + arr[i].toString()
            if(isBad(nextArray)) continue
            dfs(depth+1, nextArray)
        }
        
    }

    dfs(0)



    return result[0]
}

function isBad (str) {
    for (let i = 1; i <= str.length/2; i++) {
        const last = str.slice(-i)
        const before = str.slice(-i*2, -i)
        if (last === before) return true
    }
    return false
}

console.log(solution(...input[0]))

 

 

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 14888 자바스크립트  (0) 2023.01.22
백준 14889 자바스크립트  (0) 2023.01.19
[알고리즘 공부] 20230115 2주차 회고  (0) 2023.01.16

https://www.acmicpc.net/problem/14889

 

14889번: 스타트와 링크

예제 2의 경우에 (1, 3, 6), (2, 4, 5)로 팀을 나누면 되고, 예제 3의 경우에는 (1, 2, 4, 5), (3, 6, 7, 8)로 팀을 나누면 된다.

www.acmicpc.net

 

문제 해석

주어진 n 에서 n/2 만큼 스타트팀의 경우의 수를 구한다. 스타트팀을 구하면 남은 인원을 링크팀에 배치한다.

각 팀의 능력치를 구해 합의 차가 가장 작은 경우의 수를 구한다. 

 

문제 접근

스타트팀 n 에서 n/2 를 순서에 상관없이 뽑는 조합의 문제이다. 

ex) n = 6일 때 [1,2,3] 로 스타트팀을 구성한다면, [1,2,3] 이나 [2,1,3]이나 [3,1,2] 나 순서에 상관 없이 팀의 능력치는 각 팀원들로 만들 수 있는 모든 부분 집합 (x,y) 쌍의 합, 즉, (1,2)+(2,1)+(2,3)+{3,2)+(1,3)+(3,1) 이 될 것이기 때문이다. 

 

문제 풀이 

시도 끝에 아래 두 가지 풀이 참조 및 비교 

풀이1 : 재귀 이용

 

출처 - https://gobae.tistory.com/50

 

 

풀이2 : 구현 이용

 

출처 - https://github.com/jlee0505/algorithm-study/pull/1/commits/df6a36b419ef225e0d9196fc7126a3e762a0d194

 

배운 것 

1. min 값을 각각 0, MIN_SAFE_INTEGER, MAX_SAFE_INTEGER 의 차이:

MIN_SAFE_INTEGER, MAX_SAFE_INTEGER 는 각각 -9007199254740991, +9007199254740991 을 의미한다.

아래 코드의 min 의 초기 할당을 각각 0, MIN_SAFE_INTEGER, MAX_SAFE_INTEGER 으로 했을 때의 차이를 생각해보자.

 
min = Math.min(min, Math.abs(sSum - lSum));

 

2.  재귀로 풀 때와 구현으로 풀 때의 장단점:

첫 번째 제출은 풀이1(재귀)의 결과, 두 번째 제출은 풀이 2(구현)의 결과이다. 구현으로 풀면 가독성이 올라가고 직관적 이해가 쉽다는 장점이 있지만, 재귀로 푸는 것이 시간,공간 복잡도가 압도적으로 높다.


재귀는 여전히 어렵다.

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 14888 자바스크립트  (0) 2023.01.22
백준 2661 자바스크립트  (0) 2023.01.21
[알고리즘 공부] 20230115 2주차 회고  (0) 2023.01.16

나의 Notion - 알고리즘

2023년부터는 인턴과 기업 코딩테스트로 공부 방향을 잡았다. 어딜 가도 1차 관문이 되는 코딩테스트가 중요한 기회 때 발목 잡는 걸 원치 않았다. 

 

알고리즘 공부 시작, 지난 2주 돌아보기

1월 알고리즘 학습 계획표

 

 

2023 목표를 인턴과 기업 코딩테스트로 잡고, 어딜 가서든 1차 관문이 되는 코딩테스트의 ‘기본’을 1,2월안에 마무리 하기로 했다. 3,4월에는 본격적으로 기출을 풀며 여름방학/하반기 인턴 코테 지원(4~5월 지원)을 준비하고 싶었기 때문이다. 그리하여 2월까지 세운 러프한 계획은 이러했다:

  • 1월: 강의와 함께 빈출 알고리즘 핵심은 제대로 익혀두기(이코테 2021)
  • 2월: 백준 추천 50제 풀며 1월 배운 내용 복습 + 보강
  • 3월: 우테캠, 카카오, 토스 등 국내 기업 코딩테스트의 흐름을 주도하는 기업의 기출문제 풀어보기
  • 4월: 2차 관문인 과제 코테 준비 시작

 

 

 우선 이코테 2021 강의를 완강 + 80% 이해 및 체화를 목표로 1월 계획을 세웠다. 나는 정말 노베이스 상태에서 시작했기 때문에 전반적으로 어떻게 흘러가고 어떤 것들이 있는지 직접 한 번 훑고 싶었다. 지난 2주는 그런 시간이었다.

 

 그리디, 완전 탐색, 그리고 그래프 탐색(dfs, bfs)였다. dfs,bfs 는 중요하다는 말을 하도 많이 들어 감이 안잡혔지만 최대한 이해하고 넘어가려 했다. (관련 노션 보러가기)

 

 1월은 1회차기 때문에 문제를 많이 풀어보는 것보다 개념을 확실히 이해하는 게 더 중요하다고 생각했다. (이코테 기본 예제(2), 백준(1) - dfs, node 대신 edge 가 주어진 경우, 프로그래머스(5) - 스킵 )

 

 

Keep, Try, Problem

 회고를 쓰며 찾아보니 판교로 가는 길에서는 1주일 한 개념씩, 2~3문제를 풀어볼 것을 권한다. 예정보다 3일을 더 투자하여 (5,6,7일) 예제를 확실히 이해하고 추가로 백준 문제를 풀었다. 더 많은 문제를 풀었으면 좋았겠다는 아쉬움이 어쩔 수 없이 남는다. 하지만 카페 글을 참조해보니 오히려 이정도가 딱 맞았다는 생각이 든다. 외려 1주에 2개념을 잡았던 초기 계획이 불필요하게 빡세다는 느낌도 든다. 이에 따라

 

Keep 

- 무조건 문제 풀이보다 중요한 건 개념 이해

Problem

- 딱히 문제는 아니었지만... 한 개념 한 주씩 파는 것과 어떻게 다를지 다른 사람의 조언도 반영해볼 것.

- 예상했던 것보다 느슨해진다는 것 

Try 

- 한 개념 한 주씩: 두 개념 한 주씩 하던 지난 주와 후에 비교해볼 것

- 한 달 스프린트로 스터디를 개설하여 함께 뛰는 이가 있을 때 어떻게 달라질 수 있는지 비교해볼 것

- 불안요소를 글로써 가시화하고 제거할 수 있는 대책을 세우자.

 

다음 스텝은?

1. 한 개념 한 주씩 양치기 + 체화

다음 주부터는 본격적으로 양치기에 들어가려 한다. 지난 2주가 이코테 강의 위주의 전체 흐름 잡기 + 개념 익히기였다면, 다음 5주는 백준50제 위주의 양치기 체화 과정이 목표이다.

 

5주차 계획

 

다음 스텝: 2. 스터디와 함께 풀기

스터디를 개설했다.

 

이유:

  1. 해당 카페 포함 많은 이들이 같이 가는 스터디원이 있는 것을 강력 추천
  2. 지인과 네트워킹을 통해 혼자서는 몰랐을 시각/정보/인적자원 습득의 가능성
  3. 반복 문제 풀이인만큼 늘어지지 않게 함께 가는 메이트 만들기

걱정되는 부분:

지인과 함께 시작한 스터디라 아래 부분이 걱정되었다.

  1. 너무 루즈해지진 않을까?
  2. 친목 위주로 가서 집중력과 학습 효과가 떨어지면 어떡하지? 

어떻게 해결할 수 있을까?:

  1. 나의 불안요소: 쉽게 distracted 된다.
    1. 목표를 확실히 한다: 이 4주간 나는 무엇을 얻고자 하는가?
      1. 실버1,2 or 골드 4 를 달성 (모든 문제를 이해하고 풀이했다는 전제하)
  2. 상대에 대한 불안요소: 평소 함께 잘 놀던 지인이다.
    1. 주의사항을 상기한다:
      1. ‘성의 없는 공부’에 절대 관대하지 말 것.
    2. 전반적인 주차별 목표&계획과 구체적인 실행 컨텐츠 시간 장소 등을 확실히 하자.

다음 텀도 화이팅

 

'Board > 알고리즘' 카테고리의 다른 글

백준 15649 N과 M(1)  (0) 2023.02.05
백준 2606 자바스크립트  (0) 2023.01.26
백준 14888 자바스크립트  (0) 2023.01.22
백준 2661 자바스크립트  (0) 2023.01.21
백준 14889 자바스크립트  (0) 2023.01.19

+ Recent posts