next.js에서 tailwind를 입히려는데 적용이 안돼서 한참 고민하다 npm run dev로 돌려보니 적용이 되었다 🥲 

이 참에 next.js를 설치할 때 package.json 파일에 자동으로 추가되는 scripts 명령어를 살펴보자 ~ 

 

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
}

 

1. dev

개발 모드에서 next.js를 시작한다.

source map과 hot code reloading이 제공되어 디버깅 시 유용하다.

 

2. build

프로덕션 용도로 애플리케이션을 빌드한다.

- 빌드 과정에서 모든 페이지와 컴포넌트가 번들링되고, 최적화되어 해당 결과물이 .next 디렉토리에 저장된다.

 

3. start

프로덕션 모드에서 프로젝트를 실행한다

- 빌드된 애플리케이션 사용: next build 명령어로 미리 빌드된 애플리케이션을 사용한다.

- node.js 서버를 시작한다.

 

4. lint

기본 설정된 eslint 설정을 사용해 eslint 실행한다.

 

---

적용이 안되었던 이유는 start 명령어를 실행해서, 이전에 빌드된 내용이 실행되었기 때문이다.

dev와 start의 차이, 즉 production 모드와 development mode를 잘 이해하면 webpack 설정 시 최적화 적용에도 도움이 될 것 같다.

이분 탐색은 결정 문제(Decision Problem)의 답이 이분적일 때 사용할 수 있는 탐색 기법이다.

 

 

❗️ 결정 문제란?
답이 Yes or No인 문제를 의미하며, 보통 1개의 parameter를 가진다.

 

 

1 ~ 50까지 오름차순 정렬된 카드 더미에서 28번 카드를 찾는 문제가 있을 때, 첫 번째 카드부터 i번째 카드는 v[i], 28은 val로 설정한다.
이 경우 결정 문제를 "v[i] >= val인가?"로 잡으면, 결정 문제의 답은 i가 증가함에 따라 F, F, ..., F, T, T, ..., T와 같이 분포한다.

이 때 찾고자 하는 값은 처음으로 v[i] >= val인 지점, 즉 처음 결정 문제가 True가 되는 i값이 된다.

 

⭐️ 구현 방법

이분 탐색의 구현은

1) Check(lo) != Check(hi)가 되도록 lo, hi의 초기값을 설정한 뒤,
2) lo + 1 < hi인 동안 mid = (lo + hi) / 2를 구해서,
3) Check(lo) == Check(mid)라면 lo = mid를, Check(hi) == Check(mid)라면 hi = mid로 설정한다.

 

이분 탐색이 끝나면 lo, hi는 결정 문제의 답이 바뀌는 경계에 위치하게 되며,

만약 결정 문제의 답의 분포가 F ~ T인데 정답이 가장 큰 F라면 lo를, 가장 작은 T라면 hi를 출력한다.

 

1. 경계를 포함하도록, 즉 Check(lo) != Check(hi)가 되도록 [lo, hi]를 설정한다.

2. Check(lo) == Check(mid)라면 lo = mid, 아니라면 hi = mid를 반복한다.

 

3. lo + 1 == hi가 되면 loop 종료, lo와 hi는 경계에 위치한다.

 

 

 

⭐️  문제 예시: bj2805 나무 자르기

결정 문제를 "mid 높이에 절단기를 위치했을 때, m 이상의 나무를 얻을 수 있는가?"로 잡으면,

Check(mid)의 분포가 T, ..., T, F, ..., F이고, 구하는 답은 Check(mid) = True인 mid의 최댓값이다.

const input = require("fs")
  .readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
  .toString()
  .trim()
  .split("\n");

const [N, M] = input[0].split(" ").map(Number);
const trees = input[1].split(" ").map(Number);

function solution() {
  let left = 0,
    right = 1000000000,
    mid = 0;

  while (left + 1 < right) {
    mid = Math.floor((left + right) / 2);
    // M보다 많다 -> 많이 잘라간다 -> 높이를 높게 설정한다 -> left값을 조정한다.
    if (!Check(mid)) left = mid;
    else right = mid;
  }
  console.log(left);
}

function Check(mid) {
  let sum = 0;
  for (let i = 0; i < trees.length; i++) {
    if (trees[i] > mid) sum += trees[i] - mid;
    if (sum >= M) return false;
  }
  return true;
}
solution();

 

❗️결정 문제의 답은 항상 F~T의 분포가 아니라, T~F의 분포일 수도 있다.
또한 정답은 경계의 왼쪽일 수도 있고, 오른쪽일 수도 있으므로 문제별로 잘 생각해야 한다.


⭐️  lower_bound, upper_bound

lower_bound는 v[i-1] < k <= v[i]i를 찾으며, v[i] >= ki의 최솟값을 반환한다.
upper_bound는 v[i-1] <= k < v[i]i를 찾으며, v[i] > ki의 최솟값을 반환한다.

const lower_bound = (v, x) => {
    const n = v.length;
    let lo = -1, hi = n;
    while (lo + 1 < hi) {
        let mid = Math.floor((lo + hi)/2);
        if (!(v[mid] >= x)) lo = mid;
        else hi = mid;
    }
    return hi;
}

const upper_bound = (v, x) => {
    const n = v.length;
    let lo = -1, hi = n;
    while (lo + 1 < hi) {
        let mid = Math.floor((lo + hi)/2);
        if (!(v[mid] > x)) lo = mid;
        else hi = mid;
    }
    return hi;
}

console.log(lower_bound([1, 2, 3, 3, 4], 3)); // 2
console.log(upper_bound([1, 2, 3, 3, 4], 3)); // 4

⭐️  자주 하는 실수

1. lo, hi는 항상 정답의 범위를 나타낼 수 있어야 한다. 예를 들어 lo를 출력해야 하는 문제의 답이 최대 n일 때, hi = n으로 선언하거나, hi를 출력해야 하는 문제의 답이 최소 0일 때 lo = 0으로 선언하면 안된다. (hi = n + 1, lo = -1로 선언해야 한다.)

2. 오버플로우에 주의한다.

3. 결정 문제의 정의에 맞게 Check 함수를 잘 구현해야 한다. Check 함수의 부등호를 조금만 틀려도 전혀 다른 값이 나올 수 있다.


 

Reference

문제

내 코드 (82ms, 59.40mb)

/**
 * @param {string} s
 * @return {string[]}
 */
var findRepeatedDnaSequences = function(s) {
    const map = new Map();
    if (s.length < 10) return [];
    let answer = [];
    
    let left = 0;
    let right = 10;
    while (right !== s.length+1) {
        const cur = s.substring(left, right);
        if (!map.has(cur)) map.set(cur, 1);
        else map.set(cur, map.get(cur) + 1);
        left++; right++;
    }

    map.forEach((key, value) => {
        if (key >= 2) answer.push(value);
    })

    return answer;
};

 

천재의 코드 (출처)

var findRepeatedDnaSequences = function(s) {
    let curr = s.slice(0, 10);
    const seen = new Set([curr]);
    const res = new Set();
    
    for(let i = 10; i < s.length; i++) {
        curr = curr.slice(1) + s[i];
        if(seen.has(curr)) res.add(curr);
        seen.add(curr);
    }
    return [...res];
};

 

'개발 이론 > 풀이' 카테고리의 다른 글

[Grind75] 20. Valid Parentheses  (0) 2024.04.16

내 코드 (23:15, 66ms, 53.11MB)

class Stack {
    constructor() {
        this.arr = [];
        this.index = 0;
    }
    push(item) {
        this.arr[this.index++] = item;
    }
    pop() {
        if (this.index <= 0) return null;
        const result = this.arr[--this.index];
        return result;
    }
    peek() {
        if (this.index <= 0) return null;
        return this.arr[this.index-1];
    }
    isEmpty() {
        if (this.index <= 0) return true;
        return false;
    }
}

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    let stk = new Stack();

    for (let i = 0; i < s.length; i++) {
        const brac = s.charAt(i);
        if (brac === '(' || brac === '{' || brac === '[') stk.push(brac);
        else if (brac === ')' && !stk.isEmpty() && stk.peek() == '(') stk.pop();
        else if (brac === '}' && !stk.isEmpty() && stk.peek() == '{') stk.pop();
        else if (brac === ']' && !stk.isEmpty() && stk.peek() == '[') stk.pop();
        else stk.push(brac);
    }
    if (stk.isEmpty()) return true;
    return false;
};

 

천재의 코드 (출처)

var isValid = function(s) {
    let stack = [];
    for (let idx = 0; idx < s.length; idx++) {
        if (s[idx] == '{') {
            stack.push('}');
        } else if (s[idx] == '[') {
            stack.push(']');
        } else if (s[idx] == '(') {
            stack.push(')');
        } else if (stack.pop() !== s[idx]) {
            return false;
        }
    }
    return !stack.length;
};

 

'개발 이론 > 풀이' 카테고리의 다른 글

[Grind75] 187. Repeated DNA Sequences  (0) 2024.04.16

특정한 하나의 정점에서 다른 모든 정점으로 가는 최단 경로를 구한다.
❗️음의 간선을 포함할 수 없다.

과정

  1. 출발 노드를 설정한다.
  2. 출발 노드를 기준으로 각 노드의 최소 비용을 저장한다.
  3. 방문하지 않은 노드 중에서, 가장 비용이 적은 노드를 선택한다.
  4. 해당 노드를 거쳐서 특정한 노드로 가는 경우를 고려하여 최소 비용을 갱신한다.
  5. 위 과정에서 3번 ~ 4번을 반복한다.

예제

  1. 초기 가중치 (1번 노드에서 다른 노드로 직접 가는 비용)
  1 2 3 4
1 0 2 5 1
  1. 1번 노드 선택

현재 방문하지 않은 노드 중에서 가장 비용이 적은 노드: 4번
4번 노드를 거쳐서 가는 경우를 모두 고려하여 최소 비용 배열을 갱신한다.

1번 -> 3번 갱신 (1번 -> 4번 -> 3번 = 4)

  1 2 3 4
1 0 2 4 1

 

다음으로 방문하지 않은 노드 중에서 가장 비용이 적은 노드: 2번
2번 노드를 거쳐서 최소 비용이 갱신되는 경우는 없음.

 

다음으로 방문하지 않은 노드: 3번
3번 노드를 거쳐서 최소 비용이 갱신되는 경우는 없음.

 

  1. 1번 노드에서 다른 노드로 가는 최단 거리
  1 2 3 4
1 0 2 4 1

 

코드

public class Dijkstra {

    static int INF = 999999;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");
        int V = Integer.parseInt(st.nextToken()); // 노드의 개수
        int E = Integer.parseInt(st.nextToken()); // 간선의 개수
        int K = Integer.parseInt(br.readLine()); // 시작 노드

        Node[] graph = new Node[V+1];
        int[] dist = new int[V+1];
        boolean[] visited = new boolean[V+1];

        Arrays.fill(dist, INF);
        for (int i = 0; i < E; i++) {
            st = new StringTokenizer(br.readLine(), " ");
            int from = Integer.parseInt(st.nextToken());
            int to = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());
            graph[from] = new Node(to, cost, graph[from]);
        }

        dist[K] = 0; // 시작점
        for (int i = 1; i <= V; i++) {
            int min = Integer.MAX_VALUE;
            int cur = 0;
            for (int j = 1; j <= V; j++) {
                // cur = 방문하지 않은 것 중 가장 비용이 적은 노드
                if (!visited[j] && min > dist[j]) {
                    min = dist[j];
                    cur = j;
                }
            }

            visited[cur] = true;
            for (Node j = graph[cur]; j != null; j = j.link) {
                // j를 경유하여 갈 수 있는 경우의 최단 거리 갱신
                if(!visited[j.vertex] && dist[j.vertex]>dist[cur]+j.cost) {
                                         dist[j.vertex]=dist[cur]+j.cost;
                }
            }
        }
        for (int i = 1; i <= V; i++) {
            System.out.println(Arrays.toString(dist[i]));
        } 
    }

    static class Node {
        int vertex, cost;
        Node link;
        public Node (int vertex, int cost, Node link) {
            this.vertex = vertex;
            this.cost = cost;
            this.link = link;
        }
    }
}

 


Reference
https://blog.naver.com/ndb796/221234424646