Logo

Changchangwoo's blog

TIL
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Today I Learned를 기록하는 공간입니다. 🔥🔥🔥
잔디를 클릭하면 내용확인이 가능합니다.
Pinned

트라이 자료구조 문제풀이 연습

  • 트라이 핵심 키워드 : 접두사, 자동완성, prefix
  • 문자열을 저장하고 효율적으로 탐색하는 “트리”형태 자료구조
  • 트라이 자료구조는 문자열 자동완성, 사전검색 같은 기능에서 찾기 편하다
  • 시간복잡도 O(1), 해시와 비슷하게 키 값을 토대로 찾음. 다만 각 알파벳별로 쪼개서 저장하기에 메모리 측면에서는 비효율적.
  • 문자열이 주어지면 해당하는 문자열을 개별로 메모이제이션해서 저장하고, 이미 존재하는 값들은 그 값을 이용해서 탐색, 새로운 경우 해당하는 길을 추가함으로 탐색한다.

프로그래머스 전화번호부 문제

function solution(phone_book) {
  var answer = true;
  const root = {};
  phone_book.sort((a, b) => a - b);
  for (const num of phone_book) {
    let node = root;
    for (let char of num) {
      if (node.isEnd) return false;
      if (!node[char]) {
        node[char] = {};
      }
      node = node[char];
    }
    node.isEnd = true;
  }
  return answer;
}
  • 가장 기본이되는 구현, 전화번호 문제는 이전에 해시를 통해 풀었는데 값들의 중복을 찾는다는 점에서 Trie를 사용해서도 풀 수 있다.
  • 초기 객체를 root로 상정하고, 그 내부에 값이 있는 경우 [char]을 통해 접근, 없는 경우 새 객체를 만들어 넣고 마지막의 경우 isEnd를 통해 명시한다.
  • 해당 문제는 중복값을 찾기에 isEnd를 지나치는경우 접두사가 있다고 판단하는 문제

개미굴 문제

let input = require("fs")
  .readFileSync("input.txt")
  .toString()
  .trim()
  .split("\n");
const T = +input.shift();
const root = {};
const answer = [];
for (let i = 0; i < T; i++) {
  const slice = input[i].split(" ");
  let node = root;
  for (let j = 1; j < slice.length; j++) {
    let target = slice[j].trim();
    if (!node[target]) {
      node[target] = {};
    }
    node = node[target];
  }
  node.isEnd = true;
}
dfs(root, 0);

function dfs(node, depth) {
  const keys = Object.keys(node)
    .filter((k) => k !== "isEnd")
    .sort();
  for (let i = 0; i < keys.length; i++) {
    let prefix = "--";
    answer.push(prefix.repeat(depth) + keys[i]);
    dfs(node[keys[i]], depth + 1);
  }
}

console.log(answer.join("\n"));
  • 이것도 이전에는 해시로풀었는데 트라이 형태로 만드는것까지는 기본.
  • 진짜 문제 접근은 트라이 내부를 어떻게 탐색하느냐인데 dfs를 활용해서 객체의 키들을 배열로서 정렬하여 반환하고, 그 객체안에 현재 객체 값을 기반으로 다시 넣고 이를 반복함으로 트라이 전체 순회
  • 트라이 + DFS는 문제가 나온다면 잘 어울리는 것 같아서 해당 방식에 대해 감 익히는게 중요할듯

휴대폰 자판 ( 백준 5670 )

/*
1. 트라이 객체를 만든다
2. 객체안에 분기가 생기기전까지 자동으로 파고들어간다.
3. 분기가 있는경우 count+1
4. 트라이 객체를 만들면서 구할 수 있을까?
5. 안된다. 전부 구한다음 순회해야함. 위치에 영향을 받기에
6. DFS로 구할까? for 순회할까?
*/
let input = require("fs")
  .readFileSync("input.txt")
  .toString()
  .trim()
  .split("\n");
let idx = 0;
const answer = [];
while (idx < input.length) {
  const N = +input[idx++];
  const arr = [];
  for (let j = 0; j < N; j++) {
    arr.push(input[idx++].trim());
  }

  const root = {};
  for (const item of arr) {
    let node = root;
    for (const char of item) {
      if (!node[char]) {
        node[char] = {};
      }
      node = node[char];
    }
    node.isEnd = true;
  }
  let sum = 0;
  for (const item of arr) {
    let count = 0;
    let node = root;
    for (const char of item) {
      const keys = Object.keys(node).filter((k) => k !== "isEnd");
      if (node === root || keys.length > 1 || node.isEnd) count++;
      node = node[char];
    }
    sum += count;
  }
  answer.push((sum / arr.length).toFixed(2));
}
console.log(answer.join("\n"));
  • 트라이 문제를 유심히 읽고 근거를 세우니 접근방식은 빠르게 파악할 수 있었음
  • 구현도 어렵지 않았는데 디버깅 문제로 AI 도움받고 시간을 많이 씀
  • 첫번째, 입력이 까다롭게 나와서 slice를 통해서 구현했는데 모든 테스트케이스 다 통과했으나 반례가 있었는지 통과를 못함. 절대 못찾았을 오류 의심을 안했음
  • 두번쨰, 조건중 초기값은 반드시 count를 1로 들고 시작해야함. 그렇기에 초기에 node===root인 경우 count를 증가하고, 이후 node를 바탕으로 탐색하도록 해야함
    • 노드의 자식을 기준으로 탐색하도록 하니 역시 테스트케이스는 맞지만 반례가 숨어있었음
  • 반례를 찾는게 역시 젤 어렵다