1. 문제

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

 

1738번: 골목길

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로

www.acmicpc.net

문제 설명

민승이는 놀러가기 위해 집을 나섰다. 민승이네 집에서 코레스코 콘도까지 가기 위해서는 복잡하게 얽혀있는 골목길들을 통과해야 한다.

그런데, 어떤 길에는 깡패가 서식하고 있어, 그 길을 지나게 되면 깡패에게 일정한 양의 금품을 갈취당하게 된다. 그런가하면, 어떤 길에는 지나가던 행인들이 흘리고 간 금품들이 떨어져 있어, 그 길을 지나게 되면 일정한 양의 금품을 획득하게 된다. 한 번 지나간 길을 다시 방문하더라도 금품을 갈취당하거나 획득한다.

골목길의 연결 상태와, 각 골목길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이 주어졌을 때, 민승이가 최대한 유리한 경로를 따라 집에서 코레스코 콘도까지 가기 위해서는 어떻게 해야 하는지 출력하는 프로그램을 작성하시오. 

보유 중인 금품의 양이 음수가 될 수 있다. 최대한 유리한 경로 또는 최적의 경로는 민승이네 집에서 출발하여 코레스코 콘도에 도착하는 경로 중 금품의 양이 최대가 되는 경로이다. 

입력

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로 주어진다. 이는 u번 교차점에서 v번 교차점으로 이동할 수 있는 골목길이 나있다는 의미이다. 즉, 주어지는 골목길들은 기본적으로 모두 일방통행로이다. w (0 ≤ |w| ≤ 1,000)는 이 길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이다. 음수는 갈취, 양수는 획득을 뜻한다.

골목길의 교차점 번호는 1이상 n이하의 정수이다. 민승이네 집은 1번 교차점에 있고, 이곳 코레스코 콘도는 n번 교차점에 있다.

출력

최적의 경로를 구할 수 있다면 민승이네 집부터 코레스코 콘도까지 가는 동안 거치게 되는 교차점들의 번호를 공백 하나를 사이에 두고 차례로 출력하면 된다. 그런데, 경우에 따라서는 최적의 경로라는 것이 존재하지 않는 상황이 발생한다. 어떠한 경우에 그런 상황이 발생하는지 생각해 보자. 그러한 경우에는 -1을 출력하도록 한다.

최적의 경로가 여러 개 존재할 때는 아무거나 출력해도 된다.

 

2. 어떻게 풀까

  • 최단 경로 알고리즘을 이용해서 해결 할 수 있다.
  • 이 문제의 경우 최솟값을 구하는게 아니라 최댓값을 구하는 것이므로 음수 사이클을 찾는 것이 아니라 양수 사이클을 찾아야 한다.
  • 사이클이 형성되는지 확인 해야하므로 벨만 포드 알고리즘을 이용하여 해결했다.
    • 사이클이 존재하더라도 사이클이 목적지에 영향을 주지 않는다면 상관없다.
      • 이 점을 간과해서 틀렸다. ㅜ

 

3. 코드 (Java)

package baekjoon.shortest_path.골목길_20220318;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static StringTokenizer tokenizer;

  private static int vertexCount;
  private static int edgeCount;
  private static int[][] edges;
  private static int[] distanceFromSource;
  private static int[] predecessor;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    edgeCount = Integer.parseInt(tokenizer.nextToken());

    init();
    bellmanFord();

    if (distanceFromSource[vertexCount] == Integer.MIN_VALUE) {
      System.out.println(-1);
      return;
    }

    for (int[] edge : edges) {
      int from = edge[0];
      int to = edge[1];
      int weight = edge[2];
      if (distanceFromSource[from] == Integer.MIN_VALUE) {
        continue;
      }

      if (isCycle(from, to, weight) && isCycleOnPath(to)) {
        System.out.println(-1);
        return;
      }
    }

    printShortestPath();
  }

  private static void init() throws IOException {
    edges = new int[edgeCount][];
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      edges[i] = new int[]{from, to, weight};
    }

    distanceFromSource = new int[vertexCount + 1];
    predecessor = new int[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      distanceFromSource[i] = Integer.MIN_VALUE;
      predecessor[i] = -1;
    }
    distanceFromSource[1] = 0;
  }

  private static void bellmanFord() {
    for (int i = 0; i < vertexCount - 1; i++) {
      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];
        int weight = edge[2];
        if (distanceFromSource[from] == Integer.MIN_VALUE) {
          continue;
        }
        if (distanceFromSource[to] < distanceFromSource[from] + weight) {
          distanceFromSource[to] = distanceFromSource[from] + weight;
          predecessor[to] = from;
        }
      }
    }
  }

  private static boolean isCycle(int from, int to, int weight) {
    return distanceFromSource[to] < distanceFromSource[from] + weight;
  }

  private static boolean isCycleOnPath(int targetVertex) {
    boolean[] visit = new boolean[vertexCount + 1];
    Queue<Integer> queue = new LinkedList<>();
    queue.add(targetVertex);

    while (!queue.isEmpty()) {
      int currentVertex = queue.poll();
      if (visit[currentVertex]) {
        continue;
      }
      visit[currentVertex] = true;

      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];

        if (from == currentVertex && !visit[to]) {
          queue.add(to);
        }
      }
    }

    return visit[vertexCount];
  }

  private static void printShortestPath() {
    StringBuilder sb = new StringBuilder();
    int index = vertexCount;
    sb.append(vertexCount);
    while (predecessor[index] != -1) {
      sb.insert(0, predecessor[index] + " ");
      index = predecessor[index];
    }
    System.out.print(sb.toString());
  }
}
  • bellmanFord() 메소드를 통해 출발 노드부터 목적지 노드까지의 최단 경로를 구했다.
  • 목적지 노드까지의 경로가 없거나, 경로상에 양수 사이클이 존재할 경우 -1를 출력하도록 했다.
  • 경로 상에 양수 사이클이 존재하는지 검사했다.
    • 사이클이 존재하는 지 검증
      • 벨만 포드 알고리즘 이후에도 distanceFromSource가 갱신이 되는지 확인했다.
    • 사이클이 존재하고, 그 사이클이 경로상에 존재하는지 검증
      • 사이클이 존재하는 노드에서 목적지 노드까지의 경로가 있는지 BFS를 이용하여 확인했다.
      • 이 부분을 간과해서 틀렸다.
  • predecessor과 backtracking을 이용해서 최단 경로를 출력했다.
    • 처음에는 StringBuilder에 append()로 하나씩 추가한 후 reverse()로 마무리를 했었는데 계속 실패가 나와서 insert()로 바꾸었다.

 

벨만 포드 알고리즘을 알고 있다면 쉽게 풀 수 있긴 한데, 음수(문제는 양수)사이클이 출발 노드에서 목적지 노드까지의 경로에 포함되는지 검사하는 로직이 반드시 필요하다는것을 간과했다. 

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

https://programmers.co.kr/learn/courses/30/lessons/43163

 

코딩테스트 연습 - 단어 변환

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다. 1. 한 번에 한 개의 알파벳만 바꿀 수

programmers.co.kr

문제 설명

제한 사항

입출력 예


2. 어떻게 풀까?

  • 인자로 주어지는 begin에서 시작해서 words에 주어진 단어들과 비교하면서 알파벳 하나만 바꿔서 그 단어가 될 수 있는지 확인한다.
  • words내에 방문한 단어들을 방문 표시하고, 모든 단어들을 방문하거나 target과 같은 단어를 방문하고 난 뒤에는 다시 방문 표시를 지워주어야 한다. 이게 무슨소리냐.. 말로 설명하니 어려우니까 그림으로 대체

visit가 다음과 같이 이루어진다고 가정하자. 방문한 word에 visit을 표시
cog까지 도착했을 때 distance는 6이다. 이제 dog로 돌아갈 때 visit 표시를 한 cog를 다시 unvisit로 표시하지 않으면 → cog로 갈 수가 없다
dog → log → cog로 갈 때보다, → cog로 가는 경로가 더 빠르다. 가장 빠른 길을 찾고 있기 때문에 갱신을 위해서 visit을 다시 unvisit로 바꿔주는 작업이 반드시 필요하다.

  • 아니 그럼 visit을 굳이 둘 필요 없지않냐? 라고 생각할 수도 있는데.... 그러면 무한루프에 hot → dot → hot → dot ... 이런식으로 무한루프에 빠지니까 visit또한 반드시 필요하다.

 

 

 

  • hit에서 hot으로, hot에서 dog로 알파벳 하나를 바꿔서 만들 수 있는 단어라는 것을 우리는 직관적으로 알 수 있지만 이것을 코드로는 어떻게 구현할까?
  • 단어와 단어 한 글자씩 비교하면서 다른 글자라면 count를 증가시키고, 그 count가 1이라면 알파벳 하나를 바꿔 만들 수 있는 단어가 된다고 판단하면 되겠다.

3. 코드 (Java)

package programmers.level3.단어변환_20220125;

import java.util.HashMap;
import java.util.Map;

public class Solution {

  public int result = Integer.MAX_VALUE;
  public Map<String, Boolean> visit;

  public int solution(String begin, String target, String[] words) {
    visit = new HashMap<>();
    for (String word : words) {
      visit.put(word, false);
    }
    dfs(begin, target, words, 0);
    return result == Integer.MAX_VALUE ? 0 : result;
  }

  private void dfs(String currentWord, String targetWord, String[] words, int distance) {

    if (currentWord.equals(targetWord)) {
      result = Math.min(result, distance);
      return;
    }

    for (String word : words) {
      if (!visit.get(word) && getCharDifferenceCount(currentWord, word) == 1) {
        visit.replace(word, true);
        dfs(word, targetWord, words, distance + 1);
        visit.replace(word, false);
      }
    }
  }

  private int getCharDifferenceCount(String a, String b) {
    if (a.length() != b.length()) {
      return -1;
    }

    int result = 0;
    for (int i = 0; i < a.length(); i++) {
      if (a.charAt(i) != b.charAt(i)) {
        result++;
      }
    }
    return result;
  }
}

 

 

 

 

 

 

사실 틀렸었다.

애매하게 테스트3만 틀려서 더 멘붕옴;

이유를 생각해봤는데.... 알파벳을 한 번 바꿔서 일치하는지 체크하는 부분에서 실수를 저질렀다.

    for (String word : words) {
      String erase = currentWord.replaceAll("[" + word + "]", "");
      if (!visit.get(word) && erase.length() == 1) {
        visit.replace(word, true);
        dfs(word, targetWord, words, distance + 1);
        visit.replace(word, false);
      }
    }

내 생각은 이랬다. "hot"에서 정규표현식을 이용하여 "[hit]"에 매치되는 부분을 지우면 "o"만 남게되고 그러면 알파벳 차이가 1개이니까 조건에 부합하겠구나! 나 천잰듯?

천재가 아니라 멍청이었다.

만약 단어가 "hto"였다면 어떨까? "hto" → "hit"가 되려면 알파벳을 2개 바꿔야한다. (t → i, o → t) 근데 저 코드대로라면 erase는 역시 "o"만 남게되고 조건에 맞으니까 if문을 통과하게 된다.

아니.. 개 허술한 코드였는데.. 다 틀려야지 하나만 틀리니까 더 찾기 힘들잖아.....................

그래서 정공법으로 코드 수정한 결과가 getCharDifferenceCount() 메소드를 이용한 것이었다.

그나마 다행인 것은 구글링해서 뭐가 틀렸는지 확인한 것이 아니라 스스로 틀린 부분을 생각해낸 부분이라는점..?

1. 문제

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

 

1944번: 복제 로봇

첫째 줄에 미로의 크기 N(4 ≤ N ≤ 50)과 열쇠의 개수 M(1 ≤ M ≤ 250) 이 공백을 사이에 두고 주어진다. 그리고 둘째 줄부터 N+1째 줄까지 미로의 정보가 주어진다. 미로는 1과 0, 그리고 S와 K로 주어

www.acmicpc.net

문제 설명

세준이는 어느 날 획기적인 로봇을 한 개 개발하였다. 그 로봇은 복제 장치를 이용하면 자기 자신을 똑같은 로봇으로 원하는 개수만큼 복제시킬 수 있다. 세준이는 어느 날 이 로봇을 테스트하기 위하여 어떤 미로에 이 로봇을 풀어 놓았다. 이 로봇의 임무는 미로에 흩어진 열쇠들을 모두 찾는 것이다. 그리고 열쇠가 있는 곳들과 로봇이 출발하는 위치에 로봇이 복제할 수 있는 장치를 장착해 두었다.

N*N의 정사각형 미로와 M개의 흩어진 열쇠의 위치, 그리고 이 로봇의 시작 위치가 주어져 있을 때, 모든 열쇠를 찾으면서 로봇이 움직이는 횟수의 합을 최소로 하는 프로그램을 작성하여라. 로봇은 상하좌우 네 방향으로 움직이며, 로봇이 열쇠가 있는 위치에 도달했을 때 열쇠를 찾은 것으로 한다. (복제된 로봇이어도 상관없다) 하나의 칸에 동시에 여러 개의 로봇이 위치할 수 있으며, 로봇이 한 번 지나간 자리라도 다른 로봇 또는 자기 자신이 다시 지나갈 수 있다. 복제에는 시간이 들지 않으며, 로봇이 움직이는 횟수의 합은 분열된 로봇 각각이 움직인 횟수의 총 합을 말한다. 복제된 로봇이 열쇠를 모두 찾은 후 같은 위치로 모일 필요는 없다.

입력

첫째 줄에 미로의 크기 N(4 ≤ N ≤ 50)과 열쇠의 개수 M(1 ≤ M ≤ 250) 이 공백을 사이에 두고 주어진다. 그리고 둘째 줄부터 N+1째 줄까지 미로의 정보가 주어진다. 미로는 1과 0, 그리고 S와 K로 주어진다. 1은 미로의 벽을 의미하고, 0은 지나다닐 수 있는 길, S는 로봇이 출발하는 위치, K는 열쇠의 위치가 주어진다. S는 1개, K는 M개가 주어진다. S와 K에서만 복제를 할 수 있음에 유의한다. 미로는 벽으로 둘러쌓여 있는 형태이다. 즉, 모든 테두리는 벽이다.

출력

첫째 줄에 모든 로봇이 움직인 횟수의 총 합을 출력한다. 모든 열쇠를 찾는 것이 불가능한 경우 횟수 대신 첫째 줄에 -1을 출력하면 된다.

 

 

 

 

2. 어떻게 풀까

  • 각 Key와 Start 지점에서 로봇이 복제 될 수 있다는 것은 A지점에서 출발해서 B지점으로 가는 거리가 Start 지점에서 B지점으로 가는 거리가 더 가까울 수 있다는 뜻이다.
  • 그렇다는 것은..? 각 지점을 정점이라고 가정하고 각 정점끼리 간선이 이어져 있고 이 간선들을 이용해서 최소 신장 트리를 만들라는 얘기가 될 것이다.
  • 최소 신장 트리를 만들 수 없는 경우에는 -1을 출력하면 될 것이고, 최소 신장 트리가 만들어질 경우에는 그 가중치의 최솟값을 출력하면 되겠다.
  • 그렇다면 정점은 입력으로 주어진 map의 좌표를 이용해서 저장하면 될 것이고... 각 정점간의 간선을 어떻게 만들것인가.. map에서 상하좌우로만 움직일 수 있고, 각 좌표간 거리는 1이라고 했으니까 BFS를 이용해서 각 정점간의 거리를 구하고 그것을 이용해서 간선을 구현하면 되겠다.

드가보자

 

 

 

 

3. 코드 (자바, 통과한 코드)

package baekjoon.mst.복제로봇_20220120;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Main {

  private static BufferedReader reader;
  private static int[] pointY = {0, 0, 1, -1};
  private static int[] pointX = {1, -1, 0, 0};
  private static List<int[]> edges;
  private static Vertex[] vertices;
  private static int[] parents;

  public static void main(String[] args) throws IOException {
    reader = new BufferedReader(new InputStreamReader(System.in));
    edges = new ArrayList<>();

    String[] parts = reader.readLine().split(" ");
    int mapSize = Integer.parseInt(parts[0]);
    int vertexCount = Integer.parseInt(parts[1]) + 1;
    vertices = new Vertex[vertexCount];
    char[][] maps = buildMapsAndAddVertex(mapSize);

    for (Vertex vertex : vertices) {
      getEdgesUsingBFS(maps, vertex);
    }

    parents = makeSet();
    int result = kruskal();

    System.out.println(isMST() ? result : -1);
  }

  private static char[][] buildMapsAndAddVertex(int mapSize) throws IOException {
    char[][] maps = new char[mapSize][mapSize];
    int vertexIndex = 0;

    for (int i = 0; i < mapSize; i++) {
      String line = reader.readLine();
      for (int j = 0; j < mapSize; j++) {
        maps[i][j] = line.charAt(j);
        if (isSourceOrKey(maps[i][j])) {
          vertices[vertexIndex] = new Vertex(i, j, vertexIndex++);
        }
      }
    }
    return maps;
  }

  private static boolean isSourceOrKey(char c) {
    return c == 'S' || c == 'K';
  }

  private static void getEdgesUsingBFS(char[][] maps, Vertex sourceVertex) {
    Queue<int[]> queue = new LinkedList<>();
    boolean[][] visit = new boolean[maps.length][maps.length];
    visit[sourceVertex.y][sourceVertex.x] = true;
    queue.add(new int[]{sourceVertex.y, sourceVertex.x, 0});

    while (!queue.isEmpty()) {
      int[] current = queue.poll();
      int currentY = current[0];
      int currentX = current[1];
      int distanceFromSourceVertex = current[2];

      for (int i = 0; i < 4; i++) {
        int nextY = currentY + pointY[i];
        int nextX = currentX + pointX[i];

        if (!visit[nextY][nextX] && isValidPoint(maps, nextY, nextX)) {
          visit[nextY][nextX] = true;
          queue.add(new int[]{nextY, nextX, distanceFromSourceVertex + 1});

          if (isSourceOrKey(maps[nextY][nextX])) {
            for (Vertex destinationVertex : vertices) {
              if (destinationVertex.y == nextY && destinationVertex.x == nextX) {
                edges.add(new int[]{sourceVertex.id, destinationVertex.id, distanceFromSourceVertex + 1});
                break;
              }
            }
          }
        }
      }
    }
  }

  private static boolean isValidPoint(char[][] maps, int nextY, int nextX) {
    return (nextY >= 0 && nextY < maps.length)
        && (nextX >= 0 && nextX < maps.length)
        && (maps[nextY][nextX] != '1');
  }

  private static int kruskal() {
    edges.sort(Comparator.comparingInt(x -> x[2]));
    int result = 0;
    for (int[] edge : edges) {
      int from = edge[0];
      int to = edge[1];
      int weight = edge[2];

      if (find(parents, from) != find(parents, to)) {
        union(parents, from, to);
        result += weight;
      }
    }
    return result;
  }

  private static int[] makeSet() {
    int[] parents = new int[vertices.length];
    for (int i = 0; i < parents.length; i++) {
      parents[i] = i;
    }
    return parents;
  }

  private static int find(int[] parents, int x) {
    if (parents[x] == x) {
      return x;
    }
    return parents[x] = find(parents, parents[x]);
  }

  private static void union(int[] parents, int x, int y) {
    x = find(parents, x);
    y = find(parents, y);

    if (x == y) {
      return;
    }

    if (x < y) {
      parents[y] = x;
    } else {
      parents[x] = y;
    }
  }

  private static boolean isMST() {
    for (int i = 0; i < vertices.length; i++) {
      if (find(parents, i) != 0) {
        return false;
      }
    }
    return true;
  }

  private static class Vertex {

    int y;
    int x;
    int id;

    public Vertex(int y, int x, int id) {
      this.y = y;
      this.x = x;
      this.id = id;
    }
  }
}

부가 설명 없이 코드만 보고 이해할 수 있는 코드...라고 믿고 부가 설명은 달지 않는다. (이해가 안되신다면 댓글 감사하겠습니다..)

 

근데 틀렸었다.

무수한 틀렸습니다...

틀린 이유를 코드를 보면서 골똘히 생각해봤는데 두 부분에서 버그가 있었다.

          if (isSourceOrKey(maps[nextY][nextX])) {
            for (Vertex destinationVertex : vertices) {
              int destinationVertexId = -1;
              if (destinationVertex.y == nextY && destinationVertex.x == nextX) {
                destinationVertexId = destinationVertex.id;
                break;
              }
              edges.add(
                  new int[]{sourceVertex.id, destinationVertex.id, distanceFromSourceVertex + 1});
            }
          }

BFS를 이용해서 각 Vertex간의 거리를 구하고 탐색하는 좌표가 Vertex에 해당하면 Edge를 만들어 저장하는 부분이다. 여기서 edges.add() 부분이 if (destinationVertex.y == nextY && destinationVertex.x == nextX) 안에 있어야 했다. 그렇지 않으면 index가 -1인 상태로 edges에 추가되어서 버그가 발생할 수 있다.

 

    parents = makeSet();
    int result = kruskal();

    System.out.println(!edges.isEmpty() ? result : -1);

 

모든 Key에 닿을 수 있는 경우 가중치의 최솟값을 출력하고 그렇지 않은 경우 -1을 리턴하는 부분이다. 여기서 edges가 비어있는 경우를 모든 Key에 닿을 수 없는 경우로 생각하고 작성했는데, 생각해보니 edges 있어도 모든 Key에 닿을 수 있는 경우가 있을 수 있다. 그래서 find() 메소드로 모든 vertex들이 같은 집합인지 체크하는 isMST() 메소드를 작성하여 수정했다. 

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

문제

https://www.codewars.com/kata/5a8d2bf60025e9163c0000bc

 

Codewars: Achieve mastery through coding challenge

Codewars is a coding practice site for all programmers where you can learn various programming languages. Join the community and improve your skills in many languages!

www.codewars.com

 

문제 설명

In this Kata, you will sort elements in an array by decreasing frequency of elements. If two elements have the same frequency, sort them by increasing value.

Solution.sortByFrequency(new int[]{2, 3, 5, 3, 7, 9, 5, 3, 7});

// Returns {3, 3, 3, 5, 5, 7, 7, 2, 9}
// We sort by highest frequency to lowest frequency.
// If two elements have same frequency, we sort by increasing value.

More examples in test cases.

Good luck!

 

배열에 integer 값들 count순서대로 정렬하고, count가 같다면 오름차순 정렬하라는 뜻

 

 

 

 

어떻게 풀까?

  • 정렬 문제라서 무지성으로 sort() 메소드를 써야겠다라는 생각을 함
  • 근데 counting순서로 정렬을 할 수 있나..?? sort() 메소드 쓰기전에 배열의 값들 counting을 해야할 것 같음
  • 일단 Map에 (int, count)으로 데이터를 모으고 얘네를 count순으로 정렬하고 배열에 담는 식으로 하면 될 것 같음
  • 드가자

 

 

 

 

코드

자바 코드

package codewars._6kyu.Simple_frequency_sort_20220117;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Solution {

  public static int[] sortByFrequency(int[] array) {
    Map<Integer, Integer> countingMap = getCountingMap(array);
    List<Entry<Integer, Integer>> entryList = getSortedEntryList(countingMap);
    List<Integer> result = getListSortedByFrequency(entryList);

    return result.stream().mapToInt(x -> x).toArray();
  }

  private static Map<Integer, Integer> getCountingMap(int[] array) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int target : array) {
      map.put(target, map.getOrDefault(target, 0) + 1);
    }
    return map;
  }

  private static List<Entry<Integer, Integer>> getSortedEntryList(
      Map<Integer, Integer> countingMap) {
    List<Entry<Integer, Integer>> entryList = new ArrayList<>(countingMap.entrySet());
    entryList.sort((o1, o2) -> {
      if (o1.getValue().equals(o2.getValue())) {
        return o1.getKey() - o2.getKey();
      }
      return o2.getValue() - o1.getValue();
    });
    return entryList;
  }

  private static List<Integer> getListSortedByFrequency(List<Entry<Integer, Integer>> entryList) {
    List<Integer> result = new ArrayList<>();
    for (Entry<Integer, Integer> entry : entryList) {
      int key = entry.getKey();
      int value = entry.getValue();
      for (int i = 0; i < value; i++) {
        result.add(key);
      }
    }
    return result;
  }

  public int[] sortByFrequencyBestPracticeSolution(int[] array) {
    Map<Integer, Long> integerCountMap =
        Arrays.stream(array)
            .boxed()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    return Arrays
        .stream(array)
        .boxed()
        .sorted(Comparator.<Integer, Long>comparing(integerCountMap::get)
            .reversed()
            .thenComparing(Comparator.naturalOrder()))
        .mapToInt(Integer::intValue)
        .toArray();
  }
}

Map으로 (int, count) 데이터를 만들고, EntrySet list를 통해 value(count) 순으로 정렬 (count가 같다면 key 오름차순), 그리고 result list에 하나씩 담아 배열로 변환하여 리턴

근데 Best Practice를 보니까 Stream으로 신박하게 해결했다. 참고하면 좋을듯?

Collectors.groupingBy(), Function.identity(), Collectors.counting(), Comparator.<Integer, long>comparing() 메소드들은 처음 보는 메소드들인데 알아두면 Stream에서 요긴하게 쓸 수 있을듯

 

 

 

파이썬 코드

def solve(arr):
    int_count_map = {}
    for integer in arr:
        int_count_map.setdefault(integer, 0)
        int_count_map[integer] = int_count_map.get(integer) + 1

    sorted_by_value_dict = sorted(int_count_map.items(), key=lambda x: x[1], reverse=True)

    result = []
    for items in sorted_by_value_dict:
        key = items[0]
        value = items[1]
        for i in range(value):
            result.append(key)

    return result


def solve_best_practice(arr):
    return sorted(sorted(arr), key=lambda n: arr.count(n), reverse=True)

파이썬으로도 풀어보자 해서 억지로 풀긴했다. 근데 BestPractice보니까 헛웃음이 나더라

자바에서 푼 것 처럼 dict에 (int, count)로 담고, count 내림차순으로 정렬했다.

근데 BestPractice보니까 그냥 개 쉽게 풀어버림.. 당황스럽네

 

1. 문제

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

 

1238번: 파티

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어

www.acmicpc.net

문제 설명

N개의 숫자로 구분된 각각의 마을에 한 명의 학생이 살고 있다.

어느 날 이 N명의 학생이 X (1 ≤ X ≤ N)번 마을에 모여서 파티를 벌이기로 했다. 이 마을 사이에는 총 M개의 단방향 도로들이 있고 i번째 길을 지나는데 Ti(1 ≤ Ti ≤ 100)의 시간을 소비한다.

각각의 학생들은 파티에 참석하기 위해 걸어가서 다시 그들의 마을로 돌아와야 한다. 하지만 이 학생들은 워낙 게을러서 최단 시간에 오고 가기를 원한다.

이 도로들은 단방향이기 때문에 아마 그들이 오고 가는 길이 다를지도 모른다. N명의 학생들 중 오고 가는데 가장 많은 시간을 소비하는 학생은 누구일지 구하여라.

제한 사항

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어온다. 시작점과 끝점이 같은 도로는 없으며, 시작점과 한 도시 A에서 다른 도시 B로 가는 도로의 개수는 최대 1개이다.

모든 학생들은 집에서 X에 갈수 있고, X에서 집으로 돌아올 수 있는 데이터만 입력으로 주어진다.

출력

첫 번째 줄에 N명의 학생들 중 오고 가는데 가장 오래 걸리는 학생의 소요시간을 출력한다.

입출력 예


2. 어떻게 풀까?

  • 처음 답
    • 주어진 입력으로 인접리스트를 만든다.
    • Dijkstra 알고리즘을 이용하여 각 지점에서 X지점까지의 최단거리를 구한다.
    • Dijkstra 알고리즘을 이용하여 X지점에서 각 지점까지의 최단거리를 구한다.
    • 가장 값이 큰 값을 출력한다.
  • 다른 답 참고
    • 인접리스트를 만들 때, from -> to의 정보를 to -> from 역방향 인접리스트를 하나 더 만든다.
    • 각 지점에서 X지점까지 최단거리는 역방향 인접리스트에서 보면 X지점에서 각 지점까지의 최단거리와 같다.
    • 그러므로
      • 각 지점에서 X지점까지 최단거리는 역방향 인접리스트를 이용하고
      • X지점에서 각 지점까지는 인접리스트를 이용한다.
    • 그 이후는 같다.

3. 코드

처음 답안 코드

import java.io.*;
import java.util.*;

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int vertexCount;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    int edgeCount = Integer.parseInt(tokenizer.nextToken());
    int destination = Integer.parseInt(tokenizer.nextToken());

    List<Edge>[] adjList = new List[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      adjList[i] = new ArrayList<>();
    }
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      adjList[from].add(new Edge(to, weight));
    }

    int max = Integer.MIN_VALUE;
    for (int i = 1; i <= vertexCount; i++) {
      int distance = dijkstra(adjList, i, destination) + dijkstra(adjList, destination, i);
      max = Math.max(max, distance);
    }

    WRITER.write(max + "");
    WRITER.flush();
    WRITER.close();
    READER.close();

  }

  private static int dijkstra(List<Edge>[] adjList, int source, int destination) {
    PriorityQueue<Edge> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o.weight));
    int[] distance = new int[vertexCount + 1];
    Arrays.fill(distance, Integer.MAX_VALUE);
    boolean[] visit = new boolean[vertexCount + 1];
    queue.add(new Edge(source, 0));
    distance[source] = 0;

    while (!queue.isEmpty()) {
      Edge currentEdge = queue.poll();

      for (Edge adjEdge : adjList[currentEdge.to]) {
        if (distance[adjEdge.to] > distance[currentEdge.to] + adjEdge.weight) {
          distance[adjEdge.to] = distance[currentEdge.to] + adjEdge.weight;
          queue.add(new Edge(adjEdge.to, distance[adjEdge.to]));
        }
      }
    }

    return distance[destination];
  }
}

class Edge {

  int to;
  int weight;

  public Edge(int to, int weight) {
    this.to = to;
    this.weight = weight;
  }
}

다른 답 참고한 코드


import java.io.*;
import java.util.*;

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int vertexCount;
  private static int edgeCount;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    edgeCount = Integer.parseInt(tokenizer.nextToken());
    int destination = Integer.parseInt(tokenizer.nextToken());

    List<Edge>[] adjList = new List[vertexCount + 1];
    List<Edge>[] reverseAdjList = new List[vertexCount + 1];

    buildAdjList(adjList, reverseAdjList);

    int max = Integer.MIN_VALUE;
    int[] go_distance = dijkstra(reverseAdjList, destination);
    int[] back_distance = dijkstra(adjList, destination);

    for (int i = 1; i <= vertexCount; i++) {
      max = Math.max(max, go_distance[i] + back_distance[i]);
    }

    WRITER.write(max + "");
    WRITER.flush();
    WRITER.close();
    READER.close();

  }

  private static void buildAdjList(List<Edge>[] adjList, List<Edge>[] reverseAdjList)
      throws IOException {
    for (int i = 1; i <= vertexCount; i++) {
      adjList[i] = new ArrayList<>();
      reverseAdjList[i] = new ArrayList<>();
    }
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      adjList[from].add(new Edge(to, weight));
      reverseAdjList[to].add(new Edge(from, weight));
    }
  }

  private static int[] dijkstra(List<Edge>[] adjList, int source) {
    PriorityQueue<Edge> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o.weight));
    int[] distance = new int[vertexCount + 1];
    Arrays.fill(distance, Integer.MAX_VALUE);
    queue.add(new Edge(source, 0));
    distance[source] = 0;

    while (!queue.isEmpty()) {
      Edge currentEdge = queue.poll();

      for (Edge adjEdge : adjList[currentEdge.to]) {
        if (distance[adjEdge.to] > distance[currentEdge.to] + adjEdge.weight) {
          distance[adjEdge.to] = distance[currentEdge.to] + adjEdge.weight;
          queue.add(new Edge(adjEdge.to, distance[adjEdge.to]));
        }
      }
    }

    return distance;
  }
}

class Edge {

  int to;
  int weight;

  public Edge(int to, int weight) {
    this.to = to;
    this.weight = weight;
  }
}

4. 느낀 점

  • 처음엔 Dijkstra 알고리즘만 알면 쉽게 풀 수 있는 문제라고 생각했는데 역방향 리스트를 만들어서 더 효율적으로 해결한 답을 보고 이런 방법도 있다는 것을 알 수 있었다. 알아 둬야겠다.
  • BellmanFord나 Floyd 알고리즘으로도 해결 할 수 있을듯

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

16398번: 행성 연결

홍익 제국의 중심은 행성 T이다. 제국의 황제 윤석이는 행성 T에서 제국을 효과적으로 통치하기 위해서, N개의 행성 간에 플로우를 설치하려고 한다. 두 행성 간에 플로우를 설치하면 제국의 함

www.acmicpc.net

문제 설명

홍익 제국의 중심은 행성 T이다. 제국의 황제 윤석이는 행성 T에서 제국을 효과적으로 통치하기 위해서, N개의 행성 간에 플로우를 설치하려고 한다.

두 행성 간에 플로우를 설치하면 제국의 함선과 무역선들은 한 행성에서 다른 행성으로 무시할 수 있을 만큼 짧은 시간만에 이동할 수 있다. 하지만, 치안을 유지하기 위해서 플로우 내에 제국군을 주둔시켜야 한다.

모든 행성 간에 플로우를 설치하고 플로우 내에 제국군을 주둔하면, 제국의 제정이 악화되기 때문에 황제 윤석이는 제국의 모든 행성을 연결하면서 플로우 관리 비용을 최소한으로 하려 한다.

N개의 행성은 정수 1,…,N으로 표시하고, 행성 i와 행성 j사이의 플로우 관리비용은 Cij이며, i = j인 경우 항상 0이다.

제국의 참모인 당신은 제국의 황제 윤석이를 도와 제국 내 모든 행성을 연결하고, 그 유지비용을 최소화하자. 이때 플로우의 설치비용은 무시하기로 한다.

제한 사항

입력

입력으로 첫 줄에 행성의 수 N (1 ≤ N ≤ 1000)이 주어진다.

두 번째 줄부터 N+1줄까지 각 행성간의 플로우 관리 비용이 N x N 행렬 (Cij), (1 ≤ i, j ≤ N, 1 ≤ Cij ≤ 100,000,000, Cij = Cji) 로 주어진다.

출력

모든 행성을 연결했을 때, 최소 플로우의 관리비용을 출력한다.

입출력 예


2. 어떻게 풀까?

  • 최소 신장 트리이므로 Kruskal 알고리즘 혹은 Prim 알고리즘으로 해결한다.

3. 코드

Kruskal 알고리즘

import java.io.*;
import java.util.*;

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int[] parents;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    int vertexCount = Integer.parseInt(tokenizer.nextToken());

    kruskal(vertexCount);

    WRITER.flush();
    WRITER.close();
    READER.close();
  }

  private static void kruskal(int vertexCount) throws IOException {
    List<int[]> edges = new ArrayList<>();
    for (int i = 1; i <= vertexCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 1; j <= vertexCount; j++) {
        int weight = Integer.parseInt(tokenizer.nextToken());
        if (weight != 0) {
          edges.add(new int[]{i, j, weight});
        }
      }
    }

    edges.sort(Comparator.comparingInt(o -> o[2]));

    parents = new int[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      parents[i] = i;
    }

    long sumWeight = 0;
    for (int i = 0; i < edges.size(); i++) {
      int[] current = edges.get(i);
      int from = current[0];
      int to = current[1];
      int weight = current[2];
      if (find(from) != find(to)) {
        union(from, to);
        sumWeight += weight;
      }
    }

    WRITER.write(sumWeight + "");
  }

  private static void union(int from, int to) {
    from = find(from);
    to = find(to);

    if (from > to) {
      parents[to] = from;
    } else {
      parents[from] = to;
    }
  }

  private static int find(int x) {
    if (parents[x] == x) {
      return x;
    }
    return parents[x] = find(parents[x]);
  }
}

Prim 알고리즘

import java.io.*;
import java.util.*;

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    int vertexCount = Integer.parseInt(tokenizer.nextToken());

    prim(vertexCount);

    WRITER.flush();
    WRITER.close();
    READER.close();
  }

  private static void prim(int vertexCount) throws IOException {
    int[][] adjMatrix = new int[vertexCount + 1][vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 1; j <= vertexCount; j++) {
        adjMatrix[i][j] = Integer.parseInt(tokenizer.nextToken());
      }
    }

    PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o[1]));
    boolean[] visit = new boolean[vertexCount + 1];
    queue.add(new int[]{1, 0});

    long sumWeight = 0;
    while (!queue.isEmpty()) {
      int[] current = queue.poll();
      int to = current[0];
      int weight = current[1];

      if (visit[to]) {
        continue;
      }
      visit[to] = true;
      sumWeight += weight;

      for (int i = 1; i <= vertexCount; i++) {
        if (adjMatrix[to][i] != 0) {
          queue.add(new int[]{i, adjMatrix[to][i]});
        }
      }
    }

    WRITER.write(sumWeight + "");
  }
}

 


4. 느낀 점

  • 최소 신장 트리의 Prim, Kruskal 알고리즘을 안다면 쉽게 풀 수 있는 문제

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

2606번: 바이러스

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

www.acmicpc.net

문제 설명

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

제한 사항

입력

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

출력

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

입출력 예


2. 어떻게 풀까?

  • 벽을 먼저 3개 세운다.
  • 바이러스(2)를 중심으로 바이러스를 퍼뜨린다.
  • 0의 개수를 카운팅 한다.
  • 벽 세우는 것을 어떻게 할까?
    • DFS를 이용해서 벽을 세운다
  • 바이러스는 어떻게 퍼뜨릴까?
    • BFS나 BFS를 이용한다.
  • 벽이 3개 세워지는 모든 경우에서 0개수의 최대값을 찾아낸다.
  • 그리고 0개수를 출력!

3. 코드

백준문제는 입,출력을 사용자가 직접 해야해서 테스트코드를 따로 만들지 않았다.

import java.io.*;
import java.util.*;

class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  
  private static int maxSafeZone = Integer.MIN_VALUE;
  private static int row;
  private static int col;
  private static int[] yPoint = {1, -1, 0, 0};
  private static int[] xPoint = {0, 0, 1, -1};

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    row = Integer.parseInt(tokenizer.nextToken());
    col = Integer.parseInt(tokenizer.nextToken());

    int[][] map = buildMap();
    installWall(map, 0);

    WRITER.write(maxSafeZone + "");
    WRITER.flush();
    WRITER.close();
    READER.close();
  }
  
  private static int[][] buildMap() throws IOException {
  	int[][] map = new int[row][col];
    for (int i = 0; i < row; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 0; j < col; j++) {
        map[i][j] = Integer.parseInt(tokenizer.nextToken());
      }
    }
    return map;
  }

  private static void installWall(int[][] map, int wallCount) {
    if (wallCount == 3) {
      int[][] virusMap = spreadVirus(map);
      int safeZone = countSafeZone(virusMap);
      maxSafeZone = Math.max(safeZone, maxSafeZone);
      return;
    }

    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (map[i][j] == 0) {
          map[i][j] = 1;
          installWall(map, wallCount + 1);
          map[i][j] = 0;
        }
      }
    }
  }

  private static int countSafeZone(int[][] map) {
    int count = 0;
    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (map[i][j] == 0) {
          count++;
        }
      }
    }
    return count;
  }

  private static int[][] spreadVirus(int[][] map) {
    int[][] copyMap = new int[row][col];
    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        copyMap[i][j] = map[i][j];
      }
    }

    boolean[][] visit = new boolean[row][col];

    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (copyMap[i][j] == 2 && !visit[i][j]) {
          bfs(copyMap, visit, i, j);
        }
      }
    }
    return copyMap;
  }

  private static void bfs(int[][] map, boolean[][] visit, int startY, int startX) {
    Queue<int[]> queue = new LinkedList<>();
    queue.add(new int[]{startY, startX});
    visit[startY][startX] = true;

    while (!queue.isEmpty()) {
      int[] current = queue.poll();

      for (int i = 0; i < 4; i++) {
        int y = current[0] + yPoint[i];
        int x = current[1] + xPoint[i];

        if (y < 0 || y >= row) {
          continue;
        }
        if (x < 0 || x >= col) {
          continue;
        }
        if (map[y][x] == 0 && !visit[y][x]) {
          visit[y][x] = true;
          map[y][x] = 2;
          queue.add(new int[]{y, x});
        }
      }
    }
  }

}
  • buildMap() : 입력으로 받은 정보로 연구소 지도를 만든다.
  • installWall(int[][] map, int wallCount) : DFS를 이용하여 0을 1로 바꿔 벽을 세운다. 벽이 3개이면 바이러스를 퍼뜨리고 안전한 장소(0)을 카운팅한다.
  • countSafeZone(int[][] map) : 0 개수를 카운팅한다.
  • spreadVirus(int[][] map) : 연구소 지도를 돌며 2인 지점을 찾아내고 2에서 bfs()를 통해 바이러스를 퍼뜨린다.
  • bfs(int[][] map, boolean[][] visit, int startY, int startX) : 2인 지점을 시작으로 바이러스를 퍼뜨린다 ( 0 -> 2)

4. 느낀 점

  • 벽을 설치하는 과정이 Brute-Force라서 시간초과 되지 않을까? 라는 생각에 다른 방법을 생각하다가 더 생각이 안나서 그냥 Brute-Force 방식을 사용했다.
  • 그 외에는 딱히 어려운 것 없는 문제

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25

1. 문제

programmers.co.kr/learn/courses/30/lessons/43105

 

코딩테스트 연습 - 정수 삼각형

[[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]] 30

programmers.co.kr

문제 설명

제한 사항

입출력 예


2. 어떻게 풀까?

  • 문제에서 그림은 실제로 보면 직삼각형이다.

이런 느낌?

  • 7부터 아래로 내려가면서 값을 더한다, 겹치는 칸은 더 큰 값을 할당한다.

 10은 7+3, 15는 7+8
18은 10+8, 16은 10+1와 15+1중 15+1이 더 크기 때문에 16, 15는 15+0
20은 18+2, 25는 18+7과 16+7중 18+7이 더 크기 때문에 25, 20은 16+4와 15+4중 16+4가 더 크기때문에 20, 19는 15+4 
24는 20+4, 30은 25+5, 27은 25+2, 26은 20+6, 24는 19+5

  • 배열의 처음과 끝은, 대소 비교를 할 필요 없이 현재 값과 이전 행의 같은 열 값을 더한 값이다
  • 그 외에는 이전 행의 같은 열과 이전 행중에서 큰 값과 더한 값이다.

3. 코드

테스트 코드

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;

public class SolutionTest {

  @Test
  public void test1() {
    int[][] triangle = {
        {7},
        {3, 8},
        {8, 1, 0},
        {2, 7, 4, 4},
        {4, 5, 2, 6, 5}
    };
    assertThat(new Solution().solution(triangle), is(30));
  }
}

실제 코드

import java.util.Arrays;

public class Solution {

  public int solution(int[][] triangle) {
    int[][] maxSums = init(triangle);
    for (int row = 1; row < triangle.length; row++) {
      fillFirstAndLast(maxSums, triangle, row);
      for (int j = 1; j < maxSums[row].length - 1; j++) {
        maxSums[row][j] = Math.max(maxSums[row - 1][j - 1], maxSums[row - 1][j])
            + triangle[row][j];
      }
    }
    return Arrays.stream(maxSums[maxSums.length - 1]).max().getAsInt();
  }

  private void fillFirstAndLast(int[][] maxSums, int[][] triangle, int row) {
    int first = 0;
    int last = maxSums[row].length - 1;

    maxSums[row][first] = maxSums[row - 1][first] + triangle[row][first];
    maxSums[row][last] = maxSums[row - 1][last - 1] + triangle[row][last];
  }

  private int[][] init(int[][] triangle) {
    int[][] result = new int[triangle.length][];
    for (int i = 0; i < triangle.length; i++) {
      result[i] = new int[triangle[i].length];
    }
    result[0][0] = triangle[0][0];

    return result;
  }
}
  • init() 에서 배열을 생성하고 (0,0)에 triangle[0][0]을 할당한다.
  • fillFirstAndLast()에서 현재 행의 처음 열과 마지막 열의 값을 할당한다.
  • 그리고 그 외의 값은 이전 행의 같은 열과, 이전 열중 더 큰 값과 현재 값을 더한 값을 할당한다.

Best 코드 (내가 보기에 Best)

import java.util.Arrays;

public class BestAnswer {

  public static int solution(int[][] triangle) {
    for (int row = 1; row < triangle.length; row++) {
      triangle[row][0] += triangle[row - 1][0];
      triangle[row][row] += triangle[row - 1][row - 1];
      for (int col = 1; col < row; col++) {
        triangle[row][col] += Math.max(triangle[row - 1][col], triangle[row - 1][col - 1]);
      }
    }
    return Arrays.stream(triangle[triangle.length - 1]).max().getAsInt();
  }
}
  • 굳이 새 배열을 생성하지 않고, 현재 값에 최대값을 더한 값을 할당해준다.
  • triangle[row][triangle[row].length-1] 보다 triangle[row][row]가 훨씬 깔끔해 보인다.

4. 느낀 점

  • 예전에 비슷한 문제를 풀어본 기억이 나서 쉽게 풀었다.

1. 문제

programmers.co.kr/learn/courses/30/lessons/60059

 

코딩테스트 연습 - 자물쇠와 열쇠

[[0, 0, 0], [1, 0, 0], [0, 1, 1]] [[1, 1, 1], [1, 1, 0], [1, 0, 1]] true

programmers.co.kr

문제 설명

제한 사항

입출력 예


2. 어떻게 풀까?

  • lock을 가만히 두고 key를 끝에서 1 x 1에서 n x n까지 이동시키면서 홈에 맞는지 확인한다.
  • 맞지 않는다면 key를 90도 회전한다.
  • key를 4번 회전시켜도 그대로라면 false를 리턴한다.

시작 지점, lock이 전부 채워지지 않았으므로 x
오른쪽으로 한칸 옮겼을 때, 역시 맞지 않음
오른쪽 끝으로 갔을때, 역시 맞지 않음!!
90도 회전 한번 하고, 오른쪽으로 세 칸, 아래로 세칸 이동시키면 홈에 딱 맞는다. 그러므로 true 리턴

  • 위와 같이 생각했는데, 코드로 어떻게 구현해야 할지 감이 안 와서 고민하다가 결국 다른 사람이 푼 코드를 참고했다.
  • 코드를 보니, 반대로 생각했다. key는 가만히 두고, lock을 움직이면서 key와 맞는지 체크한다.
  • 코드로 구현하기 위해서 key에 padding을 했다.

이런식으로 (lock의 길이 -1) 만큼 상하좌우로 padding한 key의 모습이다.
그리고 lock을 처음 위치에서 오른쪽으로 한 칸, 아래로 한 칸 이동했을때 홈이 딱 맞게된다. 


3. 코드

테스트 코드

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;

public class SolutionTest {

  @Test
  public void test1() {
    int[][] key = {
        {0, 0, 0},
        {1, 0, 0},
        {0, 1, 1}
    };
    int[][] lock = {
        {1, 1, 1},
        {1, 1, 0},
        {1, 0, 1}
    };
    assertThat(new Solution().solution(key, lock), is(true));
  }
}

실제 코드

public class Solution {

  public boolean solution(int[][] key, int[][] lock) {
    int padSize = lock.length - 1;

    for (int i = 0; i < 4; i++) {
      key = rotate(key);
      int[][] paddedKey = pad(key, padSize);
      for (int j = 0; j < paddedKey.length - padSize; j++) {
        for (int k = 0; k < paddedKey.length - padSize; k++) {
          if (isValid(lock, paddedKey, j, k)) {
            return true;
          }
        }
      }
    }

    return false;
  }

  private boolean isValid(int[][] lock, int[][] paddedKey, int j, int k) {
    for (int l = 0; l < lock.length; l++) {
      for (int m = 0; m < lock.length; m++) {
        if (lock[l][m] + paddedKey[j + l][k + m] != 1) {
          return false;
        }
      }
    }
    return true;
  }

  private int[][] pad(int[][] key, int padSize) {
    int[][] result = new int[key.length + padSize * 2][key.length + padSize * 2];

    for (int i = 0; i < key.length; i++) {
      for (int j = 0; j < key.length; j++) {
        result[padSize + i][padSize + j] = key[i][j];
      }
    }
    return result;
  }

  private int[][] rotate(int[][] key) {
    int[][] result = new int[key.length][key.length];
    for (int i = 0; i < key.length; i++) {
      for (int j = 0; j < key.length; j++) {
        result[i][j] = key[key.length - 1 - j][i];
      }
    }
    return result;
  }

  private int[][] copy(int[][] key) {
    int[][] result = new int[key.length][key.length];
    for (int i = 0; i < key.length; i++) {
      for (int j = 0; j < key.length; j++) {
        result[i][j] = key[i][j];
      }
    }
    return result;
  }
}
  • rotate() 에서 key를 오른쪽 90도로 회전한다.
  • pad()에서 locklength - 1 만큼 상하좌우로 추가하여 padding 해준다.
  • padkey의 0부터 끝까지 가는 게 아니라 key까지만 이동한다. 더 이동하면 lock이 pad범위를 벗어나기 때문

4. 느낀 점

  • 문제를 처음 보고, 어렵지만 할 수 있겠다고 생각했다. 실제로 문제 해결 방법은 맞다고 생각한다.
  • 근데 이것을 코드로 구현하는 과정이 너무 어렵다.. 이걸 잘해야 level 3 문제들을 풀 수 있을 듯.

1. 문제

programmers.co.kr/learn/courses/30/lessons/42895

 

코딩테스트 연습 - N으로 표현

 

programmers.co.kr

문제 설명

제한 사항

입출력 예

11의 경우 22 / 2 = 11이므로 2를 3개 사용해서 만들 수 있다.


2. 어떻게 풀까?

  • N으로 9개 이상 표현하면 표현할 수 없다는 것으로 보고 -1을 리턴한다.
  • 그러므로 1개로 표현했을 때 만들 수 있는 값 ~ 8개로 표현했을 때 만들 수 있는 값에서 number와 같은 값이 있으면 리턴하도록 한다.
    1. N 1개를 사용했을 때 표현 가능한 숫자 - N
    2. N 2개를 사용했을 때 표현 가능한 숫자 - NN, (N 1개를 사용했을 때 표현 가능한 값) 사칙연산 (N 1개를 사용했을 때 표현 가능한 값)
    3. N 3개를 사용했을 때 표현 가능한 숫자 - NNN, (N 1개를 사용했을 때 표현 가능한 값) 사칙연산 (N 2개를 사용했을 때 표현 가능한 값) U (N 2개를 사용했을 때 표현 가능한 값) 사칙연산 (N 1개를 사용했을 때 표현 가능한 값)
      • NNN
      • N + NN, N - NN, N * NN, N / NN
      • N + (N+N), N - (N+N), N * (N+N), N / (N+N)
      • N + (N-N), N - (N-N), N * (N-N), N / (N-N)
      • N + (N*N), N -(N*N), N *(N*N), N /(N*N)
      • N + (N/N), N -(N/N), N *(N/N), N /(N/N)
      • NN + N, NN - N, NN * N, NN / N
      • (N+N) + N, (N+N) - N, (N+N) * N, (N+N) / N
      • (N-N) + N, (N-N) - N, (N-N) * N, (N-N) / N
      • (N*N) + N, (N*N) - N, (N*N) * N, (N*N) / N
      • (N/N) + N, (N/N) - N, (N/N) * N, (N/N) / N
    4. 덧셈과 곱셈은 교환 법칙이 성립하기 때문에, (1개 표현 수) 사칙연산 (2개 표현 수)(2개 표현 수) 사칙연산 (1개 표현 수)가 같지만, 뺄셈과 나눗셈은 성립하지 않기 때문에 다르다
      • 2 + 22 = 22 + 2 = 24, 2 * 22 = 22 * 2 = 44
      • 2 - 22 = -20, 22 - 2 = 20, 2 / 22 = 0, 22 / 2 = 11
    5. 그러므로 N x개를 사용했을 때 표현 가능한 숫자는 - N을 x번 붙힌 값, (N 1개 표현 수) (덧셈, 뺄셈, 역뺄셈, 곱셈, 나눗셈, 역나눗셈) (N x-1개 표현수) U (N 2개 표현 수) (덧셈, 뺄셈, 역뺄셈, 곱셈, 나눗셈, 역나눗셈) (N x-2개 표현수) U ... U (N x/2개 표현 수) (덧셈, 뺄셈, 역뺄셈, 곱셈, 나눗셈, 역나눗셈) (N x/2개 표현수) 이다.

3. 코드

테스트 코드

package doNotSolve.programmers.level3.N으로표현;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;

public class SolutionTest {

  @Test
  public void test1() {
    assertThat(new Solution().solution(5, 12), is(4));
    assertThat(new Solution().solution(2, 11), is(3));
    assertThat(new Solution().solution(5, 5), is(1));
    assertThat(new Solution().solution(5, 10), is(2));
    assertThat(new Solution().solution(5, 31168), is(-1));
    assertThat(new Solution().solution(1, 1121), is(7));
  }

  @Test
  public void test2() {
    assertThat(new Solution().solution(5, 1010), is(7));
    assertThat(new Solution().solution(3, 4), is(3));
    assertThat(new Solution().solution(5, 5555), is(4));
    assertThat(new Solution().solution(5, 5550), is(5));
  }

  @Test
  public void test3() {
    assertThat(new Solution().solution(5, 20), is(3));
    assertThat(new Solution().solution(3, 30), is(3));
    assertThat(new Solution().solution(6, 65), is(4));
    assertThat(new Solution().solution(5, 2), is(3));
    assertThat(new Solution().solution(5, 4), is(3));
    assertThat(new Solution().solution(1, 1), is(1));
    assertThat(new Solution().solution(1, 11), is(2));
    assertThat(new Solution().solution(1, 111), is(3));
    assertThat(new Solution().solution(1, 1111), is(4));
    assertThat(new Solution().solution(1, 11111), is(5));
    assertThat(new Solution().solution(7, 7776), is(6));
    assertThat(new Solution().solution(7, 7784), is(5));
  }
}

실제 코드

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class Solution {

  Map<Integer, HashSet<Integer>> calculationResultMap;

  public int solution(int N, int number) {
    calculationResultMap = new HashMap<>();

    for (int i = 1; i < 9; i++) {
      HashSet<Integer> calculationResults = getCalculationValues(i, N);
      if (calculationResults.contains(number)) {
        return i;
      }
      calculationResultMap.put(i, calculationResults);
    }

    return -1;
  }

  private HashSet<Integer> getCalculationValues(int count, int N) {
    HashSet<Integer> set = new LinkedHashSet<>();
    int attach = getAttachValue(count, N);
    set.add(attach);

    for (int j = 1; j <= count / 2; j++) {
      for (int a : calculationResultMap.get(j)) {
        for (int b : calculationResultMap.get(count - j)) {
          for (Operator op : Operator.values()) {
            set.add(op.calcuate(a, b));
          }
        }
      }
    }

    return set;
  }

  private int getAttachValue(int count, int N) {
    return Integer.parseInt(Integer.toBinaryString((int) Math.pow(2, count) - 1)) * N;
  }

}

enum Operator {
  PLUS {
    @Override
    public int calcuate(int a, int b) {
      return a + b;
    }
  },
  MINUS {
    @Override
    public int calcuate(int a, int b) {
      return a - b;
    }
  },
  BACKMINUS {
    @Override
    public int calcuate(int a, int b) {
      return b - a;
    }
  },
  MULTIPLY {
    @Override
    public int calcuate(int a, int b) {
      return a * b;
    }
  },
  DIVIDE {
    @Override
    public int calcuate(int a, int b) {
      return b == 0 ? 0 : a / b;
    }
  },
  BACKDIVIDE {
    @Override
    public int calcuate(int a, int b) {
      return a == 0 ? 0 : b / a;
    }
  };

  abstract int calcuate(int a, int b);
}

4. 느낀 점

  • 처음 문제를 풀 때, N이 9개 이상이면 -1을 리턴하라고 해서 N 1개로 표현 가능한 식, 2개로 표현 가능한 식, ..., 8개로 표현 가능한 식을 Set에 담아서 그 식을 계산한 값이 number와 같으면 리턴하도록 코드를 작성하면 되겠구나 생각했다.
  • 근데 처음 생각한 코드는, (N-N/N*NN+N) 같은 식은 괄호 위치에 따라 다르게 계산될 수 있는데 그걸 생각 못하고 앞에서부터 계산한 값만 생각했다. 그래서 계속 1번, 8번 테스트가 실패로 나왔다.
  • 왜 틀렸는지 계속 생각해봐도 이해가 안 돼서 다른 사람들이 작성한 코드를 봤는데, 괄호 때문에 계산 순서가 달라질 수 있는 것을 고려하지 않은 점, 뺼셈과 나눗셈은 교환 법칙이 성립하지 않는 점을 생각하지 못해서 틀린 것 같다.
  • 그 외에도 Enum을 사용해서 덧셈, 뺄셈, 역뺄셈, 곱셈, 나눗셈, 역나눗셈을 해결한 코드가 전혀 생각하지 못한 방법이라 똑같이 따라 해 보았다.
  • 완전 탐색(DFS)을 이용하여 해결한 답도 많았는데, 해당 문제 카테고리가 DP(Dynamic Programming)이기도 했고, DFS를 이용하면 N*N-N/N 같은 식도 고려하지 않고 통과가 된다고 한다. 그래서 DFS는 따로 참고하지 않았다.

+ Recent posts