프로그래머스 42898 - 등굣길

문제 링크

생각 및 접근

  • 초중고 시절 많이 했던 최단거리 구하기 문제를 코딩으로 구현하게 될 줄 몰랐다.
  • 최단거리 문제에 대한 정보는 깔끔수학님의 블로그를 참고하자.
  • 인덱스 생각 때문에 애먹었는데, 좌표와 행렬 표현의 이질적인 부분 때문이다.
    • 행렬로 생각하면 집이 [1][1], 학교는 [3][4]여야 하는데,
    • 좌표로 따지면 집이 (1, 1)이라고 하면, 학교의 좌표는 (4, 3)이다.
    • 좌표로 표현한 것들을 행렬로 바꾸려면 x와 y값을 뒤집어주어야 한다!
    • 어떻게 할까 고민하다가, 행렬을 기준으로 생각해서, 좌표인 것들을 뒤집어주기로 했다.
  • 좌표를 행렬로 바꾸기
    • 행렬 dp에서 PUDDLE를 넣을 때,
        for(auto k : puddles)   dp[k[1]][k[0]] = PUDDLE;
      와 같이 하였다.
    • 행렬 dp의 첫번째 인덱스의 끝은 m, 행렬 dp의 두번째 인덱스의 끝은 n으로 하였다.
    • solution 함수의 반환값 또한 dp[n][m]
  • dp의 초깃값 잡기
      for(auto k : puddles)   dp[k[1]][k[0]] = PUDDLE;
      for(int i = 1; i <= m; i++){
          if(dp[1][i] == PUDDLE)  break;
          dp[1][i] = 1;
      }
      for(int i = 1; i <= n; i ++){
          if(dp[i][1] == PUDDLE)  break;
          dp[i][1] = 1;
      }
  • dp 계산
      for(int i = 2; i <= n; i++){
          for(int j = 2; j <= m; j++){
              if(dp[i][j] == PUDDLE)  continue;
              if(dp[i - 1][j] != PUDDLE)  dp[i][j] += dp[i - 1][j];
              if(dp[i][j - 1] != PUDDLE)  dp[i][j] += dp[i][j - 1];
              dp[i][j] %= 1000000007;
          }
      }

코드

#include <bits/stdc++.h>
#define PUDDLE -1
using namespace std;

int solution(int m, int n, vector<vector<int>> puddles) {
    vector< vector<int> > dp(n + 1, vector<int>(m + 1, 0));
    for(auto k : puddles)   dp[k[1]][k[0]] = PUDDLE;
    for(int i = 1; i <= m; i++){
        if(dp[1][i] == PUDDLE)  break;
        dp[1][i] = 1;
    }
    for(int i = 1; i <= n; i ++){
        if(dp[i][1] == PUDDLE)  break;
        dp[i][1] = 1;
    }

    for(int i = 2; i <= n; i++){
        for(int j = 2; j <= m; j++){
            if(dp[i][j] == PUDDLE)  continue;
            if(dp[i - 1][j] != PUDDLE)  dp[i][j] += dp[i - 1][j];
            if(dp[i][j - 1] != PUDDLE)  dp[i][j] += dp[i][j - 1];
            dp[i][j] %= 1000000007;
        }
    }

    return dp[n][m];
}

채점 결과

프로그래머스 43105 - 정수 삼각형

문제 링크

생각 및 접근

  • 정수 삼각형을 배열 형태로 숫자를 작성해보면 다음과 같다.
  • 삼각형의 맨 윗 층부터 내려오면서, vector<vector<int>> tt[i][j]까지 검사했을 때, 최댓값이 들어오게끔 DP 방식 코드를 짜고자 했다.
  • 검사하는 방식은 이렇다.
    • 화살표를 하나만 받는 원소와 화살표를 두 개 받는 원소를 나눴다.
      • 화살표 하나만 받는 원소 : 각 층의 양 끝
      • 화살표 두 개 받는 원소 : 양 끝을 제외한 나머지
        • max 함수를 사용해서 두 가지중 큰 것을 t[i][j]에 더해주면 된다.

코드

#include <bits/stdc++.h>
using namespace std;

int solution(vector<vector<int>> t) {
    for(int i = 1; i < t.size(); i++){
        for(int j = 0; j < t[i].size(); j++){
            if(j == 0)                      t[i][j] += t[i - 1][j];
            else if(j == t[i].size() - 1)   t[i][j] += t[i - 1][j - 1];
            else                            t[i][j] += max(t[i - 1][j - 1], t[i - 1][j]);
        }
    }

    int answer = -1;
    for(auto d : t[t.size() - 1]){
        if(d > answer)  answer = d;
    }
    return answer;
}

채점 결과

프로그래머스 43104 - 타일 장식물

문제 링크

생각 및 접근

  • 우선, 정사각형의 갯수에 따라 정사각형의 길이가 얼마나 길어지는지 살펴보아야 한다.

    • 1, 1, 2, 3, 5, 8, ...
    • 누가 봐도 이건 피보나치 수열이다.
  • 문제에서 N이 주어지면, 일단 피보나치 수열을 통해 정사각형 길이를 구하고, 그 값을 토대로 직사각형의 길이를 구하면 된다.

  • 직사각형 길이는 점화식으로 나타내면 length(n) = 4 * square(n) + 2 * square(n - 1)이 된다.

코드

#include <bits/stdc++.h>
using namespace std;

long long tiles[81];

long long dp(int N){
    if(N == 1 || N == 2)    return tiles[N] = 1;
    else if(tiles[N] == 0)  return tiles[N] = dp(N - 1) + dp(N - 2);
    else                    return tiles[N];
}

long long solution(int N) {
    return 4 * dp(N) + 2 * dp(N - 1);
}

채점 결과

프로그래머스 42895 - N으로 표현

문제 링크
멍토 님의 IT블로그 게시글을 참고하였습니다.

생각 및 접근

  • 문제 분야는 DP에 있었지만, 문제 접근은 DFS밖에 떠오르지 않았다.
  • DFS로 접근하려고 했지만, dfs로 탐색해야 될 것이 너무 많았다. 예로 들어, 숫자를 합쳐서 계산하는 경우 모두를 dfs로 고려하려고 하니 쉽지 않았다.
  • 구글링을 통해 멍토 님의 IT블로그를 참조하여 문제를 풀고, 이해할 수 있었다.
  • DFS의 포인트는
    • DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가 ,
    • DFS의 초깃값을 어떻게 잡을 것인가 ,
    • 언제 DFS를 멈추게 할 것인가 ,
    • DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가

DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가

  • 숫자를 몇 개 사용했는지 세는 int count와 count까지 오면서 계산한 int currentNumber를 넘긴다.

      void dfs(int count, int currentNumber);

DFS의 초깃값을 어떻게 잡을 것인가

```
dfs(0, 0);
```

언제 DFS를 멈추게 할 것인가

  • count가 8보다 크면 답으로 인정하지 않고, 탐색을 멈춰야 한다.

      if(count >= 9)  return;
  • currentNumber == num이라면, 정답을 찾은 것이므로 탐색을 멈추고, answer를 업데이트한다.

      if(currentNumber == num){
          answer = min(answer, count);
          return;
      }

DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가

  • 수많은 DFS를 반복문에 의해 처리된다.

    int tempNumber = 0;
    for(int i = 0; i + count < 9; i++){
      tempNumber = tempNumber * 10 + n;
      dfs(count + 1 + i, currentNumber + tempNumber);
      dfs(count + 1 + i, currentNumber - tempNumber);
      dfs(count + 1 + i, currentNumber * tempNumber);
      dfs(count + 1 + i, currentNumber / tempNumber);
    }
  • i의 역할

    • n을 i번 붙여낸다. 즉, "nnn... n" (n이 i번) 라는 형태이다.
  • i + count < 9

    • count가 9가 넘으면 더이상 DFS를 하지 않으니, 굳이 DFS를 하지 않고 for문 선에서 잘라낸다.
  • dfs(count + 1 + i, currentNumber ? tempNumber);

    • n을 i번 붙인만큼, n을 i번 사용한 것이므로 count + 1 + i
    • ?는 사칙연산

코드

#include <bits/stdc++.h>
using namespace std;

int n, num, answer = 9;

void dfs(int count, int currentNumber){
    if(count >= 9)  return;
    if(currentNumber == num){
        answer = min(answer, count);
        return;
    }

    int tempNumber = 0;
    for(int i = 0; i + count < 9; i++){
        tempNumber = tempNumber * 10 + n;
        dfs(count + 1 + i, currentNumber + tempNumber);
        dfs(count + 1 + i, currentNumber - tempNumber);
        dfs(count + 1 + i, currentNumber * tempNumber);
        dfs(count + 1 + i, currentNumber / tempNumber);
    }
}

int solution(int N, int number) {
    n = N;
    num = number;
    dfs(0, 0);
    if(answer == 9) return -1;
    return answer;
}

채점 결과

프로그래머스 43164 - 여행경로

문제 링크

접근 및 생각

  • 문제는 DFS 방식으로 풀이하였다. 왜냐하면 알파벳 순서에 대한 처리가 필요했기 때문이다.
    • DFS에 대한 설명은 (링크)를 참조.
    • DFS란 깊이 우선 탐색으로써 일단 깊게 들어가서 leaf node에 닿아, 탐색한 경로가 답에 걸맞기만 하면 그걸 쓰면 된다.
    • 이 점을 사용해서, 미리 tickets를 알파벳 순서로 배열을 한 뒤에 DFS하기로 했다_ .
    • 미리 tickets를 알파벳 순서로 배열을 한 뒤에 DFS를 하면, 제일 빨리 찾은 답이 알파벳 순서가 맞는 답일 것이다.
    • tickets를 나열하는 cmp 함수
        // 티켓의 시작 주소 오름차순이 1순위, 티켓의 도착 주소 오름차순이 2순위
        bool cmp(vector<string> a, vector<string> b){
            if(a[0] == b[0]){
                return a[1] < b[1];
            }
            return a[0] < b[0];
        }
  • DFS의 포인트는
    • DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가 ,
    • DFS의 초깃값을 어떻게 잡을 것인가 ,
    • 언제 DFS를 멈추게 할 것인가 ,
    • DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가

DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가

  • 여러 경우의 수가 있겠지만, 필자는 이제까지 이동한 경로를 넣었다.
      void dfs(vector<string> path);

DFS의 초깃값을 어떻게 잡을 것인가

  • 시작 도시는 ICN이다.
      dfs({"ICN"});

언제 DFS를 멈추게 할 것인가

  • 반환되는 벡터의 크기는 늘 tickets 벡터 크기 + 1이므로, path 벡터 크기가 tickets 벡터 크기 + 1 이라면 DFS를 멈춘다.
      if(path.size() == t.size() + 1){
          if(answer.empty()){
              answer = path;
          }
      }
  • 또한, 가장 먼저 찾은 경로를 answer로 쓸 것이기 때문에 다음과 같은 코드를 추가하였다.
      if(!answer.empty()) return;

DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가

  • 아직 사용하지 않은 티켓이고, path의 마지막 원소가 티켓의 시작 주소라면, 그 티켓을 탐색할 수 있다.
      for(int i = 0; i < t.size(); i++){
          if(used[i] == 0 && temp == t[i][0]){
              used[i] = 1;
              path.push_back(t[i][1]);
              dfs(path);
              used[i] = 0;
              path.pop_back();
          }
      }

코드

#include <bits/stdc++.h>
using namespace std;

vector<vector<string>> t;
vector<int> used;
vector<string> answer;

bool cmp(vector<string> a, vector<string> b){
    if(a[0] == b[0]){
        return a[1] < b[1];
    }
    return a[0] < b[0];
}

void dfs(vector<string> path){
    if(!answer.empty()) return;

    string temp = path[path.size() - 1];
    if(path.size() == t.size() + 1){
        if(answer.empty()){
            answer = path;
        }
    }
    else{
        for(int i = 0; i < t.size(); i++){
            if(used[i] == 0 && temp == t[i][0]){
                used[i] = 1;
                path.push_back(t[i][1]);
                dfs(path);
                used[i] = 0;
                path.pop_back();
            }
        }
    }
}

vector<string> solution(vector<vector<string>> tickets) {
    t = tickets;
    used.assign(t.size(), 0);
    sort(t.begin(), t.end(), cmp);
    dfs({"ICN"});
    return answer;
}

채점 결과

프로그래머스 43163 - 단어 변환

문제 링크

생각 및 접근

  • BFS는 최단거리를 보장한다. 이 문제는 begin에서 target으로 변환 가능한 최소 경로를 구하는 것이므로, BFS로 접근하려고 했다.
  • BFS는 코드로 구현할 때 다음과 같은 요소를 고려하여야 한다.
    • queue의 데이터 타입
    • queue의 초깃값
    • q.front() 원소가 답이 될 수 있는지, 혹은 정보 update가 가능한지 check 이후 행동
    • 읽어온 원소로부터 queue에 push할 수 있는 원소를 push 하기

queue의 데이터 타입

  • BFS를 하면서 탐색한 횟수 int, 현재 변환된 상태 string을 pair로 선언하였다.
      queue< pair<string, int> > q;

    queue의 초깃값

    q.push(make_pair(begin, 0));

q.front() 원소가 답이 될 수 있는지, 혹은 정보 update가 가능한지 check 이후 행동

  • string temp = q.front().firsttarget과 같다면, 최소 경로를 찾은 것이므로 return q.front().second;

읽어온 원소로부터 queue에 push할 수 있는 원소를 push 하기

  • words를 모두 순회하여 아래 조건에 해당하는 원소를 push하고, visit를 체크.
    • 아직 방문하지 않았고, visit\[i\]
    • temp와 words[i]를 비교해서 변환이 가능하다면 chk1diff(temp, words[i])
        bool chk1diff(string a, string b){
            int cnt = 0;
            for(int i = 0; i < a.length(); i++){
                if(a[i] != b[i]) cnt++;
            }
            if(cnt == 1)    return true;
            return false;
        }

코드

#include <bits/stdc++.h>
using namespace std;

bool chk1diff(string a, string b){
    int cnt = 0;
    for(int i = 0; i < a.length(); i++){
        if(a[i] != b[i]) cnt++;
    }
    if(cnt == 1)    return true;
    return false;
}

int answer = 0, visit[50];
queue< pair<string, int> > q;

int solution(string begin, string target, vector<string> words) {
    q.push(make_pair(begin, 0));
    while(!q.empty()){
        string temp = q.front().first;
        int cnt = q.front().second;
        q.pop();

        if(temp == target){
            return cnt;
        }

        for(int i = 0; i < words.size(); i++){
            if(visit[i] != 0) continue;
            else{
                if(chk1diff(temp, words[i])){
                    visit[i] = 1;
                    q.push(make_pair(words[i], cnt + 1));
                }
            }
        }
    }
    return 0;
}

채점 결과

프로그래머스 43162 - 네트워크

문제 링크

접근 및 생각

사족

  • 프로그래머스의 대부분 풀이들은 DFS 방식이였지만, 내가 이 문제를 처음 접근할 때 BFS 방식이 먼저 떠올라서 BFS 방식으로 풀게 되었다.

본론

  • BFS에 관한 내용은 (링크)를 참조
  • BFS는 큐를 활용하는 탐색 방식으로, 코드로 구현할 때 다음과 같은 요소를 고려하여야 한다.
    • queue의 데이터 타입
    • queue의 초깃값
    • q.front() 원소가 답이 될 수 있는지, 혹은 정보 update가 가능한지 check 이후 행동
    • 읽어온 원소로부터 queue에 push할 수 있는 원소를 push 하기

queue의 데이터 타입

  • queue가 기억해야 할 것은 computers의 index 값이므로 queue<int> q;

queue의 초깃값

  • 일단 computers의 0번 index부터 탐색할 것이므로 q.push(0); visit[0] = answer;
  • 그리고 computers의 index를 탐색했는 지 기억하기 위해 int visit[200]; 선언

읽어온 원소로부터 queue에 push할 수 있는 원소를 push 하기

  • computers[q.front()][i = 0..n] 을 탐색해서,
    • q.front()와 연결되어 있고, (computers[i][temp] == 1)
    • 아직 방문하지 않았다면, (visit[i] == 0)
      q.push();

q.front() 원소가 답이 될 수 있는지, 혹은 정보 update가 가능한지 check 이후 행동

  • 정보 update가 가능한 시점
    • 더이상 탐색할 수 있는 원소가 없는 경우 한 네트워크를 다 찾은 것이므로, (if(q.empty()))
    • 다른 네트워크를 찾아야 한다.
        for(int i = 0; i < n; i++){
            if(visit[i] == 0){
                q.push(i);
                visit[i] = ++answer;
                break;
            }
        }
    • 위 과정을 거쳐도 추가된 원소가 없다면 (즉, 모든 컴퓨터를 탐색했다면) , return answer

코드

#include <bits/stdc++.h>
using namespace std;

queue<int> q;
int visit[200], answer = 1;

int solution(int n, vector<vector<int>> computers) {    
    q.push(0);
    visit[0] = answer;
    while(-1){
        if(q.empty()){
            for(int i = 0; i < n; i++){
                if(visit[i] == 0){
                    q.push(i);
                    visit[i] = ++answer;
                    break;
                }
            }
            if(q.empty())   return answer;
        }

        int temp = q.front();
        q.pop();

        for(int i = 0; i < n; i++){
            if(i == temp) continue;
            else if(computers[i][temp] == 1 && visit[i] == 0){
                q.push(i);
                visit[i] = answer;
            }
        }
    }
}

채점 결과

프로그래머스 43165 - 타겟 넘버

문제 링크

생각 및 접근

  • 문제를 보자마자 아주 전형적인 DFS 문제구나. 라고 딱 떠올랐다.
    • DFS에 대한 설명은 (링크)를 참조.
  • DFS의 포인트는
    • DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가 ,
    • DFS의 초깃값을 어떻게 잡을 것인가 ,
    • 언제 DFS를 멈추게 할 것인가 ,
    • DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가
      로 나눠볼 수 있겠다.

DFS 함수의 파라미터로 무엇을 넘기고 넣을 것인가

  • DFS 탐색할 때 depth == numbers.size()까지 탐색해야 하므로 파라미터 level을 넘길 것이다.
  • DFS 탐색할 때 (DFS 결과 총 합) == target이 되면 answer++해주어야 하므로 DFS 결과 총 합을 나타내는 파라미터 result를 넘길 것이다.

DFS의 초깃값을 어떻게 잡을 것인가

dfs(0, 0);

언제 DFS를 멈추게 할 것인가

if(level == nums.size()){
    // ...
}

DFS 함수 내에서 새 DFS 함수를 호출할 때 문제의 조건에 맞게 적절히 호출하는가

// + 하는 경우
dfs(level + 1, result + nums[level]);
// - 하는 경우
dfs(level + 1, result - nums[level]);

코드

#include <bits/stdc++.h>
using namespace std;

vector<int> nums;
int answer = 0, tar;

void dfs(int level, int result){
    if(level == nums.size()){
        if(result == tar){
            answer++;
        }
    }
    else{
        dfs(level + 1, result + nums[level]);
        dfs(level + 1, result - nums[level]);
    }
}

int solution(vector<int> numbers, int target) {
    tar = target;
    nums = numbers;

    dfs(0, 0);
    return answer;
}

채점 결과

+ Recent posts