diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a09375 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/* + +*.iml \ No newline at end of file diff --git a/contents/divide_and_conquer.md b/contents/divide_and_conquer.md new file mode 100644 index 0000000..6c2d0eb --- /dev/null +++ b/contents/divide_and_conquer.md @@ -0,0 +1,23 @@ +# Divide and Conquer 알고리즘 + +- '분할정복' 기법 이라고도 부릅니다. +- 문제(problem)를 잘게 쪼개면서(sub-problem) 풀어나가고 다시 병합하며 해를 찾는 알고리즘 입니다. +- 상위 문제를 구하기 위해 아래로 내려가며 답을 찾는 하향식 접근법 +- [divide and conquer](https://www.youtube.com/watch?v=2Rr2tW9zvRg) + + +### 알고리즘 설계 조언 +1. Divide: 분할이 가능한 문제라면 분할 기준을 선택하여 문제를 2개 이상으로 나눈다. +2. Conquer: 분할이 불가능한 경우 문제를 푼다. +3. Combine: 정복한 문제를 병합하여 해를 찾는다. 제대로 나누면 병합하기도 쉽다. + + +### 알고리즘 효율성 +- 분할정복 알고리즘은 Recursion calls(재귀호출)가 사용되는 경우가 많다. 재귀호출에서 알고리즘 효율이 떨어질 수 있다. => 분할 기준에 따라 효율이 달라질 수 있다. + + +### 분할정복 관련 자료 + +- [백준 Z](https://www.google.com/search?q=%EB%B0%B1%EC%A4%80+z+%EB%AC%B8%EC%A0%9C&oq=%EB%B0%B1%EC%A4%80+z+%EB%AC%B8%EC%A0%9C&aqs=chrome..69i57j0l9.3894j0j7&sourceid=chrome&ie=UTF-8) +- [별 찍기 풀이](https://st-lab.tistory.com/95) +- https://gamedevlog.tistory.com/58 diff --git a/contents/greedy.md b/contents/greedy.md index a557839..096b9e6 100644 --- a/contents/greedy.md +++ b/contents/greedy.md @@ -4,35 +4,37 @@ - 문제를 작은 단위로 쪼개 반복적으로 진행하며 접근하는 방식이다. - 탐욕법은 각 단계마다 현재에 충실한 선택을 한다. - 미래의 선택, 최종 결과는 고려하지 않는다. -- 반드시 최적의 해를 보장하는건 아니다. -- 문제를 작은 단위로 쪼개 풀다보면 같은 문제를 반복해 푸는 경우가 있는데 이 때 그 문제들을 매번 재계산 하지 않고 값을 저장해두었다가 재사용 하는 동적프로그래밍(DP) 기법을 이용 할 수 있다. - - [DP 요약설명](https://github.com/TheCopiens/algorithm-study/blob/master/contents/DP.md) - -### 탐욕법 문제 풀이 순서 -- 회의실 예약을 예시로 설명합니다. 하나의 회의실을 겹치치 않고 최다로 사용할때 몇개의 회의를 진행 할 수 있을까요? -1. 문제의 답을 내는 과정을 여러 부분으로 나눈다. -2. 방식 결정. 어떤 우선순위로 탐욕적 선택을 할지 결정한다. - - 회의 시간이 가장 짧은 회의부터 선택, 가장 먼저 끝나는 회의부터 선택 -3. 탐욕적 선택 속성, 최적 부분 구조 를 만족하는지 따져본다. - - 탐욕적 선택 속성: 답의 모든 부분을 고려하지 않고 탐욕적 선택만 하더라도 최적의 해를 구할 수 있는 속성 - - 모든 단계의 최적해에 '종료시간이 빠른 회의'라는 탐욕적 기준으로 선택한 회의가 존재할까? - - 최적 부분 구조 : 주어진 문제가 **부분 문제의 최적해**로 **전체 문제의 최적해**를 만들 수 있는 구조인지 나타내는 속성 - - 가장 빨리 끝나는 회의를 선택하면 가장 많은 회의를 선택 할 수 있다. - - -1. 해 선택 - - 현재 단계에 가장 최적의 해를 구해서 부분해 집합에 포함시킨다. -2. 조건으로 적절성 검사 - - 포함시킨 부분해 집합이 문제의 조건을 만족하는지 검사한다. - - 만족하지 않을 경우, 한 단계 전으로 돌아가 다른 해를 구해 부분해 집합에 포함한다 -3. 해 검사 - - 포함시킨 부분해 집합이 문제의 해가 되는지 검사한다. - - 아직 문제의 해가 완성되지 않았다면 1번부터 다시 시작한다. +- 반드시 최적의 해를 보장하는건 아니므로 '근사치 추정'을 위해 사용한다. -### 탐욕법을 활용한 문제 -- 동전을 최소 개수로 거슬러주기 -- [체육복](https://github.com/TheCopiens/algorithm-study/blob/ohhako/source/ohhako/200202_greedy.md) -- [큰수만들기](https://programmers.co.kr/learn/courses/30/lessons/42883) +### 탐욕법을 적용한 문제 풀이 +- 회의실 예약을 예시로 설명. 회의 시작 시간/끝나는 시간을 갖는 회의 목록이 존재한다. 회의실 하나를 겹치치 않게 최다로 사용할때 몇개의 회의를 진행 할 수 있을까? + +- 탐욕적 선택 속성: 앞의 선택이 이후 선택에 영향을 주지 않는다. 답의 모든 부분을 고려하지 않고 탐욕적 선택만 하더라도 최적의 해를 구할 수 있는 속성 +- 최적 부분 구조: 작은 부분 문제에서 구한 최적의 답으로 합쳐진 큰 문제의 최적의 답을 구할 수 있어야 한다. + +1. 문제의 답을 내는 과정을 여러 부분으로 나누자 + - 해: 겹치지 않고 선택한 회의 목록 + - 최적해: 겹치지 않고 최다로 선택한 회의 목록 +2. 어떤 우선순위로 탐욕적 선택을 할지 선택 속성 결정 + - 회의 시간이 가장 짧은 회의부터 선택/ 가장 먼저 끝나는 회의부터 선택 +3. 선택 속성이 최적 부분 구조를 만족하는지 따져본다. + - 가장 빨리 끝나는 회의를 선택하면 가장 많은 회의를 선택 할 수 있다. + +### 알고리즘 개선 방향 + +문제를 작은 단위로 쪼개 풀다보면 같은 문제를 반복해 푸는 경우가 있는데 이 때 그 문제들을 매번 재계산 하지 않고 값을 저장해두었다가 재사용 하는 동적프로그래밍(DP) 기법을 이용 할 수 있다. +- [DP 요약설명](https://github.com/TheCopiens/algorithm-study/blob/master/contents/DP.md) + +### 탐욕법을 활용한 문제 +* 동전을 최소 개수로 거슬러주기 +* [체육복](https://github.com/TheCopiens/algorithm-study/blob/ohhako/source/ohhako/200202_greedy.md) +* [큰수만들기](https://programmers.co.kr/learn/courses/30/lessons/42883) +* [RGB거리](https://github.com/TheCopiens/algorithm-study/blob/master/source/ohhako/boj/1149_dp.md) +* [정수 삼각형](https://github.com/TheCopiens/algorithm-study/blob/master/source/ohhako/boj/1932_DP.md) +* [동물원](https://www.acmicpc.net/problem/1309) +* [기타리스트](https://www.acmicpc.net/problem/1495) +* [LCS](https://www.acmicpc.net/problem/9251) +* [동전2](https://www.acmicpc.net/problem/2294) diff --git "a/contents/\352\270\260\353\263\270\354\240\225\353\240\254.md" "b/contents/\352\270\260\353\263\270\354\240\225\353\240\254.md" new file mode 100644 index 0000000..1c0cd7f --- /dev/null +++ "b/contents/\352\270\260\353\263\270\354\240\225\353\240\254.md" @@ -0,0 +1,24 @@ +# 기본정렬 알고리즘 + +## 1. 선택정렬 +구하고자 하는 요소(min or max)를 찾아 swap하는 방식에서 착안하여 정렬에 '선택'이라는 이름이 붙었다. +- 크기가 n인 일차원 배열에서 첫번쨰 인덱스를 i로 뽑는다. +- i의 이후(i+1) 요소중 가장 작은 수(minIndex)를 선택해 i와 자리를 바꾼다. (swap) +- i 다음의 인덱스에서 위 순서를 반복한다. +- 복잡도 항상 O(n^2) + +## 2. 버블정렬 +가장 가까운 인덱스끼리 비교하여 swap하는 과정이 거품이 이는 현상같아 '버블'이라는 이름이 붙었다. +- 크기가 n인 일차원 배열 +- 인덱스 0부터 n-1까지 순회하며 i와 i+1 요소를 비교한다. +- 배열을 한번 돌고나면 마지막 요소에는 가장 큰 수가 있다. +- 마지막 인덱스를 하나씩 줄여가며 위 순서를 반복한다. +- 복잡도 항상 O(n^2) + +## 3. 삽입정렬 +key를 주체로 삽입하는 알고리즘이다. 어떤 값 뒤에 key를 삽입할 것인가? 가 중요하다. +- 크기가 n인 일차원 배열 +- key는 인덱스 1번부터 시작한다. +- 배열을 인덱스 0부터 방문하며 key보다 크면 우측으로 모두 밀고 작으면 그 요소의 바로 뒤에 삽입한다. +- 어느정도 정렬이 되어있는 경우에는 O(n)지만 최악의 평균적으로 복잡도 O(n^2)를 갖는다. +- 밀어야 하는 요소가 많은 경우 i/o가 많이 일어나는 단점이 있다. \ No newline at end of file diff --git "a/contents/\354\235\264\353\266\204\355\203\220\354\203\211.md" "b/contents/\354\235\264\353\266\204\355\203\220\354\203\211.md" new file mode 100644 index 0000000..52ead54 --- /dev/null +++ "b/contents/\354\235\264\353\266\204\355\203\220\354\203\211.md" @@ -0,0 +1,8 @@ +# 이분탐색 + +- 사용 조건: 정렬된 배열에서 데이터를 찾아야 할 떄. +- 완전탐색에서 매 반복마다 탐색 범위를 반으로 줄여가며 찾아가는 탐색 방법 +- 예를들어 `1 2 3 4 5 6 7 8 9 10`에서 8을 찾고 싶을때 정가운데 값 5를 찾은 후 왼쪽은 쳐다보지 않고 5의 오른쪽을 탐색 범위로 한다. + +문제 목록 +https://www.acmicpc.net/problem/2110
diff --git "a/contents/\354\240\225\353\240\254.md" "b/contents/\354\240\225\353\240\254.md" new file mode 100644 index 0000000..6c46085 --- /dev/null +++ "b/contents/\354\240\225\353\240\254.md" @@ -0,0 +1,24 @@ +# 정렬 알고리즘 + +## 1. 선택정렬 +구하고자 하는 요소(min or max)를 찾아 swap하는 방식에서 착안하여 정렬에 '선택'이라는 이름이 붙었다. +- 크기가 n인 일차원 배열에서 첫번쨰 인덱스를 i로 뽑는다. +- i의 이후(i+1) 요소중 가장 작은 수(minIndex)를 선택해 i와 자리를 바꾼다. (swap) +- i 다음의 인덱스에서 위 순서를 반복한다. +- 복잡도 O(n^2) + +## 2. 버블정렬 +가장 가까운 인덱스끼리 비교하여 swap하는 과정이 거품이 이는 현상같아 '버블'이라는 이름이 붙었다. +- 크기가 n인 일차원 배열 +- 인덱스 0부터 n-1까지 순회하며 i와 i+1 요소를 비교한다. +- 배열을 한번 돌고나면 마지막 요소에는 가장 큰 수가 있다. +- 마지막 인덱스를 하나씩 줄여가며 위 순서를 반복한다. +- 복잡도 O(n^2) + +## 3. 삽입정렬 +key를 주체로 삽입하는 알고리즘이다. 어떤 값 뒤에 key를 삽입할 것인가? 가 중요하다. +- 크기가 n인 일차원 배열 +- key는 인덱스 1번부터 시작한다. +- 배열을 인덱스 0부터 방문하며 key보다 크면 우측으로 모두 밀고 작으면 그 요소의 바로 뒤에 삽입한다. +- 어느정도 정렬이 되어있는 경우에는 O(n^2)지만 최악의 평균적으로 복잡도 O(n^2)를 갖는다. +- 밀어야 하는 요소가 많은 경우 i/o가 많이 일어나는 단점이 있다. \ No newline at end of file diff --git "a/docs/\354\204\234\353\241\234 \353\213\244\353\245\270 \354\226\270\354\226\264\353\241\234 \354\225\214\352\263\240\353\246\254\354\246\230 \355\225\250\352\273\230 \352\263\265\353\266\200\355\225\230\352\270\260.md" "b/docs/\354\204\234\353\241\234 \353\213\244\353\245\270 \354\226\270\354\226\264\353\241\234 \354\225\214\352\263\240\353\246\254\354\246\230 \355\225\250\352\273\230 \352\263\265\353\266\200\355\225\230\352\270\260.md" new file mode 100644 index 0000000..446986d --- /dev/null +++ "b/docs/\354\204\234\353\241\234 \353\213\244\353\245\270 \354\226\270\354\226\264\353\241\234 \354\225\214\352\263\240\353\246\254\354\246\230 \355\225\250\352\273\230 \352\263\265\353\266\200\355\225\230\352\270\260.md" @@ -0,0 +1,34 @@ + + +### 서로 다른 언어로 알고리즘 함께 공부하기 + +- 기존에 사용하던 언어 => Java + +- 새로 추가된 언어 => JavaScript + +- 제안하는 방법 + - 코드리뷰 시작 전 알고리즘 이론 정리/분석 설명하기 + - 팀원들과 이 알고리즘에 대해 얘기하는 시간 갖기 + - JavaScript로 문제를 푸는 것은 옵션! + + + +#### 어떤 알고리즘을 선택할 것인가? + +기존 알고리즘 문제는 그날 문제 리뷰가 끝나고 다음 주에 어떤 문제를 풀지 얘기를 나누면서 선택한다. + +- 오늘의 알고리즘! (요즘 각광받고 있는 알고리즘이나 마이너한 알고리즘) + +- 기본적인 알고리즘 (BFS,DFS,DP...) + +- 팀원들이 푸는 문제와 관련있는 알고리즘 + + + +#### 무엇을 설명할 것인가? + +- 동작 방식 +- 복잡도 +- 장/단점 +- 비슷한 알고리즘 유형 +- 대표적인 문제 소개 (풀이는 개인적으로 작성해 업로드) diff --git a/source/.DS_Store b/source/.DS_Store new file mode 100644 index 0000000..4fd3577 Binary files /dev/null and b/source/.DS_Store differ diff --git a/source/diane/[baekjoon]1005.md b/source/diane/[baekjoon]1005.md new file mode 100644 index 0000000..17db1a3 --- /dev/null +++ b/source/diane/[baekjoon]1005.md @@ -0,0 +1,135 @@ +# Baekjoon ACM Craft 문제 + +- ACM Craft + - https://www.acmicpc.net/problem/1005 + +### 문제 이해하기 +ACM Craft는 건물을 짓는 게임으로, 특정 건물을 지으면 승리할 수 있다. +건물은 짓는데 걸리는 건설 시간이 있고, 어떤 건물은 다른 특정 건물이 지어진 후에만 건설할 수 있다. +승리를 위한 건물을 가장 빨리 지을 때까지 걸리는 최소 시간을 알아내자. + +#### 입력 +- int T : 테스트케이스 개수 + - 이 아래 입력은 테스트케이스 개수만큼 반복됨 +- int N, K : 건물의 개수 N, 건설 순서 규칙의 개수 K +- int D1, D2, ... , DN : 각 건물의 건설에 걸리는 시간 +- int X, Y (*K) : 건설 순서 규칙으로, X 건물이 지어진 후에 Y 건물을 지을 수 있다는 뜻 + - 건설 순서 규칙이 K번 반복하여 주어짐 +- int W : 승리하기 위해 지어야할 목표 건물 + +#### 출력 +- int : 목표 건물을 건설 완료하기까지 드는 최소 시간 + +### 문제 접근 방법 +현재 지을 수 있는 건물들을 찾아 지금 지을 수 있는 건물 목록에 추가한다. +건물은 이 목록에 추가된 순간부터 즉시 건설되기 시작한다고 가정한다. +이 목록에서 건설 시간이 가장 짧은 건물을 찾아, 이 건물을 목록에서 제거하고 그 건물이 다 지어진 후의 시간으로 이동한다. +지금 지을 수 있는 건물 목록의 건물들은 그 건설 시간만큼 이미 지어졌으므로, 이 건물들을 건설하는데 걸리는 시간에서 그만큼 빼준다. +이 시점에서 지금 지을 수 있는 건물 목록을 업데이트하고, 위 과정을 반복한다. +그러다가 목표 건물이 지금 지을 수 있는 건물 목록에 추가되는 순간, 그 건물을 건설 시간에 관계 없이 즉시 건설하고 총 시간을 반환한다. + +### 구현 배경 지식 +- Topological Sorting + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; +import java.util.Scanner; + +public class Main { + + // i번 건물이 지어진 후 지을 수 있는 건물 번호들 + static ArrayList> nextBuilding; + // i번 건물이 지어지기 전에 먼저 지어져야 할 건물들 수 + static int[] numOfSource; + // i번 건물이 지어지는 데에 걸리는 시간 + static int[] runningTime; + + static int buildings; + static int rules; + static int target; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + int testCase = sc.nextInt(); + + for (int t=0; t startBuilding = new ArrayList<>(); + while (true) { + // 앞에 해야하는 것이 없는 것들 모으기 + // = 지금 지을 수 있는 건물 + for (int i=0; i runningTime[building]) { + minTime = runningTime[building]; + minBuilding = building; + } + } + time += minTime; + startBuilding.remove((Integer) minBuilding); + for (int b : nextBuilding.get(minBuilding)) { + numOfSource[b]--; + } + for (int b : startBuilding) { + runningTime[b]-=minTime; + } + } + + return time; + } + + private static void getInput(Scanner sc) { + buildings = sc.nextInt(); + rules = sc.nextInt(); + + nextBuilding = new ArrayList<>(); + numOfSource = new int[buildings]; + runningTime = new int[buildings]; + for (int i=0; i()); + numOfSource[i] = 0; + runningTime[i] = sc.nextInt(); + } + + for (int i=0; i visited; + private static double[] Direction; + private static int[] Path; + + public static void main(String args[]) throws Exception { + Scanner sc = new Scanner(System.in); + Number = sc.nextInt(); + Path = new int[Number]; + Direction = new double[4]; + for (int i=0; i<4; i++) { + int temp = sc.nextInt(); + Direction[i] = (double) temp/100; + } + + position = new Position(); + visited = new HashSet<>(); + visited.add(position); + double simple = backtracking(0); + + System.out.println(simple); + } + + private static double backtracking(int depth) throws Exception { + if (depth == Number) { + return calculate(); + } + + double result = 0; + for (int i=0; i<4; i++) { + position.move(i); + Path[depth] = i; + if (isSimple()) { + visited.add(position); + result += backtracking(depth+1); + visited.remove(position); + } + position.back(i); + } + return result; + } + + private static boolean isSimple() { + return !visited.contains(position); + } + + private static double calculate() { + double result = 1; + for (int d : Path) { + result = result*Direction[d]; + } + return result; + } +} + +class Position extends Object { + public int x; + public int y; + + Position() { + x=0; y=0; + } + + Position(int x, int y) { + this.x = x; this.y = y; + } + + public void move(int direction) throws Exception { + if (direction == 0) { + x++; + } else if (direction == 1) { + x--; + } else if (direction == 2) { + y--; + } else if (direction == 3) { + y++; + } else { + throw new Exception("Invalid direction " + direction); + } + } + + public void back(int direction) throws Exception { + if (direction == 0) { + x--; + } else if (direction == 1) { + x++; + } else if (direction == 2) { + y++; + } else if (direction == 3) { + y--; + } else { + throw new Exception("Invalid direction " + direction); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass()!=o.getClass()) return false; + Position p = (Position) o; + return (x==p.x)&&(y==p.y); + } + + @Override + public int hashCode() { + return x*100+y; + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]14888.md b/source/diane/[baekjoon]14888.md new file mode 100644 index 0000000..29e746c --- /dev/null +++ b/source/diane/[baekjoon]14888.md @@ -0,0 +1,101 @@ +# Baekjoon 연산자 끼워넣기 + +- 연산자 끼워넣기 + - hhttps://www.acmicpc.net/problem/14888 + +### 문제 이해하기 +N개의 수로 이루어진 수열이 주어지고, 이 N개의 수 사이에 끼워넣을 (N-1)개의 연산자가 주어진다. +연산자는 덧셈(+), 뺄셈(-), 곱셈(x), 나눗셈(/)으로만 이루어져 있다. +수열의 순서를 바꾸지 않으면서, 수 사이에 이 연산자를 끼워넣어 여러 개의 수식을 만들 수 있다. +이렇게 만들 수 있는 수식의 계산 결과가 최대인 것과 최소인 것을 구하여라. +(단, 수식의 결과값이나 중간 과정값은 항상 -10억보다 크거나 같고, 10억보다 작거나 같다.) + +#### 입력 +- int N : 수열의 길이 (수의 개수, 2<=N<=11) +- int A1, A2, ... , AN : 수열의 숫자 (1<=A<=100) +- int op1, op2, op3, op4 : 사용할 수 있는 덧셈, 뺄셈, 곱셈, 나눗셈의 개수 + +#### 출력 +- int : 최대인 수식의 결과 값 +- int : 최소인 수식의 결과 값 + +### 문제 접근 방법 +모든 경우를 dfs로 탐색하면서, 만들어진 수식의 값이 지금까지의 결과 중 최대이거나 최소인 경우 그 값을 저장한다. + + +### 구현 배경 지식 +- Backtracking +- Depth-First Searching + +### 문제를 해결한 코드 +~~~java +import java.util.Scanner; + +public class Main { + + private static int[] numbers; + private static int[] operations; + private static int MIN; + private static int MAX; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + int size = sc.nextInt(); + + numbers = new int[size]; + for (int i=0; i MAX) { + MAX = number; + } + if (number < MIN) { + MIN = number; + } + return; + } + + for (int op = 0; op<4; op++) { + if (operations[op] > 0) { + operations[op]--; + int newNumber = calculate(op, numbers[idx], number); + putOperation(idx+1, newNumber); + operations[op]++; + } + } + } + + private static int calculate(int op, int num, int number) throws Exception { + if (op == 0) { + return number+num; + } else if (op == 1) { + return number-num; + } else if (op == 2) { + return number*num; + } else if (op == 3) { + return number/num; + } + throw new Exception("Invaild operation."); + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]15684.md b/source/diane/[baekjoon]15684.md new file mode 100644 index 0000000..05bdc57 --- /dev/null +++ b/source/diane/[baekjoon]15684.md @@ -0,0 +1,132 @@ +# Baekjoon 사다리 조작 문제 + +- 사다리 조작 + - https://www.acmicpc.net/problem/15684 + +### 문제 이해하기 +사다리 게임이 있다. 이 사다리 게임은 N개의 세로선과 그 세로선들 사이를 잇는 M개의 가로선으로 이루어져 있다. +이 사다리 게임에서는 세로선에 총 H개의 가로선을 놓을 수 있는데, 먼저 그려진 가로선도 그 위치에 맞추어 그려져 있다. +사다리 게임에서 가로선은 인접한 두 세로선을 연결하고, 두 가로선이 연속하거나 접하는 일은 없다. +주어진 사다리 게임에 가로선을 추가하여 i번 세로선의 결과가 i가 나오도록 만들고 싶다. +최대 3개의 가로선을 추가하여 조건에 맞는 사다리 게임을 만들 때, 최소 몇 개의 가로선을 추가해야 하는지 구하여라. + +#### 입력 +- int N M H : 사다리 게임의 세로선, 가로선 갯수와 세로선에 가로선을 놓을 수 있는 위치의 갯수 +- int a b (x M) : 사다리 게임의 M개의 가로선 위치 + - b번째 세로선과 (b+1)번째 세로선을 잇는, 세로로 a번 위치에 가로선이 존재 + +#### 출력 +- int K : 조건을 만족하기 위해 추가해야 하는 가로선 갯수의 최솟값 + - 정답이 3보다 큰 경우, 또는 불가능한 경우에는 -1을 반환 + +### 문제 접근 방법 +주어진 사다리 게임에 비어있는 모든 가로선 자리에 가로선을 최대 3개까지 그리면서 조건에 맞는지를 확인할 것이다. +이에 따라 아래와 같은 상태 공간 트리를 그릴 수 있다. + +(가로선 0개)                                  추가된가로선없음 +                                                    /                       \ +(가로선 1개)                       1번자리가로선            2번자리가로선    ... +                                         /                    \ +(가로선 2개)           2번자리가로선           3번자리가로선   ... +                              /                 \ +(가로선 3개) 3번자리가로선 4번자리가로선   ... + +이 상태 공간 트리를 dfs로 탐색하면서, 각 노드에서 문제의 요구 조건을 충족하는지 확인할 것이다. +각 노드를 탐색할 때 그 노드가 문제의 기준을 만족하지 않는다면, 그 노드의 자식 노드는 탐색하지 않고, 그 노드의 부모 노드로 돌아가 다른 노드를 탐색할 것이다. +이 때 사용되는 기준 함수(bounding function)은 다음과 같다. 해당 노드의 상태에서 추가될 가로선이 (1) 이미 존재하지 않고 (2) 왼쪽이나 오른쪽의 가로선과 만나서는 안 된다. + +가능성 있는 모든 상태를 탐색하면서, 그 중 조건을 만족하는 상태에서 그 depth를 반환한다. +추가할 가로선이 어떤 가로선인지를 알고 싶은 것이 아니라, 몇 개의 가로선이 추가되어야 하는지를 알기 위해 그 깊이를 반환하고, 그 깊이 중 최소값을 반환한다. +만약 깊이가 3을 탐색했는데 조건을 만족하지 않는다면, 그 이상은 탐색하지 않고 -1을 반환한다. + +### 구현 배경 지식 +- backtracking +- dfs + +### 문제를 해결한 코드 +~~~java +import java.util.Scanner; + +public class Main { + private static int N, M, H; + private static boolean[][] ladder; + private static int Limit = 3; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + N = sc.nextInt(); + M = sc.nextInt(); + H = sc.nextInt(); + ladder = new boolean[N][H]; + + for (int i=0; i iter) Limit = iter; + return iter; + } + + if (iter == Limit) { + return -1; + } + + int result = -1; + for (int i=idx; i temp)) result = temp; + if (result == iter+1) return result; + } + } + } + return result; + } + + private static boolean makeLadder(int lad, int step) { + if (ladder[lad][step]) return false; + if (lad>0 && ladder[lad-1][step]) return false; + if (lad tile; + private HashMap sharks; + + Solution(String file) { + tile = new HashMap<>(); + sharks = new HashMap<>(); + if (file == null) { + setData(); + } else { + readFromFile(file); + } + } + + public int run() { + int iter = 0; + while (iter < 1000) { + iter ++; + ArrayList ss = new ArrayList<>(sharks.keySet()); + ArrayList pp = new ArrayList<>(tile.keySet()); + Collections.sort(ss); + for (int s : ss) { + nextPosition(s); + } + for (Position p : pp) { + State state = tile.get(p); + if (state.next()) { + tile.put(p, state); + } else { + tile.remove(p); + } + } + for (int s : ss) { + Position p = sharks.get(s).position; + if (tile.get(p) == null) { + tile.put(new Position(p.x,p.y), new State(s,k)); + } else if (tile.get(p).shark == s) { + tile.put(new Position(p.x,p.y), new State(s,k)); + } else { + sharks.remove(s); + } + } + if (sharks.size() < 2) return iter; + } + + return -1; + } + + private Position nextPosition(int s) { + Shark shark = sharks.get(s); + Position p = shark.position; + ArrayList next = shark.next.get(shark.direction-1); + boolean flag = false; + for (int d : next) { + shark.direction = d; + if (d == 1 && p.y>0) { + if (tile.get(new Position(p.x, p.y-1))!= null) continue; + p.y--; + flag = true; break; + } else if (d==2 && p.y0) { + if (tile.get(new Position(p.x-1, p.y))!= null) continue; + p.x--; + flag = true; break; + } else if (d==4 && p.x 0) { + if (tile.get(new Position(p.x, p.y-1)).shark != s) continue; + p.y--; + flag = true; break; + } else if (d == 2 && p.y < N - 1) { + if (tile.get(new Position(p.x, p.y+1)).shark != s) continue; + p.y++; + flag = true; break; + } else if (d == 3 && p.x > 0) { + if (tile.get(new Position(p.x-1, p.y)).shark != s) continue; + p.x--; + flag = true; break; + } else if (d == 4 && p.x < N - 1) { + if (tile.get(new Position(p.x+1, p.y)).shark != s) continue; + p.x++; + flag = true; break; + } + } + } + if (!flag) System.out.println("ERROR"); + shark.position = p; + sharks.put(s, shark); + return new Position(p.x,p.y); + } + + private void readFromFile(String filename) { + File file = new File(filename); + + try { + FileReader reader = new FileReader(file); + BufferedReader breader = new BufferedReader(reader); + + String line = breader.readLine(); + String[] temp = line.split(" "); + N = Integer.parseInt(temp[0]); + M = Integer.parseInt(temp[1]); + k = Integer.parseInt(temp[2]); + + for (int i=0; i dir = new ArrayList<>(); + for (String t : temp) { + dir.add(Integer.parseInt(t)); + } + s.next.add(dir); + } + sharks.put(i, s); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void printTiles() { + int[][] tiles = new int[N][N]; + tile.forEach((Position p, State s) -> { + tiles[p.y][p.x] = s.shark*10+s.smell; + }); + sharks.forEach((Integer n, Shark s) -> { + tiles[s.position.y][s.position.x] = s.num*10+s.direction; + }); + System.out.println("-----------------------------------------------"); + for (int i=0; i> next; + + Shark(int i, Position p) { + num = i; + position = p; + next = new ArrayList<>(); + } +} + +public class Position extends Object { + public int x; + public int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass()!=o.getClass()) return false; + Position p = (Position) o; + return (x==p.x)&&(y==p.y); + } + + @Override + public int hashCode() { + return x*100+y; + } +} + +public class State { + int shark; + int smell; + + State(int shark, int smell) { + this.shark = shark; + this.smell = smell; + } + + public boolean next() { + this.smell--; + if (smell > 0) { + return true; + } + return false; + } + + public String toString() { + return shark+" "+smell; + } +} +``` + + +### 그 외 참고사항 +HashMap에서 Key를 찾을 때에, +HashMap에 있는 key와 주어진 key를 비교할 때, +hashcode()를 불러 먼저 비교하고, equals() 함수를 불러 true이면 같다고 판단한다. diff --git a/source/diane/[baekjoon]1647.md b/source/diane/[baekjoon]1647.md new file mode 100644 index 0000000..2264ded --- /dev/null +++ b/source/diane/[baekjoon]1647.md @@ -0,0 +1,148 @@ +# Baekjoon 도시 분할 계획 문제 + +- 도시 분할 계획 + - https://www.acmicpc.net/problem/1647 + +### 문제 이해하기 +N개의 집과 이 집들 사이를 잇는 M개의 길이 있는 마을이 있다. +이 길은 양쪽 방향 모두로 다닐 수 있고, 길을 유지하는데 드는 유지비가 있다. +이 마을을 두 개의 분리하려고 한다. +각 마을에 있는 집들은 모두 연결되어야 하는데, 유지비가 최소가 되게끔만 길을 남기려고 한다. +두 마을은 서로 다른 마을과는 연결되지 않는다. +이 때 이 마을들의 길 유지비의 최솟값을 구하라. + +#### 입력 +- int N, M : 집의 개수, 길의 개수 + - N : integer, such that 2 <= N <= 100,000 + - M : integer, such that 1 <= M <= 1,000,000 +- int 3개 * M : 길이 연결하는 집의 번호 두 개, 그 길의 유지비 + - 길의 유지비 C : integer, such that 1 <= C <= 1,000 + +#### 출력 +- int : 남은 길의 유지비의 합의 최솟값 + +### 문제 접근 방법 +길의 유지비를 최소로 하도록 마을을 둘로 나누는 것은, +기존 마을을 MST로 만든 다음 가장 유지비가 큰 길을 없애서 나누면 원하는 조건대로 만들 수 있다. + +길의 갯수가 주어지고, Prim 알고리즘으로 풀기에는 각 집을 지나는 길을 찾는데 비용이 들어, +이보다는 Kruskal로 푸는 것이 유리하다고 판단했다. + +먼저 모든 길을 유지비의 오름차순으로 정렬한다. +유지비가 적은 길부터 차례대로, 길의 양쪽 집이 이미 연결되어 있는지 확인하고, 연결되어 있지 않으면 이 길을 선택한다. +선택한 길이 N-1개라는 것은 MST를 만들었다는 것이기 때문에, +이 때는 마지막으로 찾은 길을 포함하지 않고 (마을을 둘로 만들어야 하니까) 지금까지 선택한 길의 유지비의 합을 출력한다. + +길의 양쪽 집이 이미 연결되어 있는지를 확인할 때는, +union-find 알고리즘을 사용하였다. + + +### 구현 배경 지식 +- MST (Kruskal Algorithm) +- Union-Find + +### 문제를 해결한 코드 +~~~java +import java.util.*; + +public class Main { + private static int N; + private static int M; + private static int[] disjointSet; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + N = sc.nextInt(); // vertex + M = sc.nextInt(); // edge + + ArrayList paths = new ArrayList<>(); + for (int i=0; i { + public int house1; + public int house2; + public int cost; + + Path(int h1, int h2, int c) { + house1 = h1; + house2 = h2; + cost = c; + } + + @Override + public int compareTo(Path b) { + return this.cost - b.cost; + } +} +~~~ + +### 새로 알게 된 내용 +ArrayList의 remove 함수가 굉장히 시간적 cost가 큰 로직이라는 것을 새로 알게 되었다. +초기에는 길을 선택할 때, 유지비로 정렬한 다음 리스트의 가장 첫 원소를 가져오고 삭제하고 이를 반복했는데, +이 때는 시간 초과가 발생한다. +이 후 길의 리스트를 돌면서 get으로 길을 가져오도록 변경하자, 알고리즘은 동일하지만, 시간 초과 없이 성공했다. +이는 두 함수의 로직 차이에서 기인한다. +remove 함수는 그 원소를 제거한 후에 그 뒤의 원소를 모두 한 칸 씩 앞으로 새로 저장하기 때문에 눈에 띄게 오래 걸리는 것이다. +```java + public E get(int index) { + rangeCheck(index); + + return elementData(index); + } + + public E remove(int index) { + rangeCheck(index); + + modCount++; + E oldValue = elementData(index); + + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + + return oldValue; + } +``` \ No newline at end of file diff --git a/source/diane/[baekjoon]1931.md b/source/diane/[baekjoon]1931.md new file mode 100644 index 0000000..0c9cc0d --- /dev/null +++ b/source/diane/[baekjoon]1931.md @@ -0,0 +1,84 @@ +# Baekjoon 회의실 배정 + +- 회의실 배정 + - https://www.acmicpc.net/problem/1931 + + +### 문제 이해하기 +N개의 회의가 있는데, 회의실은 하나이다. 각 회의에 대해 시작시간과 끝나는 시간이 주어지고, +회의가 겹치지 않게 회의실을 사용할 때 할 수 있는 회의의 최대 개수를 찾아라. +회의는 중단되지 않으며, 회의가 끝남과 동시에 다른 회의가 시작될 수 있다. + + +#### 입력 +- int N : 회의의 개수 +- int Start End : N번 반복되며, 각 회의의 시작시간과 끝시간 + + +#### 출력 +- int : 할 수 있는 회의의 최대 개수 + + +### 문제 접근 방법 +회의실에서 어떤 회의가 진행된다면, 그 회의가 빨리 끝날수록 그 회의실에서 더 많은 회의를 할 가능성이 증가한다. +따라서 지금 회의실에서 할 수 있는 회의 중 가장 빨리 끝나는 회의를 해야 한다. + + +### 구현 배경 지식 +- Greedy Algorithm + + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; +import java.util.Collections; +import java.util.Scanner; + +public class Main { + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + ArrayList meetings = new ArrayList<>(); + + int count = sc.nextInt(); + for (int i=0; i= time) { + number++; + time = meeting.end; + } + } + + System.out.println(number); + } +} + +class Meeting implements Comparable { + public int start; + public int end; + + public Meeting(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public int compareTo(Meeting m) { + if (end == m.end) + return start - m.start; + return end - m.end; + } + + @Override + public String toString() { + return "Meeting{" + start + "~" + end + '}'; + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]2042.md b/source/diane/[baekjoon]2042.md new file mode 100644 index 0000000..5ca7ae0 --- /dev/null +++ b/source/diane/[baekjoon]2042.md @@ -0,0 +1,116 @@ +# Baekjoon 구간 합 구하기 + +- 구간 합 구하기 + - https://www.acmicpc.net/problem/2042 + +### 문제 이해하기 +N개의 숫자가 주어져 있다. 이 N개의 숫자를 가지고, 이 중 특정한 수의 값을 바꾸거나, 이 중 특정 구간의 수들의 합을 구하는 작업을 하려고 한다. +수의 값을 바꾸고, 구간의 합을 구하는 작업이 들어올 때, 이를 작업이 들어올 때마다 실행하며, 합을 구하면 그 값을 반환해라. + +#### 입력 +- int N M K : 주어진 숫자의 갯수, 수의 변경이 일어나는 횟수, 구간의 합을 구하는 횟수 +- long NUM : N번 반복하면서 주어진 숫자를 입력 +- int A B (long) C : (M+K)번 반복 + - A==1 : 수의 변경, B번째 수를 C의 값으로 바꾸어야 한다. + - A==2 : 구간의 합, B번째 수부터 C번째 수까지의 합을 구하고 출력한다. + +#### 출력 +- int : K번 반복하면서, 구간의 합을 출력 + +### 문제 접근 방법 +주어진 숫자 배열로 먼저 segment tree를 만든다. +이후로는 숫자의 값을 바꿀 때는 tree의 root부터 값의 변화량을 적용시키면서, 해당 index의 leaf의 값까지 변화시킨다. +구간의 합을 구할 때는 tree의 root부터 검색하면서, 원하는 구간에 해당하는 node의 값을 더하여 합을 구한다. + + +### 구현 배경 지식 +- Segment Tree + +주어진 숫자의 갯수가 N, 수의 변경이 M번, 구간 합을 K번 구한다고 가정하자. + +| 작업 | 일반 | segment tree | +|-----|:----:|:------------:| +|값 변경하기|O(1)|O(logN)| +|구간 합 구하기|O(N)|O(logN)| +|모든 작업|O(NK)|O((M+K)logN)| + +작업의 수(K)가 많을수록, 주어진 숫자의 갯수(N)이 많을수록, segment tree가 유리하다. + + +### 문제를 해결한 코드 +~~~java +import java.util.Scanner; + +public class Main { + private static long[] array; + private static long[] segTree; + + public static void main(String args[]) throws Exception { + Scanner sc = new Scanner(System.in); + + int N = sc.nextInt(); + int M = sc.nextInt(); + int K = sc.nextInt(); + + array = new long[N]; + for (int i=0; i end) return; + + segTree[node] += diff; + if (start == end) return; + int mid = (start+end)/2; + updateSegTree(index, diff, node*2, start, mid); + updateSegTree(index, diff, node*2+1, mid+1, end); + } + + private static long sumSegTree(int node, int start, int end, int left, int right) { + if (left > end || right < start) return 0; + if (left <= start && end <= right) return segTree[node]; + int mid = (start+end)/2; + return sumSegTree(node*2, start, mid, left, right) + sumSegTree(node*2+1, mid+1, end, left, right); + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]2583.md b/source/diane/[baekjoon]2583.md new file mode 100644 index 0000000..e377d1b --- /dev/null +++ b/source/diane/[baekjoon]2583.md @@ -0,0 +1,121 @@ +# Baekjoon 영역 구하기 문제 + +- 영역 구하기 + - https://www.acmicpc.net/problem/2583 + +### 문제 이해하기 +MxN 크기의 모눈종이가 있다. +이 모눈종이에 눈금을 맞추어 k개의 직사각형을 그리면, +모눈종이가 이 직사각형의 내부를 제외한 나머지 부분으로 나뉘게 된다. +이 때 만들어진 나머지 영역이 몇 개이고, 그 넓이는 각각 얼마인지 구하라. + +#### 입력 +- int M, N, K : 모눈종이의 세로 길이, 가로 길이, 그려진 직사각형의 개수 + - 모두 100 이하의 자연수 +- int 4개 * k : k개의 직사각형의 왼쪽 아래 꼭지점의 좌표와 오른쪽 위 꼭지점의 좌표 + +#### 출력 +- int : 분리된 영역의 개수 +- int 여러 개 : 각 영역의 넓이 (띄어쓰기로 구분) + +### 문제 접근 방법 +모눈종이의 각 칸은 아래 두가지로 구분한다. +- visited = true : 직사각형의 내부로, 문제에서 필요 없는 부분이다. + 또한, 이미 검사한 나머지 부분으로, 이미 검사했으니 다시 검사할 필요가 없는 부분도 이렇게 나타낸다. +- visited = false : 직사각형에 의해 생긴 나머지 부분으로, 앞으로 검사해야 할 부분이다. + +문제를 해결하기 위해 사용한 방법은 아래와 같다. +1. 모눈종이에서 아직 검사하지 않은 나머지 부분을 찾는다. +2. 찾은 나머지 부분이 포함된 나머지 영역을 찾고, 그 넓이를 구한다. + - 검사하지 않은 나머지 부분과 연결된 검사하지 않은 나머지 부분을 찾는다. + - 새로 찾은 검사하지 않은 나머지 부분에 대하여 이를 반복한다. + - 더 이상 연결된 검사하지 않은 나머지 부분이 없다면, 이 부분이 포함된 영역은 모두 찾은 것이기 때문에 넓이를 기록하고 종료한다. +3. 1-2번을 더 이상 검사하지 않은 나머지 부분이 없을 때까지 반복한다. + + +### 구현 배경 지식 +BFS + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Scanner; + +public class Main { + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + int m = sc.nextInt(); + int n = sc.nextInt(); + int k = sc.nextInt(); + boolean[][] matrix = new boolean[n][m]; + ArrayList width = new ArrayList<>(); + + for (int c=0; c queue = new ArrayList<>(); + for (int i=0; i x).toArray(); + int x = position[0]; + int y = position[1]; + if (matrix[x][y]) continue; + + matrix[x][y] = true; + count++; + + if (x-1>=0 && !matrix[x-1][y]) queue.add(new Integer[] {x-1,y}); + if (x+1=0 && !matrix[x][y-1]) queue.add(new Integer[] {x,y-1}); + if (y+1 cups; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + int sugar = sc.nextInt(); + + cups = new HashMap<>(); + cups.put(3,1); + cups.put(5,1); + cups.put(1,-1); + cups.put(2,-1); + cups.put(4,-1); + cups.put(6,2); + + System.out.println(calculate(sugar)); + } + + private static int calculate(int sugar) { + if (cups.containsKey(sugar)) return cups.get(sugar); + + int cup = -1; + int cup1 = calculate(sugar-5); + int cup2 = calculate(sugar-3); + + if (cup1 != -1) { + if (cup2 != -1) { + cup = (cup1 > cup2? cup2:cup1)+1; + } else { + cup = cup1+1; + } + } else if (cup2 != -1) { + cup = cup2+1; + } + + cups.put(sugar, cup); + + return cup; + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]4256.md b/source/diane/[baekjoon]4256.md new file mode 100644 index 0000000..503437e --- /dev/null +++ b/source/diane/[baekjoon]4256.md @@ -0,0 +1,135 @@ +# Baekjoon 트리 문제 + +- 트리 + - https://www.acmicpc.net/problem/4256 + +### 문제 이해하기 +어떤 이진 트리가 주어진다. 이 이진 트리는 노드 n개로 이루어져있고, 각 노드는 1부터 n까지의 유일한 번호를 가지고 있다. +이 이진 트리를 전위 순회(preorder)한 결과와 중위 순회(inorder)한 결과가 주어졌을 때, +후위 순회(postorder)한 결과를 구하여라. + +#### 입력 +- int T : 테스트케이스 개수 + - 이 아래 입력은 테스트케이스 개수만큼 반복됨 +- int n : 이진 트리의 노드 갯수 +- int o1, o2, ... , on : 이진 트리를 전위 순회한 결과 +- int o1, o2, ... , on : 이진 트리를 중위 순회한 결과 + +#### 출력 +- int o1, o2, ... , on : 이진 트리를 후위 순회한 결과 (테스트케이스 개수 만큼) + +### 문제 접근 방법 +주어진 전위 순회 결과와 중위 순회 결과를 가지고 이진 트리를 복원한 다음 이 이진 트리를 후위 순회한 결과를 출력한다. + +먼저, 전위 순회 결과와 중위 순회 결과로 이진 트리를 복원해야 한다. +전위 순회는 root 노드를 먼저 출력하고, 그 노드의 왼쪽 자식 노드로 가 전위 순회하고, 이후에 오른쪽 자식 노드로 가 전위 순회한다. +따라서 전위 순회 결과는 언제나 (root 노드) (그 노드의 왼쪽 자식 노드가 root인 트리를 전위 순회한 결과) (그 노드의 오른쪽 자식 노드가 root인 트리를 전위 순회한 결과)이다. +중위 순회는 왼쪽 자식 노드를 중위 순회한 결과를 출력하고, 그 노드를 출력한 다음, 오른쪽 자식 노드를 중위 순회한 결과를 출력한다. +따라서 중위 순회 결과는 언제나 (그 노드의 왼쪽 자식 노드가 root인 트리를 중위 순회한 결과) (root 노드) (그 노드의 오른쪽 자식 노드가 root인 트리를 중위 순회한 결과)이다. + +이진 트리를 복원할 때는 분할 정복을 사용한다. +전위 순회 결과를 통해 트리의 root 노드를 찾은 다음, 그 root 노드를 중위 순회 결과에서 찾으면, 그 노드를 중심으로 왼쪽에 위치한 모든 노드와 오른쪽에 위치한 모든 노드를 찾을 수 있다. +전위 순회 결과에서 root 노드 이후에는 그 노드를 중심으로 왼쪽에 위치한 모든 노드가 나오고 그 뒤에 오른쪽에 위치한 모든 노드가 나온다. +따라서 중위 순위 결과에서 찾을 수 있는 왼쪽에 위치한 모든 노드를 대상으로, 왼쪽의 이진 트리를 복원하고, +그 후에 오른쪽에 위치한 모든 노드를 대상으로 오른쪽의 이진 트리를 복원하는 과정을 반복할 것이다. +즉 root 노드를 찾은 후에, 그 root 노드의 왼쪽 자식을 root로 하는 이진 트리를 만들고, 오른쪽 자식을 root로 하는 이진 트리를 만드는 것이다. +전체의 이진 트리를 만드는 것이 기본 문제(problem)이고, root 노드의 왼쪽 또는 오른쪽 자식 노드를 root로 하는 이진 트리를 만드는 것이 하위 문제(sub problem)인 것이다. + +이 과정에서 전위 순회 결과는 앞에서부터 순서대로 출력되므로, queue를 사용하기에 알맞으며, +중위 순회 결과는 하위 문제로 갈 때, root 노드를 기준으로 왼쪽 부분과 오른쪽 부분으로 분할되게 된다. + +이진 트리가 전부 만들어지면, 이제 이를 후위 순회하며 그 결과를 출력한다. +후위 순회는 root 노드에서 먼저 왼쪽 자식 노드를 root로 하는 트리에서 후위 순회를 하고, 이후에 오른쪽 자식 노드를 root로 하는 트리에서 후위 순회를 한 다음에 root 노드를 출력한다. + + + +### 구현 배경 지식 +- 트리와 이진 트리 +- 이진 트리의 순회 방법 + - preorder : root -> left -> right + - inorder : left -> root -> right + - postorder : left -> right -> root + +- 분할 정복 divide and conquer + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Scanner; + +public class Main { + + private static Queue preorder; + private static ArrayList inorder; + private static int size; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + int testcase = sc.nextInt(); + + for (int t=0; t(); + inorder = new ArrayList<>(); + for (int i=0; i in1) { + Node left = makeTree(in1, index); + node.left = left; + } + if (index < in2-1) { + Node right = makeTree(index+1, in2); + node.right = right; + } + + return node; + } + + private static void printPostOrder(Node n) { + if (n == null) return; + if (n.left != null) { + printPostOrder(n.left); + } + if (n.right != null) { + printPostOrder(n.right); + } + System.out.print(n.index+" "); + } +} + +class Node { + public Node left; + public Node right; + public int index; + + public Node(int n) { + index = n; left = null; right = null; + } + + @Override + public String toString() { + int l = (left==null)? 0 : left.index; + int r = (right==null)? 0 : right.index; + return "Node{" + index+" : "+l+" "+r+"}"; + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[baekjoon]6549-2.md b/source/diane/[baekjoon]6549-2.md new file mode 100644 index 0000000..cc775fb --- /dev/null +++ b/source/diane/[baekjoon]6549-2.md @@ -0,0 +1,181 @@ +# Baekjoon 히스토그램에서 가장 큰 직사각형 + +- 히스토그램에서 가장 큰 직사각형 + - https://www.acmicpc.net/problem/6549 + +### 문제 이해하기 +히스토그램은 직사각형 여러 개가 아래쪽으로 정렬되어 있는 도형으로, 이 직사각형은 모두 같은 너비를 가지고 있지만 높이는 서로 다르다. +이 때 이 히스토그램에서 그릴 수 있는 직사각형 중 가장 넓이가 큰 직사각형의 넓이를 구하여라. + +#### 입력 +- int N A1 A2 ... AN : 직사각형의 수와 높이 (여러 벌) + - N : 직사각형의 갯수 + - A1 ... AN : 직사각형의 높이 +- 0 : 입력의 끝 + +#### 출력 +- int : 히스토그램에서 가장 넓이가 큰 직사각형의 넓이 (입력의 수 만큼) + +### 문제 접근 방법 +먼저 주어진 히스토그램으로 segment tree를 만든다. +이 때 각 노드는 히스토그램에서 그 노드의 구간만을 보았을 때, 그 너비로 만들 수 있는 가장 큰 직사각형의 정보를 담을 것이다. +따라서, 그 너비(number)와 가장 큰 직사각형의 높이(height), 그리고 이 높이를 가지는 직사각형의 인덱스(index) 정보를 가진다. +두 노드의 부모 노드는, 이 두 노드가 가진 구간에서 그 너비로 만들 수 있는 가장 큰 직사각형의 정보를 담아야 하기 때문에, +너비는 자식 노드 둘의 너비의 합이고, 높이는 두 자식 노드가 가진 높이 중에 작은 것, 인덱스는 더 작은 높이를 가진 자식 노드가 가졌던 인덱스를 가진다. + +segment tree가 다 만들어지면, 이를 이용해서 전체 히스토그램에서의 가장 큰 직사각형을 찾을 것이다. +가장 간단한 방법은 완전 탐색이지만, 이는 O(N^2)의 시간 복잡도를 가지므로 주어진 시간 안에 찾지 못할 수도 있다. +따라서 Divide and Conquer 방식을 사용한다. +먼저 전체 너비로 만들 수 있는 가장 큰 직사각형의 넓이를 구한다. +이 직사각형의 높이는 이 너비에 있는 각 직사각형의 높이 중 가장 작은 것이다. +이 가장 작은 직사각형을 중심으로 그 왼쪽 구간과 오른쪽 구간으로 나누어 그 넓이를 구하고, +또 이 때 가장 작은 직사각형을 찾아 구간을 나누어 넓이를 구하는 과정을 반복한다. +가장 작은 높이가 포함되어 있다면, 현재 구한 너비가 가장 큰 너비이기 때문에, 이 높이의 직사각형을 제하고 양 옆으로 구간을 나누는 것이다. +이렇게 구한 직사각형의 넓이 중 가장 큰 것이, 전체 히스토그램에서 그릴 수 있는 가장 넓이가 큰 직사각형이다. + + +### 구현 배경 지식 +- Segment Tree + Divide and Conquer + +### 문제를 해결한 코드 +~~~java +import java.util.Scanner; + +public class Main { + private static long[] array; + private static Node[] segTree; + + public static void main(String args[]) { + Scanner sc = new Scanner(System.in); + String testCase = null; + + while (true) { + testCase = sc.nextLine(); + if (testCase.equals("0")) break; + + array = convert(testCase); + System.out.println(solution()); + } + + } + + private static long solution() { + segTree = makeSegTree(); + init(1,0,array.length-1); + +/* 완전 탐색으로 접근하여 시간 초과가 난 풀이 + int maxArea = Integer.MIN_VALUE; + for (int i=array.length-1; i>0; i--) { + for (int j=0; j maxArea) maxArea = area; + } + } +*/ + return getBiggerArea(0, array.length-1); + } + + private static Node[] makeSegTree() { + double height = Math.ceil(Math.log(array.length)/Math.log(2.0)); + int treeSize = (int) Math.pow(2.0, height+1); + return new Node[treeSize]; + } + + private static Node init(int node, int start, int end) { + if (start == end) return segTree[node] = new Node(array[start], 1, start); + int mid = (start+end)/2; + Node n1 = init(node*2, start, mid); + Node n2 = init(node*2+1, mid+1, end); + long height = n1.height; + int index = n1.index; + if (n1.height > n2.height) { + height = n2.height; index = n2.index; + } + return segTree[node] = new Node(height, n1.number+n2.number, index); + } + + private static long getBiggerArea(int start, int end) { + Node node = findArea(1,0, array.length-1, start, end); + long area = node.area; + if (node.index == -1) return area; + if (start < node.index) { + long temp = getBiggerArea(start, node.index-1); + if (temp > area) area = temp; + } + if (end > node.index) { + long temp = getBiggerArea(node.index+1, end); + if (temp > area) area = temp; + } + return area; + } + + private static Node findArea(int node, int start, int end, int left, int right) { + if (left > end || right < start) return new Node(0,0, -1); + if (left <= start && end <= right) { + return segTree[node]==null? new Node(0,0, -1):segTree[node]; + } + int mid = (start+end)/2; + Node n1 = findArea(node*2, start, mid, left, right); + Node n2 = findArea(node*2+1, mid+1, end, left, right); + long height = n1.height; + int index = n1.index; + if (n1.height == 0) { + if (n2.height == 0) { + height = 0; + if (n1.index == -1) index = n2.index; + else if (n2.index == -1) index = n1.index; + else index = (n1.index>n2.index)?n2.index:n1.index; + } else if (n1.index == -1){ + height = n2.height; index = n2.index; + } else { + height = n1.height; index = n1.index; + } + } else if (n2.index == -1) { + height = n1.height; index = n1.index; + } else if (n2.height == 0) { + height = n2.height; index = n2.index; + } else if (n1.height > n2.height){ + height = n2.height; index = n2.index; + } + return new Node(height, n1.number+n2.number, index); + } + + private static long[] convert(String array) { + String[] temp = array.split(" "); + long[] result = new long[temp.length-1]; + for (int i=1; i stack = new Stack<>(); // stack에는 현재보다 작은 것들만 남아있을 것 + int size = array.length; + + long area = Long.MIN_VALUE; + for (int i=0; iheight) { + Element oldElm = stack.pop(); + int width = (stack.empty()? i: i-stack.peek().index-1); + long newArea = width*oldElm.height; + if (area < newArea) area = newArea; + } + stack.push(new Element(height, i)); + } + + while(!stack.empty()) { + Element temp = stack.pop(); + int width = (stack.empty()? size: size-stack.peek().index-1); + long newArea = width*temp.height; + if (area < newArea) area = newArea; + } + + System.out.println(area); + } + + } + + private static long[] convert(String array) { + String[] temp = array.split(" "); + long[] result = new long[temp.length-1]; + for (int i=1; i> networks = new ArrayList<>(); + + while (checkFlag()) { + int start = getFirstRemainder(); + System.out.println(start); + Set network = dfs(start); + networks.add(network); + } + return networks.size(); + } + + private boolean checkFlag() { + boolean result = true; + int idx = 0; + while (result) { + if (idx >= flags.length) break; + result = result && flags[idx]; + idx++; + } + return !result; + } + + private int getFirstRemainder() { + for (int i=0; i dfs(int start) { + Set network = new HashSet<>(); + + flags[start] = true; + for (int i=0; i> disks; + private MinHeap remainings; + + public Solution() { + disks = new HashMap<>(); + remainings = new MinHeap(); + } + + public int solution(int[][] jobs) { + int time = 0; + int runtime = 0; + int number = jobs.length; + + for (int[] disk : jobs) { + ArrayList list = new ArrayList<>(); + if (disks.containsKey(disk[0])) { + list = disks.get(disk[0]); + } + list.add(Arrays.stream(disk).boxed().toArray(Integer[]::new)); + disks.put(disk[0], list); + } + + ArrayList times = new ArrayList<>(disks.keySet()); + Collections.sort(times); + while (times.size() > 0 || remainings.size() > 0) { + // 이 시간에 사용할 수 있는 disk 넣 + while (times.size() > 0 && times.get(0) <= time) { + for (Integer[] item : disks.get(times.get(0))) { + remainings.insert(item); + } + times.remove(0); + if (times.size() < 1) break; + } + + // 만약 이 시간에 할 수 있는 것이 없다면 + if (remainings.size()<1) { + time++; + continue; + } + + Integer[] item = remainings.delete(); + time = time + item[1]; + runtime = runtime + (time-item[0]); + } + + System.out.println("Total Time : "+time); + runtime = runtime / number; + + return runtime; + } +} + +class MinHeap { + private ArrayList heap; + + public MinHeap() { + heap = new ArrayList<>(); + heap.add(new Integer[] {0,0}); // index 0 added (not use index 0) + } + + public void insert(Integer[] v) { +// Integer[] value = Arrays.stream(v).boxed().toArray(Integer[]::new); + heap.add(v); + + int p = heap.size()-1; + while (p>1 && heap.get(p/2)[1]>heap.get(p)[1]) { + Integer[] temp = heap.get(p/2); + heap.set(p/2, heap.get(p)); + heap.set(p, temp); + p = p/2; + } + } + + public Integer[] delete() { + if (heap.size() < 2) { + return new Integer[] {0,0}; + } + + Integer[] deleteItem = heap.get(1); + heap.set(1, heap.get(heap.size()-1)); + heap.remove(heap.size()-1); + + int p = 1; + while ((p*2) < heap.size()) { + Integer[] min = heap.get(p*2); + int minp = p*2; + if ((minp+1 < heap.size()) && (min[1] > heap.get(minp+1)[1])) { + min = heap.get(minp+1); + minp = minp+1; + } + if (heap.get(p)[1] < min[1]) break; + + heap.set(minp, heap.get(p)); + heap.set(p, min); + p = minp; + } + + return deleteItem; + } + + public int size() { + return heap.size()-1; + } + + public String toString() { + String line = ""; + return line; + } +} +~~~ + diff --git a/source/diane/[programmers]42628.md b/source/diane/[programmers]42628.md new file mode 100644 index 0000000..5b61947 --- /dev/null +++ b/source/diane/[programmers]42628.md @@ -0,0 +1,85 @@ +# Programmers 이중우선순위큐 문제 + +- 이중우선순위큐 + - https://programmers.co.kr/learn/courses/30/lessons/42628 + +### 문제 이해하기 +이중우선순위큐는 연산의 목록을 받는다. +그 연산은 세 가지 종류로, 큐에 숫자를 추가하거나, 큐에서 최댓값 또는 최솟값을 제거한다. +이중우선순위큐가 이 연산을 모두 실행한 후의 큐의 최댓값과 최솟값을, 큐가 비어있다면 0,0을 반환한다. +모든 연산은 숫자 하나만을 대상으로 하고, 만약 큐가 비어있는데 값을 삭제해야한다면, 그 연산은 무시한다. + +#### 입력 +- String[] operations : 연산의 목록 + - 각 연산은 첫 글자로 알파벳 I 또는 D가 오고, 띄어쓰기 후 I라면 모든 숫자, D라면 1 또는 -1이 온다. + - 연산의 의미 + - I 숫자 : 큐에 이 숫자를 추가 + - D 1 : 큐에서 최댓값 삭제 + - D -1 : 큐에서 최솟값 삭제 + +#### 출력 +- int[] : 크기 2의 숫자 배열로, 첫 원소는 이 큐의 최댓값, 마지막 원소는 이 큐의 최소값. + - 만약 큐가 비어있다면, [0,0]을 반환. + +### 문제 접근 방법 +"이중우선순위큐"이지만, FIFO과는 관계가 없다. +또한 원소들의 순서는 전혀 관계없고, 그때그때 최솟값과 최댓값만 알면 되기 때문에, +자료구조로 minHeap과 maxHeap을 동시에 사용했다. + +모든 연산에 대해 순서대로 아래 행동을 반복한다. +- I 숫자 : minHeap과 maxHeap에 이 숫자를 추가한다. +- D 1 : maxHeap에서 최댓값을 제거하고, minHeap에서 동일한 값을 제거한다. +- D -1 : minHeap에서 최솟값을 제거하고, maxHeap에서 동일한 값을 제거한다. +모든 연산을 수행했다면, maxHeap에서 최댓값을, minHeap에서 최솟값을 찾아 반환한다. +만약 힙의 크기가 0이라면, 최댓값과 최솟값으로 0을 반환한다. + +### 구현 배경 지식 +MinHeap, MaxHeap + +### 문제를 해결한 코드 +~~~java +import java.util.Collections; +import java.util.PriorityQueue; + +public class Solution { + PriorityQueue minHeap; + PriorityQueue maxHeap; + + public Solution() { + minHeap = new PriorityQueue<>(); + maxHeap = new PriorityQueue<>(Collections.reverseOrder()); + } + + public int[] solution(String[] operations) throws Exception { + int[] answer = new int[2]; + + for (String operation : operations) { + String[] commands = operation.split(" "); + if (commands[0].equals("I")) { + int number = Integer.parseInt(commands[1]); + minHeap.add(number); + maxHeap.add(number); + } else if (commands[0].equals("D")) { + if (minHeap.size() < 1) continue; + if (commands[1].equals("1")) { + int number = maxHeap.poll(); + minHeap.remove(number); + } else if (commands[1].equals("-1")) { + int number = minHeap.poll(); + maxHeap.remove(number); + } else { + throw new Exception("Invalid operation : "+operation); + } + } else { + throw new Exception("Invalid operation : "+operation); + } + } + if (maxHeap.size() != 0) { + answer[0] = maxHeap.peek(); + answer[1] = minHeap.peek(); + } + + return answer; + } +} +~~~ \ No newline at end of file diff --git a/source/diane/[programmers]42862.md b/source/diane/[programmers]42862.md new file mode 100644 index 0000000..a07a970 --- /dev/null +++ b/source/diane/[programmers]42862.md @@ -0,0 +1,64 @@ +# Programmers 체육복 문제 + +- 체육복 + - https://programmers.co.kr/learn/courses/30/lessons/42862 + +### 문제 이해하기 +학생들은 기본으로 체육복을 하나씩 가지고 있다. 그런데 점심시간에 도둑이 들어, 일부 학생이 체육복을 도난 당했다. +따라서 여벌 체육복을 가진 학생들이 체육복이 없는 학생에게 체육복을 빌려주려고 한다. +여벌 체육복이 없는 학생은 자신의 체육복을 입어야 하기 때문에, 체육복을 빌려줄 수 없으며, 여벌의 체육복이 있었더라도 자신의 체육복을 도난 당했다면 자신의 여벌 체육복을 빌려줄 수 없다. +학생들은 모두 체격이 다르기 때문에, 체격 순으로 매겨진 번호로 자신의 바로 앞번호나 바로 뒷번호의 학생의 체육복만 입을 수 있다. +가능한 많은 학생들이 체육복을 입게 한다면, 총 몇 명의 학생이 체육복을 입을 수 있는지를 구하여라. + +#### 입력 +- int n : 전체 학생의 수 (번호는 1번부터 n번까지, 중복되지 않는다.) +- int[] lost : 체육복을 도난당한 학생들의 번호가 담긴 배열 (중복 없음) +- int[] reserve : 여벌의 체육복이 있는 학생들의 번호가 담긴 배열 (중복 없음) + +#### 출력 +- int : 체육복을 입을 수 있는 가장 많은 학생 수 + +### 문제 접근 방법 +체육복이 없는 학생의 번호를 작은 순부터 탐색하면서, +여벌의 체육복을 가진 학생이 앞번호에 있으면 그 학생에게, 없으면 그 뒷학생에게 체육복을 빌린다고 가정한다. +만약 양쪽 번호 모두 여벌의 체육복이 없으면 체육복이 없는 학생은 체육복을 빌리지 못한다. + +여벌의 체육복이 있더라도, 자신의 체육복을 도난당한 학생은 체육복을 빌려줄 수 없기 떄문에, +이 경우는 사전에 제거한다. + +### 구현 배경 지식 +Greedy Algorithm + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; + +public class Solution { + public int solution(int n, int[] lost, int[] reserve) { + int answer = n - lost.length; + ArrayList more = new ArrayList<>(); + ArrayList no = new ArrayList<>(); + + for (int i : reserve) more.add(i); + for (int i : lost) { + if (more.contains(i)) { + more.remove((Integer)i); + answer++; + } else { + no.add(i); + } + } + for (int i : no) { + if (more.contains(i-1)) { + more.remove((Integer)(i-1)); + answer++; + } else if (more.contains(i+1)) { + more.remove((Integer)(i+1)); + answer++; + } + } + + return answer; + } +} +~~~ diff --git a/source/diane/[programmers]43164.md b/source/diane/[programmers]43164.md new file mode 100644 index 0000000..975d8eb --- /dev/null +++ b/source/diane/[programmers]43164.md @@ -0,0 +1,109 @@ +# Programmers 여행경로 문제 + +- 여행경로 + - https://programmers.co.kr/learn/courses/30/lessons/43164 + +### 문제 이해하기 +출발 도시와 도착 도시가 고정된 항공권 목록이 주어진다. +이 항공권을 모두 이용하여 여행경로를 짜야한다. +첫 출발지는 "ICN"이며, 항공권을 모두 사용할 수 없는 경우는 주어지지 않는다. +만약 가능한 여행경로가 여러 개라면, 알파벳 순으로 앞서는 경로를 구한다. + +#### 입력 +- String[][] tickets : 항공권의 목록 + - 모든 ticket은 길이 2의 String 배열로 이루어져 있다 + - ticket의 첫번째 원소는 출발지, 두번째 원소는 도착지이다 + +#### 출력 +- String[] : "ICN"으로부터 시작하는 여행 경로의 도시 목록 + +### 문제 접근 방법 +모든 가능성을 전부 탐색하면서 항공권을 모두 사용하는 여행경로를 찾는다. +그 방법은 다음과 같다. +1. 현재 위치한 도시를 찾는다. (처음은 ICN) +2. 현재 위치한 도시에서 갈 수 있는 모든 도시를 찾는다. +3. 각 다음 도시에 대해 그 도시를 현재의 도시로 하여 이 전체 과정을 반복한다. 즉, 그 도시로 이동할 경우 가능한 여행경로 중 가장 좋은 경로를 찾는다. +4. 과정 3에서 찾은 각 다음 도시의 가장 좋은 여행경로 중 조건에 맞는 여행경로를 찾는다. + +여기서 조건이 맞다는 것은 아래와 같다. +1. 이 도시에서 시작하는 여행경로 중 가장 긴 경로일 것. +한 여행경로보다 더 긴 여행경로가 있다는 것은, 그 여행경로가 충분히 많은 도시를 여행하지 않았다는 뜻이다. +또한, 문제의 조건에서 모든 도시를 여행하지 못하는 경우는 없다고 했기 때문에 (즉, 그런 여행경로가 없는 경우를 판단할 필요가 없기 때문에), 모든 항공권을 다 사용한 여행경로는 필연적으로 가장 길 것이다. +2. 만약 여러 여행경로의 길이가 같다면, 이 중 알파벳 순으로 앞서는 것을 선택한다. + +### 구현 배경 지식 +DFS, Backtracking + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; +import java.util.HashMap; + +public class Solution { + + public String[] solution(String[][] tickets) { + // tickets 정보를 HashMap에 넣는다 + HashMap> list = new HashMap<>(); + for (String[] ticket : tickets) { + ArrayList dest = new ArrayList<>(); + if (list.containsKey(ticket[0])) { + dest = list.get(ticket[0]); + } + dest.add(ticket[1]); + list.put(ticket[0], dest); + } + + // ICN을 시작으로 여행경로 찾기 + ArrayList path = new ArrayList<>(); + path.add("ICN"); + path = findPath(path, list); + + return path.toArray(new String[path.size()]); + } + + private ArrayList findPath(ArrayList path, HashMap> list) { + ArrayList finalpath = path; + String depart = path.get(path.size()-1); + + // 이 다음으로 갈 수 있는 모든 목적지에 대해서 경로 탐색하기 + if (list.get(depart) == null) return path; + for (String dest : list.get(depart)) { + // 사용한 티켓을 제외한 list 만들기 + HashMap> newlist = new HashMap<>(list); + ArrayList temp = new ArrayList<>(list.get(depart)); + temp.remove(dest); + if (temp.size() == 0) { + newlist.remove(depart); + } else { + newlist.put(depart, temp); + } + + // 새로운 목적지로 가는 path 만들기 + temp = new ArrayList<>(path); + temp.add(dest); + + // 그 결과! + temp = findPath(temp, newlist); + finalpath = comparePath(temp, finalpath); + } + + return finalpath; + } + + private ArrayList comparePath(ArrayList path1, ArrayList path2) { + // 더 긴 경로가 정답일 것 + if (path1.size() < path2.size()) return path2; + if (path1.size() > path2.size()) return path1; + + // 길이가 같다면, 알파벳 순으로 먼저 오는 것이 정답일 것 + ArrayList answer = path1; + for (int i=0; i 0) answer = path2; + break; + } + return answer; + } +} +~~~ diff --git a/source/diane/[programmers]60059.md b/source/diane/[programmers]60059.md new file mode 100644 index 0000000..8204e04 --- /dev/null +++ b/source/diane/[programmers]60059.md @@ -0,0 +1,132 @@ +# Programmers 자물쇠와 열쇠 문제 + +- 자물쇠와 열쇠 + - https://programmers.co.kr/learn/courses/30/lessons/60059 + +### 문제 이해하기 +NxN 크기의 자물쇠와 MxM 크기의 열쇠가 있다. +자물쇠의 모든 홈에 열쇠의 돌기가 들어가고, 자물쇠의 돌기와 열쇠의 돌기가 만나지 않는 경우, 그 열쇠로 자물쇠를 열 수 있다. +이 때 열쇠는 회전하고 이동할 수 있다. +하나의 열쇠와 하나의 자물쇠가 주어질 때, 이 열쇠로 자물쇠를 열 수 있는지 여부를 반환하라. +M은 항상 N 이하이고, M과 N은 3 이상 20 이하인 자연수이다. + +#### 입력 +- int[][] key : 열쇠의 모양, 0은 홈 부분, 1은 돌기 부분. +- int[][] lock : 자물쇠의 모양, 0은 홈 부분, 1은 돌기 부분. + +#### 출력 +- boolean : 주어진 열쇠로 자물쇠를 열 수 있는지 여부 + +### 문제 접근 방법 +열쇠로 자물쇠를 열 수 있는 조건은 아래와 같다. +1. 자물쇠의 모든 홈에 열쇠의 돌기가 있을 것 +2. 자물쇠의 돌기와 열쇠의 돌기가 만나지 않을 것 + +자물쇠의 홈 부분이 없는 경우는 이미 자물쇠가 열린 상태이므로 true를 반환하고, 열쇠의 돌기의 수가 자물쇠의 홈의 수보다 작은 경우는 무슨 수를 써도 열쇠로 자물쇠를 열 수 없으므로 false를 반환한다. + +자물쇠의 홈 중에 하나를 선택하여, 열쇠의 모든 돌기를 차례대로 그 홈에 맞추어보고, 그 때 자물쇠의 모든 홈을 열쇠의 돌기로 채울 수 있는지 확인한다 (1번 조건). +만약 이 조건을 만족한다면, 이제 열쇠의 모든 돌기에 대해서 자물쇠의 홈에 들어가지 않고 자물쇠의 돌기와 만나는 부분이 없는지 확인한다(2번 조건). +두 조건을 모두 만족하면, 열쇠로 자물쇠를 열 수 있으므로 true를 반환하고, +그렇지 않다면 열쇠를 90도씩 돌려가며 위의 과정을 반복하여 자물쇠를 열어본다. +열쇠를 90도씩 돌려가며 모든 방향에 대해 확인했는데도 자물쇠를 열 수 없다면 false를 반환한다. + + +### 문제를 해결한 코드 +~~~java +import java.util.ArrayList; + +public class Solution { + private ArrayList locks; + private ArrayList keys; + + public boolean solution(int[][] key, int[][] lock) { + boolean answer = true; + + locks = makeList(lock, 0); + keys = makeList(key, 1); + + if (locks.size() == 0) return true; // 이미 열려있는 경우 + if (locks.size() > keys.size()) return false; // 답에 영향이 없네 + + // locks의 모든 것이 keys에 있어야 함 + int size = keys.size(); + for (int rotate=0; rotate<4; rotate++) { + for (int i = 0; i < size; i++) { + answer = true; + int xdiff = keys.get(i).x-locks.get(0).x; + int ydiff = keys.get(i).y-locks.get(0).y; + for (Pair l : locks) { + Pair temp = new Pair(l.x+xdiff, l.y+ydiff); + if (!keys.contains(temp)) { + answer = false; + break; + } + } + if (answer && NotExistConflict(lock.length, xdiff, ydiff)) { + return true; + } + } + keys = rotateRight(keys, key.length); + } + + return answer; + } + + private boolean NotExistConflict(int n, int xdiff, int ydiff) { + if (locks.size() == keys.size()) return true; + // key의 튀어나온 부분과 lock의 튀어나온 부분이 충돌하는 경우 + for (Pair key : keys) { + Pair temp = new Pair(key.x-xdiff, key.y-ydiff); + if (!locks.contains(temp)) { + if (temp.x >=0 && temp.x < n && temp.y >=0 && temp.y < n) + return false; + } + } + return true; + } + + private ArrayList makeList(int[][] rawData, int flag) { + ArrayList result = new ArrayList<>(); + int length = rawData.length; + for (int i=0; i rotateRight(ArrayList list, int length) { + ArrayList result = new ArrayList<>(); + for (Pair pair : list) { + result.add(new Pair(pair.y, length-pair.x)); + } + return result; + } + +} + +class Pair extends Object { + public int x; + public int y; + + public Pair(int x, int y) { + this.x = x; this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass()!=o.getClass()) return false; + Pair p = (Pair) o; + return (x==p.x)&&(y==p.y); + } + + @Override + public int hashCode() { + return x*100+y; + } +} +~~~ + diff --git a/source/eunbee/.DS_Store b/source/eunbee/.DS_Store new file mode 100644 index 0000000..ba488e3 Binary files /dev/null and b/source/eunbee/.DS_Store differ diff --git a/source/eunbee/.gitignore b/source/eunbee/.gitignore new file mode 100644 index 0000000..e10e727 --- /dev/null +++ b/source/eunbee/.gitignore @@ -0,0 +1 @@ +/.metadata/ diff --git a/source/eunbee/boj/.DS_Store b/source/eunbee/boj/.DS_Store new file mode 100644 index 0000000..4bf7b64 Binary files /dev/null and b/source/eunbee/boj/.DS_Store differ diff --git a/source/eunbee/boj/.classpath b/source/eunbee/boj/.classpath new file mode 100644 index 0000000..1cd9d95 --- /dev/null +++ b/source/eunbee/boj/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/source/eunbee/boj/.gitignore b/source/eunbee/boj/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/source/eunbee/boj/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/source/eunbee/boj/.project b/source/eunbee/boj/.project new file mode 100644 index 0000000..79a2957 --- /dev/null +++ b/source/eunbee/boj/.project @@ -0,0 +1,17 @@ + + + boj + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/source/eunbee/boj/.settings/org.eclipse.jdt.core.prefs b/source/eunbee/boj/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..910a770 --- /dev/null +++ b/source/eunbee/boj/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=14 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=14 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=14 diff --git a/source/eunbee/boj/src/.DS_Store b/source/eunbee/boj/src/.DS_Store new file mode 100644 index 0000000..478d1bc Binary files /dev/null and b/source/eunbee/boj/src/.DS_Store differ diff --git a/source/eunbee/boj/src/boj/boj_2583.java b/source/eunbee/boj/src/boj/boj_2583.java new file mode 100644 index 0000000..a9c1fec --- /dev/null +++ b/source/eunbee/boj/src/boj/boj_2583.java @@ -0,0 +1,85 @@ +package boj; +import java.util.*; + +public class boj_2583 { + public static int n, m, k; + public static int[][] graph = new int[100][100]; + //public static int[] size = new int[100]; + public static ArrayList size= new ArrayList(); + public static int check = 0; + + public static boolean dfs(int x, int y) { + if(x<0 || x >=m || y<0 || y >= n) { + return false; + } + if(graph[x][y] == 0) { + graph[x][y] = 1; + //상 + dfs(x-1,y); + //하 + dfs(x+1,y); + //좌 + dfs(x,y-1); + // + dfs(x,y+1); + check++; + return true; + } + + return false; + } + + public static void main(String[] args) { + // TODO Auto-generated method stub + Scanner sc = new Scanner(System.in); + m = sc.nextInt(); + n = sc.nextInt(); + k = sc.nextInt(); + sc.nextLine(); + + for(int i=0;i answerlist = new ArrayList(); + HashMap genrePlayHashMap = new HashMap(); + + for(int i =0; i key = genrePlayHashMap.keySet(); + HashMap playGenreHashMap = new HashMap(); + for(String k : key) playGenreHashMap.put(genrePlayHashMap.get(k), k); + + //플레이수 : 장르 오름차순 + TreeMap treeMap = new TreeMap(playGenreHashMap); + int treeMapSize = treeMap.size(); + for(int i =0; i8){ + answer = -1; + return; + } + if(number == prev){ + if(answer ==-1 || answer>cnt){ + answer = cnt; + return; + } + } + for(int i=0; i<8-cnt; i++){ + dfs(N, number, cnt+i+1, prev-nn); + dfs(N, number, cnt+i+1, prev+nn); + dfs(N, number, cnt+i+1, prev*nn); + dfs(N, number, cnt+i+1, prev/nn); + nn = nn*10+N; + } + } +} +``` + diff --git a/source/woodi/201023_Programmers_43238.md b/source/woodi/201023_Programmers_43238.md new file mode 100644 index 0000000..fecdb77 --- /dev/null +++ b/source/woodi/201023_Programmers_43238.md @@ -0,0 +1,46 @@ +# 프로그래머스 이분탐색문제 +- 입국심사 + - https://programmers.co.kr/learn/courses/30/lessons/43238 + +## 풀이 방법 +### 문제 이해하기 +- 입국심사를 기다리는 사람 수 n +- int[] times : 각 심사관이 한 명을 심사하는데 걸리는 시간이 담긴 배열 +- 가장 앞에 서 있는 사람은 비어 있는 심사대로 가서 심사를 받을 수 있습니다. 하지만 더 빨리 끝나는 심사대가 있으면 기다렸다가 그곳으로 가서 심사를 받을 수도 있습니다. +- 모든 사람이 심사를 받는데 걸리는 최소 시간을 구하시오. + + +### 문제 접근 방법 +- 정답을 가정(mid)하고 조건(n)과 비교한다. -> 가장 n과 유사한 mid가 정답 +- mid 시간 이내에 심사 가능한 인원의 수 -> sum(mid/times[i])=result, n과 result 비교, n== result이면 mid 값을 반환한다. +### 구현 배경 지식 +- 이분탐색 +### 접근 방법을 적용한 코드 +```java +import java.util.*; + +class Solution { + public long solution(int n, int[] times) { + long answer = 0; + Arrays.sort(times); + long left =1; + long right = (long)n*times[times.length-1]; // 가장 오래 걸리는 경우 + while(left<=right){ + long mid = (left+right)/2; + long sum=0; // mid 시간 이내에 심사 가능한 인원의 수 + for(int t : times){ + sum+= (mid/t); + } + if(sum 이후 d[lastidx] 중 가장 큰 값이 answer이다 + + for(int i=0; i li = new ArrayList<>(); + for(int i=0; i q = new LinkedList<>(); + q.add(begin);// begin을 담는다. + d[d.length-1] = 0; +// System.out.println("begin: "+begin); + while(!q.isEmpty()){ + String now = q.remove(); +// System.out.println("now: "+now); + if(now.equals(target)) { + answer = d[li.indexOf(now)]; + break; + } + for(int i=0; imap[a+1][i]) {// 경사로 : \ + boolean slopePossible=true; + for(int t=a+2; ta-l; t--) { + if(t<0 || map[a][i]!=map[t][i]) { + slopePossible = false; + } + } + if(!slopePossible) { + result = false; + break; + }else { + a++; + } + } + }else if(Math.abs(map[a][i]-map[a+1][i])==0){ + a++; + }else { + result = false; + break; + } + } + return result; + } + public static boolean checkRow(int i) { // map[i][0~n-1] + boolean result= true; + int a=0; + while(amap[i][a+1]) {// 경사로 : \ + boolean slopePossible=true; + for(int t=a+2; ta-l; t--) { + if(t<0 ||map[i][a]!=map[i][t]) { + slopePossible = false; + } + } + if(!slopePossible) { + result = false; + break; + }else { + a++; + } + } + }else if(Math.abs(map[a][i]-map[a+1][i])==0){ + a++; + }else { + result = false; + break; + } + } + return result; + } + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StringTokenizer st = new StringTokenizer(br.readLine()); + n = Integer.parseInt(st.nextToken()); + l = Integer.parseInt(st.nextToken()); + map = new int[n][n]; + for(int i=0; i +- 첫째 줄) N: 격자 크기, M: 상어 마리 수 , K: 냄새가 사라지는데 걸리는 시간 +- 둘째 줄~N개의 줄 ) 0은 빈칸, 그 외 0이 아닌 수 x는 x번 상어가 들어가 있는 칸 +- 그 다음 줄 ) 각 상어의 방향 (1: 위, 2: 아래, 3: 좌, 4: 우) +- 그 다음 줄~) 각 상어의 방향 우선 순위 (하나의 상어당 4줄(현재 방향), 각 줄은 4개로 이루어짐(현재 방향에 따른 우선순위)) +- <출력> +- 1번 상어만 격자에 남게 되기까지 걸리는 시간 출력. 단, 1000초를 넘어갈 경우 -1 출력 + +### 문제 접근 방법 +- +### 구현 배경 지식 +- 시뮬레이션 +### 접근 방법을 적용한 코드 +```java +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +public class AdultShark { + + static int[][][] priority; // 상어별 이동 우선순위 : 1(위), 2(아래), 3(좌), 4(우) + // priority[1] : 상어 1 + // priority[1][2] : 상어1의 현재 방향 (아래) + // priority[1][2][1] : 상어1의 현재 방향에서 첫번째 우선순위 + static int n; + static int m; + static int k; + static int[] di = {0, -1, 1, 0, 0}; + static int[] dj = {0, 0, 0, -1, 1}; + + public static boolean checkBound(int x, int y) { + if(x<0 || x>=n || y<0 || y>=n) { + return false; + } + return true; + } + + public static void move(int[][] map_smell, int[][] map_shark, int[] curr_direct, int[][] map_smell_own, int count_shark) { + int time = 0; + // count_shark + while(true) { + + // 1초 후 + time++; + // map_smell의 모든 숫자는 1씩 감소하고 +// for(int i=0; i0) +// map_smell[i][j]--; +// } +// } +// System.out.println("---------------------"); +// System.out.println(time); +// printMap(map_shark); +// System.out.println(); +// printSmell(map_smell); + // 모든 상어가 각자의 우선순위에 따라 움직임 + for(int i=0; i update curr_direct[shark_num] + if(checkBound(tempi, tempj) && map_smell[tempi][tempj]==0) { + curr_direct[shark_num] = next_direct; + flag = true; + break; + }else { // 그렇지 않으면 다음 p + continue; + } + } + // p를 다 돌았는데도 아무 냄새 없는 칸이 없어(flag==false) -> 자신의 냄새가 있는 칸으로 update curr_direct[shark_num] + + if(flag==false) { +// System.out.println("냄새 없는 칸이 없음. 자신의 냄새가 있는 곳 탐색"); + for(int p=1; p<=4; p++) { + int next_direct = priority[shark_num][curr_direct[shark_num]][p]; + int tempi = i+di[next_direct]; + int tempj = j+dj[next_direct]; + // tempi, tempj가 격자 밖으로 나가는지 검사하기 + // next_move 한 칸에 자신의 냄새가 있으면 -> update curr_direct[shark_num] + if(checkBound(tempi, tempj) && map_smell_own[tempi][tempj]==shark_num) { + curr_direct[shark_num] = next_direct; + flag = true; + break; + }else { // 그렇지 않으면 다음 p + continue; + } + } + } + } + } + } + // curr_direct에 따라 상어의 새로운 위치를 구하고 + + int[][] new_shark_map = new int[n][n]; + + for(int i=0; i 더 작은 숫자의 상어가 차지 한다. + int a = new_shark_map[ni][nj]; + if(shark0) + map_smell[i][j]--; + } + } + // 새로운 위치 중 겹치는 위치가 있으면 가장 숫자가 작은 상어가 차지한다. --> update map_shark + // (이때 사라지는 상어 수 만큼 count_shark-- 해준다.) + // 냄새를 뿌린다. --> update map_smell, map_smell_own + for(int i=0; i=1000) { + System.out.println(-1); + break; + } +// System.out.println(count_shark); + } + // while 문 끝남 -> 1번 샤크만 남았을 것 + } + public static int[][] copyMap(int[][] new_map_shark){ + int[][] map = new int[n][n]; + for(int i=0; i[] link에 담는다. +- link[i]에는 i번 컴퓨터와 연결된 다른 컴퓨터들의 번호가 ArrayList로 담긴다. +- BFS로 탐색한다. +### 구현 배경 지식 +- BFS +### 접근 방법을 적용한 코드 +```java +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +class Solution { + public int solution(int n, int[][] computers) { + int answer = 0; // network 개수 + ArrayList[] link = new ArrayList[n]; // 안해주면 object 형태로 저장 (generic) + for(int i=0; i(); + } + // 1. 연결된 컴퓨터 정보 담기 + for(int i=0; i q = new LinkedList<>(); + while(count!=n) { + System.out.println("count :"+count); + // 아직 방문 안한 노드 중 하나를 추가한다. + for(int i=0; i +- 첫째 줄) N: 격자 크기, M: 상어 마리 수 , K: 냄새가 사라지는데 걸리는 시간 +- 둘째 줄~N개의 줄 ) 0은 빈칸, 그 외 0이 아닌 수 x는 x번 상어가 들어가 있는 칸 +- 그 다음 줄 ) 각 상어의 방향 (1: 위, 2: 아래, 3: 좌, 4: 우) +- 그 다음 줄~) 각 상어의 방향 우선 순위 (하나의 상어당 4줄(현재 방향), 각 줄은 4개로 이루어짐(현재 방향에 따른 우선순위)) +- <출력> +- 1번 상어만 격자에 남게 되기까지 걸리는 시간 출력. 단, 1000초를 넘어갈 경우 -1 출력 + +### 문제 접근 방법 +- +### 구현 배경 지식 +- 시뮬레이션 +### 접근 방법을 적용한 코드 +```java +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +public class AdultShark { + + static int[][][] priority; // 상어별 이동 우선순위 : 1(위), 2(아래), 3(좌), 4(우) + // priority[1] : 상어 1 + // priority[1][2] : 상어1의 현재 방향 (아래) + // priority[1][2][1] : 상어1의 현재 방향에서 첫번째 우선순위 + static int n; + static int m; + static int k; + static int[] di = {0, -1, 1, 0, 0}; + static int[] dj = {0, 0, 0, -1, 1}; + + public static boolean checkBound(int x, int y) { + if(x<0 || x>=n || y<0 || y>=n) { + return false; + } + return true; + } + + public static void move(int[][] map_smell, int[][] map_shark, int[] curr_direct, int[][] map_smell_own, int count_shark) { + int time = 0; + // count_shark + while(true) { + + // 1초 후 + time++; + // map_smell의 모든 숫자는 1씩 감소하고 +// for(int i=0; i0) +// map_smell[i][j]--; +// } +// } +// System.out.println("---------------------"); +// System.out.println(time); +// printMap(map_shark); +// System.out.println(); +// printSmell(map_smell); + // 모든 상어가 각자의 우선순위에 따라 움직임 + for(int i=0; i update curr_direct[shark_num] + if(checkBound(tempi, tempj) && map_smell[tempi][tempj]==0) { + curr_direct[shark_num] = next_direct; + flag = true; + break; + }else { // 그렇지 않으면 다음 p + continue; + } + } + // p를 다 돌았는데도 아무 냄새 없는 칸이 없어(flag==false) -> 자신의 냄새가 있는 칸으로 update curr_direct[shark_num] + + if(flag==false) { +// System.out.println("냄새 없는 칸이 없음. 자신의 냄새가 있는 곳 탐색"); + for(int p=1; p<=4; p++) { + int next_direct = priority[shark_num][curr_direct[shark_num]][p]; + int tempi = i+di[next_direct]; + int tempj = j+dj[next_direct]; + // tempi, tempj가 격자 밖으로 나가는지 검사하기 + // next_move 한 칸에 자신의 냄새가 있으면 -> update curr_direct[shark_num] + if(checkBound(tempi, tempj) && map_smell_own[tempi][tempj]==shark_num) { + curr_direct[shark_num] = next_direct; + flag = true; + break; + }else { // 그렇지 않으면 다음 p + continue; + } + } + } + } + } + } + // curr_direct에 따라 상어의 새로운 위치를 구하고 + + int[][] new_shark_map = new int[n][n]; + + for(int i=0; i 더 작은 숫자의 상어가 차지 한다. + int a = new_shark_map[ni][nj]; + if(shark0) + map_smell[i][j]--; + } + } + // 새로운 위치 중 겹치는 위치가 있으면 가장 숫자가 작은 상어가 차지한다. --> update map_shark + // (이때 사라지는 상어 수 만큼 count_shark-- 해준다.) + // 냄새를 뿌린다. --> update map_smell, map_smell_own + for(int i=0; i=1000) { + System.out.println(-1); + break; + } +// System.out.println(count_shark); + } + // while 문 끝남 -> 1번 샤크만 남았을 것 + } + public static int[][] copyMap(int[][] new_map_shark){ + int[][] map = new int[n][n]; + for(int i=0; i=5) { + d[5]=1; + } + + for(int i=6; i<=n; i++) { + if(d[i-5]>0) { + d[i]=d[i-5]+1; + }else if(d[i-3]>0) { + d[i]=d[i-3]+1; + } + } + + int answer; + if(d[n]==0) { + answer =-1; + }else { + answer = d[n]; + } + System.out.println(answer); + } +} + +``` + diff --git a/source/woodi/201226_programmers_43164.md b/source/woodi/201226_programmers_43164.md new file mode 100644 index 0000000..54078d6 --- /dev/null +++ b/source/woodi/201226_programmers_43164.md @@ -0,0 +1,111 @@ +# 프로그래머스 dfs +- 여행경로 + - https://programmers.co.kr/learn/courses/30/lessons/43164 + +## 풀이 방법 +### 문제 이해하기 +- 주어진 항공권을 '모두' 이용하여 여행경로를 짤때, 이 경로를 출력해라 +- 항상 "ICN" 공항에서 출발한다. +- [입력] + - 항공권 정보가 담긴 2차원 배열 tickets +- [출력] + - 방문하는 공항 경로를 1차원 배열에 담아 출력 +- [추가 조건] + - 모든 공항은 알파벳 대문자 3글자 + - 주어진 공항 수는 3개 이상 10,000개 이하 + - tickets의 각 행 [a,b]는 a공항에서 b공항으로 가는 항공권이 있다는 뜻 + - 주어진 항공권은 모두 사용해야함 + - 가능한 경로가 2개 이상일 경우, 알파벳 순서가 앞서는 경로를 return + - 모든 도시를 방문할 수 없는 경우는 주어지지 않음 + +### 문제 접근 방법 +- +### 구현 배경 지식 +- DFS +### 접근 방법을 적용한 코드 +```java +package programmers; + +import java.util.*; + +public class bfsdfs_43164 { + static ArrayList candidates; + public static void dfs(String depart, HashMap> map,String[] path, int i) { + // 출발지, 출발지에서 직통으로 갈수있는 도시 map, 현재까지 경로, 현재까지 경로 인덱스 + + // 더 이상 갈 수 있는 경로(도시)가 없는 경우 + if(map.get(depart)==null || map.get(depart).size()==0) { + // 마지막 도시까지 전부 방문. 즉, path를 다 채움 + if(i==path.length-1) { + String[] newpath = new String[path.length]; // 경로를 값만 복사 (깊은 복사) + // 복사를 안하고 candidates에 담으면 추후 path가 바뀔때마다 저장된 값도 바뀜(주소값이 저장되므로) + for(int t=0; t li = map.get(depart); + i++; + + // for문 돌면서 remove하기 위해 임시로 복사해둠 + ArrayList templi = new ArrayList<>(); + templi.addAll(li); + + for(String next:templi) { + // 해당 ticket을 사용했을 경우, li에서 지워버린 후 map에 저장 -> dfs + li.remove(next); + map.put(depart, li); + path[i] = next; + dfs(next,map,path,i); + // 백트래킹 + li.add(next); + map.put(depart, li); + } + } + public static String[] solution(String[][] tickets) { + String[] answer = {}; + HashMap> map = new HashMap<>(); + // key: 출발지, value : 출발지에서 직통으로 갈 수 있는 도시들 + candidates = new ArrayList<>(); + for(int i=0; i()); + map.get(tickets[i][0]).add(tickets[i][1]); + } + } + + String[] path = new String[tickets.length+1]; // 경로 + path[0] = "ICN"; + dfs("ICN", map, path,0); // 가능한 경로를 모두 candidates에 담는다. + + // 알파벳 순으로 정렬 + Collections.sort(candidates, new Comparator(){ + @Override + public int compare(String[] o1, String[] o2) { + return Arrays.toString(o1).compareTo(Arrays.toString(o2)); + } + }); + // 가장 앞에 있는 것이 답이다. + answer = candidates.get(0); + return answer; + } + + public static void main(String[] args) { + String[][] tickets1 = {{"ICN", "JFK"}, {"HND", "IAD"}, {"JFK", "HND"}}; + String[][] tickets2 = {{"ICN", "SFO"}, {"ICN", "ATL"}, {"SFO", "ATL"}, {"ATL", "ICN"}, {"ATL","SFO"}}; + String[][] tickets3 = {{"ICN", "A"}, {"ICN", "B"}, {"B", "ICN"}}; + String[] answer = solution(tickets3); + System.out.println("정답 : "+Arrays.toString(answer)); + } +} + + +``` + diff --git a/source/woodi/210116_boj_2887.md b/source/woodi/210116_boj_2887.md new file mode 100644 index 0000000..124fbfc --- /dev/null +++ b/source/woodi/210116_boj_2887.md @@ -0,0 +1,144 @@ +# 백준 MST +- 행성터널 + - https://www.acmicpc.net/problem/2887 + +## 풀이 방법 +### 문제 이해하기 +- 각 행성의 좌표(x,y,z)가 주어질 때 +- 두 행성 A(xA, yA, zA)와 B(xB, yB, zB)를 터널로 연결할 때 드는 비용은 min(|xA-xB|, |yA-yB|, |zA-zB|)이다 +- 터널을 총 N-1개 건설해서 모든 행성이 서로 연결되게 하려고 한다. 이때, 모든 행성을 터널로 연결하는데 필요한 최소 비용을 구하시오 + +### 문제 접근 방법 +- 두 행성을 연결하는 간선이 3개(x,y,z 기준)씩 있다고 생각한다. +- 모든 행성들을 node list에 담고 x, y, z 각각에 대해서 정렬한 후, +- 각 행성의 번호화 비용을 edgeList에 추가한다. +- 그 결과 각 행성간의 x,y,z 거리(에지)가 edgeList에 저장된다. +- edgeList를 오름차순으로 정렬 후, 크루스칼 MST를 적용한다. +### 구현 배경 지식 +- MST : 크루스칼 +### 접근 방법을 적용한 코드 +```java + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; + +// https://www.acmicpc.net/problem/2887 +class Node{ + int num; + int x; + int y; + int z; + public Node(int num, int x, int y, int z){ + this.x = x; + this.y = y; + this.z = z; + this.num = num; + } +} +class Edge implements Comparable{ + int start; + int end; + int weight; + + Edge(int start, int end, int weight){ + this.start = start; + this.end = end; + this.weight = weight; + } + @Override + public int compareTo(Edge o) { + // TODO Auto-generated method stub + return weight -o.weight; + } + +} + +public class boj_2887 { + static int[] parent; + static ArrayList edgeList; + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StringTokenizer st =null; + int n = Integer.parseInt(br.readLine()); // 행성의 개수 + + Node[] nodes = new Node[n]; + for(int i=0; i(); + + // x, y, z 각각에 대해서 정렬하고 각 행성의 번호화 비용을 edgeList에 추가 + Arrays.sort(nodes, (n1,n2)->n1.x-n2.x); + for(int i=0; in1.y-n2.y); + for(int i=0; in1.z-n2.z); + for(int i=0; i=N || y>=M) { + return true; + } + return false; + } + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StringTokenizer st = new StringTokenizer(br.readLine()); + M = Integer.parseInt(st.nextToken()); // y + N = Integer.parseInt(st.nextToken()); // x + K = Integer.parseInt(st.nextToken()); + List li = new ArrayList<>(); + Block[][] blockli = new Block[N][M]; + // 색칠한 영역 추가 + int lx=0,ly=0,rx=0,ry=0; + for(int i=0; i hm = new HashMap<>(); // + ArrayList ar = new ArrayList<>(); + int count=0; + // bfs 탐색 시작 + Queue q = new LinkedList(); + for(Block b : li) { + if(b.check) { +// System.out.println(b.x+", "+b.y); + continue; + } + q.add(b); + blockli[b.x][b.y].check=true; + int square=0; // 넓이 + while(!q.isEmpty()) { + Block now = q.remove(); + int x = now.x; + int y = now.y; + square++; + for(int i=0; i<4; i++) { + int nx = x+dx[i]; + int ny = y+dy[i]; + if(!isOutOfBound(nx, ny) && !blockli[nx][ny].check ) { + blockli[nx][ny].check=true; + q.add(blockli[nx][ny]); +// square++; + } + } + } +// debug(blockli); + count++; + ar.add(square); + } + + System.out.println(count); + Collections.sort(ar); + for(int a : ar) { + System.out.print(a+" "); + } + } +} + +``` + diff --git a/source/woodi/210320_boj_1931.md b/source/woodi/210320_boj_1931.md new file mode 100644 index 0000000..af57066 --- /dev/null +++ b/source/woodi/210320_boj_1931.md @@ -0,0 +1,105 @@ +# 백준 MST +- 회의실 배정 + - https://www.acmicpc.net/problem/1931 + +## 풀이 방법 +### 문제 이해하기 +- 하나의 회의실이 있음 +- N개의 회의에 대하여 회의실 사용표를 만들려고한다. +- 각 회의에 대해서는 시작시간과 끝나느 시간이 주어짐 +- 이때 각 회의가 겹치지 않게 하면서 회의실을 사용할 수 있는 회의의 최대 개수를 찾아라 +- (단, 회의의 시작 시간과 끝나는 시간이 같을 수도 있다.) + +### 문제 접근 방법 + +1. 목록 S에 남은 회의실 중 가장 일찍 끝나는 회의 Smin을 선택한다. +2. Smin과 겹치는 회의를 S에서 모두 지운다 +3. S가 텅 빌 때까지 반복한다. + +- 조건 '가장 종료 시간이 빠른 회의(Smin)를 포함하는 최적해가 반드시 존재한다.'가 성립. + +- 증명) + - S의 최적해 중 Smin을 포함하지 않는 답 (S')가 존재 + - 이 답(S')은 서로 겹치지 않는 회의의 목록임 + - S'에서 첫번째로 개최되는 회의를 지우고, Smin을 대신 추가해서 새로운 목록을 만든다. + - 이때, Smin은 S에서 가장 일찍 끝나는 회의이기 때문에 지워진 회의는 Smin보다 일찍 끝날 수 없다. + - 따라서 S'의 가장 첫번째 회의를 Smin으로 대체하여 새로 만든 목록 역시 최적해 중 하나이다. + +- 정리) + - S' = {a,b,c,d, ....} (a,b,c,d..는 서로 겹치지 않음) + - Smin의 끝나는 시간 < a의 끝나는시간 + - S'' = {Smin, b,c,d, ...} -> 마찬가지로 최적해 + + + +### 구현 배경 지식 +- Greedy +### 접근 방법을 적용한 코드 +```java +import java.util.ArrayList; +import java.util.PriorityQueue; +import java.util.Scanner; + +public class boj_1931 { + static class Node implements Comparable{ + + int s; + int e; + + public Node(int s, int e) { + this.s = s; + this.e = e; + } + + // 빨리 끝나는 순으로 정렬 + @Override + public int compareTo(Node o) { + if(this.e == o.e) { + return Integer.compare(this.s, o.s); + } + return Integer.compare(this.e, o.e); + } + } + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + PriorityQueue pq = new PriorityQueue(); + for(int i=0; i pq.peek().s)) { + pq.remove(); + } + } + + System.out.println("result : "+ cnt); + } +} + + +``` + + +### 그리디 알고리즘 +#### 완전 탐색(혹은 DP)과 그리디 +1. 공통점 : (분할정복) 여러 조각으로 쪼개고, 각 단계마다 답의 한 부분을 만들어감 +2. 차이점 + - 완탐 : 모든 선택지를 고려해보고 그 중 전체 답이 가장 좋은 방법을 선택 + - 그리디 : 각 단계마다 '지금 당장' 좋은 방법만을 선택 (지금 선택이 나중에 어떤 영향을 미칠지 고려하지 않는다.) + +- 참고) + - 탐욕법으로 최적해를 찾을 수 있는 많은 문제들은 동적 계획법으로도 풀 수 있다. + - 탐욕법으로 최적해를 찾을 수 있다 = 지금 한 단계만을 고려해도 답을 찾을 수 있다. + - 따라서 동적 계획법은 모든 단계를 고려하므로 답이 틀릴 수가 없다. + - 그럼에도 그리디를 사용하는 이유는 동적 계획법에 필요한 메모리나 시간이 과도하게 크기 때문이다. \ No newline at end of file diff --git a/source/woodi/210327_programmers_42862.md b/source/woodi/210327_programmers_42862.md new file mode 100644 index 0000000..f4e5e74 --- /dev/null +++ b/source/woodi/210327_programmers_42862.md @@ -0,0 +1,88 @@ +# 프로그래머스 +- 체육복 + - https://programmers.co.kr/learn/courses/30/lessons/42862?language=java + +## 풀이 방법 +### 문제 이해하기 +- <입력> +- 전체 학생 수 n (2 <= n <= 30) +- 도난 학생 배열 lost (1 <= lost.length <= n) +- 여벌 학생 배열 reserve + +- <조건> +- 여벌 옷이 있는 학생 번호 +1, -1 한테만 빌려줄 수 있음 +- 여벌 옷이 있는 학생도 도난 가능, 이 경우 하나만 도난 당한다고 가정. 빌려 줄 수 없다. + +- 이때 체육 수업을 들을 수 있는 최대 학생 수를 구하라 + +### 문제 접근 방법 +- 전체 학생 배열 int[] student에 대하여 여벌 있으면 1, 도난 -1, 둘다 아니면 0, 여벌이 있으나 도난 당하면 0으로 초기화 +- student 배열을 for 문으로 돌면서 + - student[i] == -1 이면, + - 빌려 줄 수 있는 경우가 1개 + - student[i+1], student[i-1] 둘 중 하나 값이 1일 경우 -> answer++, student[i+1] or student[i-1] =0으로 set + - i가 1이거나 n인 경우 + - 빌려 줄 수 있는 경우가 2개 (student[i+1]==1 && student[i-1]==1) + - 앞에껄 빌려준다. 즉, student[i-1]을 먼저 빌려준다. (그리디) + - 뒤에꺼부터 빌려주면 나중에 도난 당한 사람이 못 빌릴 수도 있음 + - 아니면, answer++ +### 구현 배경 지식 +- 그리디 +### 접근 방법을 적용한 코드 +```java +class Solution { + public int solution(int n, int[] lost, int[] reserve) { + int answer = 0; + + int[] student = new int[n+1]; // 잃어버림 = -1, 여벌 = 1, 여벌있는데 잃어버림 =0, 둘다 아님 =0 + + for(int i=0; i lock의 홈 딱 맞으면 열림 + - 돌기와 돌기가 만나면 안됨 + - key는 lock 영역을 벗어날 수 있음 + - key는 회전, 이동 가능 + +### 문제 접근 방법 +- 90도씩 회전한 4가지 형태의 keys list 만들기 +- key가 lock 영역을 벗어나는 것 까지 포함하는 전체 영역 map 만든다 +- key를 90도씩 회전하며 모든 map 영역을 탐색한다. +### 구현 배경 지식 +- 완전탐색 +### 접근 방법을 적용한 코드 +```java +import java.util.*; +class Solution { + static int m; + static int n; + static int size; + public boolean solution(int[][] key, int[][] lock) { + + m = key.length; + n = lock.length; + // 회전한 모든 key의 경우의 수 담기 (총 4가지) + int[][][] keys = lotatedKeys(key); + + // map 영역 정의 + size = n+2*m-2; + int[][] map = new int[size][size]; + for(int i=m-1; i test 함수 (lock 영역의 모든 값이 1이면 true 리턴) + for(int t=0; t<4; t++) { + for(int i=0; i