no image
알고리즘 : DFS (깊이 우선 탐색)
깊이우선탐색 (DFS) Depth-First Search그래프 or 트리에서 한 노드 지정해 탐색을 시작, 인접한 노드를 탐색하고 더 깊은 단계로 들어가며 탐색을 이어나감한 경로를 끝까지 탐색한 후 다음 경로로 넘어간다    동작 원리 시작 노드에서 출발하여 가장 깊은 노드까지 탐색을 진행더이상 방문할 노드가 없으면, 한 단계 위로 돌아가서 탐색을 이어나감모든 노드를 방문할 때까지 반복!  DFS가 필요한 상황?1. 경로 탐색 문제 : 미로 탐색 등의 문제에서 특정 경로를 탐색해야 하는 경우 2. 연결 요소 탐색 : 그래프에서 특정 노드를 탐색해야 할 때, 모든 노드를 탐색해야 할 때 3. 백트래킹 : 퍼즐이나 조합을 탐색할 때 백트래킹 알고리즘의 기반이 되는 방식! 4. 사이클 탐지 : 순환 그래프의 ..
2024.09.17
no image
알고리즘 : 퀵 정렬 (Java)
퀵정렬 분할정복 알고리즘의 일종!시간복잡도 : O(NlogN)피벗을 정한 후, 피벗을 기준으로 피벗보다 작은 쪽과 큰 쪽으로 분할하며 정렬하는 방식최악의 경우는 이미 배열이 정렬되어 있거나 역순으로 되어있을 경우, O(n^2)의 시간복잡도 발생 가능    정렬 방식 피벗을 선택 (맨 앞, 가운데 ,맨 끝, 랜덤 등...)피벗을 기준으로 피벗보다 작은 수는 왼쪽으로, 큰 수는 오른쪽으로 정렬정렬이 완료되면 또다른 피벗을 정해 같은 방법으로 정렬       static void quickSort(int left, int right) { if (left = arr[L]) { L++; } // pibot보다 작은놈 찾을 때까지 while (arr[pivot]
2024.09.12
no image
알고리즘 : 병합정렬(Java)
병합정렬 분할정복 알고리즘의 일종!시간복잡도 : O(NlogN)배열을 더이상 나눠지지 않을 때까지 반으로 나눈 후 각각을 정렬하여 병합하는 방식별도의 배열을 생성하여 정렬한 요소들을 담아줘야 함=> 정렬 과정에서 계속 정렬 상태가 유지되는 안정 정렬 방식    정렬 방식 배열을 반으로 나눈다.  (분할)왼쪽 배열의 1번째 인덱스, 오른쪽 배열의 1번째 인덱스부터 비교 (정렬)작은 것을 우선으로 새로운 배열에 옮긴다. (병합)       import java.util.Arrays;public class MergeSort { public static void mergeSort(int[] array, int left, int right) { if (left
2024.09.11
백준: 10158번 개미(자바, Java) 답 정리
[백준] 10158번 개미https://www.acmicpc.net/problem/2164   문제가로 길이가 w이고 세로 길이가 h인 2차원 격자 공간이 있다. 이 격자는 아래 그림처럼 왼쪽 아래가 (0,0)이고 오른쪽 위가 (w,h)이다. 이 공간 안의 좌표 (p,q)에 개미 한 마리가 놓여있다. 개미는 오른쪽 위 45도 방향으로 일정한 속력으로 움직이기 시작한다. 처음에 (p,q)에서 출발한 개미는 1시간 후에는 (p+1,q+1)로 옮겨간다. 단, 이 속력으로 움직이다가 경계면에 부딪치면 같은 속력으로 반사되어 움직인다. 예를 들어 6×4 격자에서 처음에 (4,1)에 있는 개미는 2시간 후에 (6,3)에 있으며 8시간 후에 (0,1)에 있다. 만일 그 개미가 처음에 (5,3)에 있었다면 매 시간마다..
2024.08.15
no image
알고리즘: 병합 정렬 (JAVA)
병합정렬 분할정복 알고리즘의 일종!시간복잡도 : O(NlogN)배열을 더이상 나눠지지 않을 때까지 반으로 나눈 후 각각을 정렬하여 병합하는 방식별도의 배열을 생성하여 정렬한 요소들을 담아줘야 함=> 정렬 과정에서 계속 정렬 상태가 유지되는 안정 정렬 방식    정렬 방식 배열을 반으로 나눈다.  (분할)왼쪽 배열의 1번째 인덱스, 오른쪽 배열의 1번째 인덱스부터 비교 (정렬)작은 것을 우선으로 새로운 배열에 옮긴다. (병합)       import java.util.Arrays;public class MergeSort { public static void mergeSort(int[] array, int left, int right) { if (left
2024.08.12
no image
알고리즘: 버블 정렬 (JAVA)
버블정렬 정렬 방식이 마치 물속 거품이 수면으로 올라가는 듯하다고 하여 붙여진 이름가장 단순하고 이해하기 쉬운 방식 중 하나이다시간복잡도 : O(n^2)    정렬 방식첫 번째 값과 두 번째 값 비교첫 번째 값이 크면 서로 위치 교환. 아니면 그대로!두 번째 값과 세 번째 값 비교하여 같은 작업 반복끝 값까지 같은 작업을 반복하면, 가장 큰 값이 가장 끝 배열 요소로 들어감같은 과정을 반복하면 오름차순 정렬이 완성    장단점장점: 구현이 간단하고 코드가 직관적이다!단점: 시간복잡도가 O(n^2)로, 배열 크기가 커지면 비효율적이다.      import java.util.Arrays;public class BubbleSort { public static void main(String[] args) { ..
2024.08.05
백준: 2164번 카드2 (자바, Java)
[백준] 2164번 카드2https://www.acmicpc.net/problem/2164   문제N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다. 이제 다음과 같은 동작을 카드가 한 장 남을 때까지 반복하게 된다. 우선, 제일 위에 있는 카드를 바닥에 버린다. 그 다음, 제일 위에 있는 카드를 제일 아래에 있는 카드 밑으로 옮긴다. 예를 들어 N=4인 경우를 생각해 보자. 카드는 제일 위에서부터 1234 의 순서로 놓여있다. 1을 버리면 234가 남는다. 여기서 2를 제일 아래로 옮기면 342가 된다. 3을 버리면 42가 되고, 4를 밑으로 옮기면 24가 된다. 마지막으로 2를 버리고 나..
2024.05.28
백준: 1436번 영화감독 숌 (자바, Java)
[백준] 1436번 영화감독 숌https://www.acmicpc.net/problem/1436      문제숫자 6이 연속으로 3개 이상 포함되는 수 중 가장 작은 수는 666이다.그렇다면 숫자 6이 연속으로 3개 이상 포함되는 수 중  N번째로 작은 수를 구하는 프로그램을 작성하라. 입력 :첫째 줄에 N이 주어진다. N은 10,000보다 작거나 같은 자연수이다. 출력 : 첫째 줄에 N번째 영화의 제목에 들어간 수를 출력한다.  나의 답안처음에 문제를 잘못 읽었다. 6이 연속해서 3개 이상 포함되어야 하는데, 연속하지 않아도 3개 이상 포함되기만 하면 되는 줄 알고 charAt() 메서드를 사용해서 코드를 작성했다... 왜 안되나 계속 들여다 보다가 한참 뒤에 문제를 다시 읽고 원인을 발견했다 ㅋㅋㅋ ..
2024.05.25
백준: 1920번 수 찾기 (자바, Java)
[백준] 1920번 수 찾기https://www.acmicpc.net/problem/1920     문제육각형으로 이루어진 벌집이 있다. 중앙의 방 1부터 시작해서 이웃하는 방에 돌아가면서 1씩 증가하는 번호를 주소로 매길 수 있다. 숫자 N이 주어졌을 때, 벌집의 중앙 1에서 N번 방까지 최소 개수의 방을 지나서 갈 때 몇 개의 방을 지나가는지(시작과 끝을 포함하여)를 계산하는 프로그램을 작성하시오. 예를 들면, 13까지는 3개, 58까지는 5개를 지난다. 입력 :첫째 줄에 N(1 ≤ N ≤ 1,000,000,000)이 주어진다. 출력 :입력으로 주어진 방까지 최소 개수의 방을 지나서 갈 때 몇 개의 방을 지나는지 출력한다.  나의 답안 import java.util.*; public class Mai..
2024.05.24
백준: 1920번 수 찾기 (자바, Java)
[백준] 1920번 수 찾기https://www.acmicpc.net/problem/1920     문제N개의 정수 A[1], A[2], …, A[N]이 주어져 있을 때, 이 안에 X라는 정수가 존재하는지 알아내는 프로그램을 작성하라입력 :첫째 줄에 자연수 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 N개의 정수 A[1], A[2], …, A[N]이 주어진다. 다음 줄에는 M(1 ≤ M ≤ 100,000)이 주어진다. 다음 줄에는 M개의 수들이 주어지는데, 이 수들이 A안에 존재하는지 알아내면 된다. 모든 정수의 범위는 -231 보다 크거나 같고 231보다 작다. 출력 :M개의 줄에 답을 출력. 존재하면 1, 존재하지 않으면 0을 출력 나의 답안 import java.util.*; publ..
2024.05.23

깊이우선탐색 (DFS)

 

  • Depth-First Search
  • 그래프 or 트리에서 한 노드 지정해 탐색을 시작, 인접한 노드를 탐색하고 더 깊은 단계로 들어가며 탐색을 이어나감
  • 한 경로를 끝까지 탐색한 후 다음 경로로 넘어간다

 

 

 

 

동작 원리

 

  1. 시작 노드에서 출발하여 가장 깊은 노드까지 탐색을 진행

  2. 더이상 방문할 노드가 없으면, 한 단계 위로 돌아가서 탐색을 이어나감

  3. 모든 노드를 방문할 때까지 반복!

 

 

DFS가 필요한 상황?

1. 경로 탐색 문제 : 미로 탐색 등의 문제에서 특정 경로를 탐색해야 하는 경우

 

2. 연결 요소 탐색 : 그래프에서 특정 노드를 탐색해야 할 때, 모든 노드를 탐색해야 할 때

 

3. 백트래킹 : 퍼즐이나 조합을 탐색할 때 백트래킹 알고리즘의 기반이 되는 방식!

 

4. 사이클 탐지 : 순환 그래프의 경우 사이클의 여부를 탐지할 때 사용 가능

 

 

장점

  • 메모리 효율성 : BFS 방식에 비해 메모리 소비가 적다. 특히 그래프가 넓은 경우 더 효율적이다
  • 깊이가 중요한 문제에 적합함.

 

단점

  • 최적 경로가 보장되지 않는다. 최단 경로를 찾기 위해선 BFS가 적합

  • 방문여부를 체크하지 않으면 무한 루프가 발생할 수 있다.

 

 

 

구현

1. 스택을 이용한 구현

import java.util.*;

public class DFSStack {
    // DFS 알고리즘을 스택을 이용해 구현한 코드
    public static void dfsStack(Map<Integer, List<Integer>> graph, int startNode) {
        Stack<Integer> stack = new Stack<>();
        Set<Integer> visited = new HashSet<>();
        
        // 시작 노드를 스택에 추가
        stack.push(startNode);
        
        while (!stack.isEmpty()) {
            int node = stack.pop();  // 스택에서 노드를 꺼냄
            
            if (!visited.contains(node)) {
                visited.add(node);  // 방문한 노드로 처리
                System.out.print(node + " ");  // 방문 순서 출력
                
                // 인접 노드를 스택에 추가
                for (int neighbor : graph.get(node)) {
                    if (!visited.contains(neighbor)) {
                        stack.push(neighbor);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        // 그래프 초기화
        Map<Integer, List<Integer>> graph = new HashMap<>();
        graph.put(1, Arrays.asList(2, 3, 7));
        graph.put(2, Arrays.asList(1));
        graph.put(3, Arrays.asList(1, 4, 6));
        graph.put(4, Arrays.asList(3));
        graph.put(5, Arrays.asList(5));
        graph.put(6, Arrays.asList(3));
        graph.put(7, Arrays.asList(1));
        graph.put(8, Arrays.asList(7));

        System.out.println("DFS using stack:");
        dfsStack(graph, 1);  // 0번 노드에서 DFS 시작
    }
}

 

 

2. 재귀를 이용한 구현

메서드 호출은 Java 자체에서 스택 방식으로 처리되므로, 이를 이용해 재귀함수를 만들어 구현하는 방법.

가장 많이 사용된다!

import java.util.*;

public class DFSStack {
    // 재귀를 이용한 DFS
    public static void dfsRecursive(Map<Integer, List<Integer>> graph, int node, Set<Integer> visited) {
        // 노드를 방문하고 출력
        visited.add(node);
        System.out.print(node + " ");
        
        // 인접 노드를 재귀적으로 방문
        for (int neighbor : graph.get(node)) {
            if (!visited.contains(neighbor)) {
                dfsRecursive(graph, neighbor, visited);
            }
        }
    }

    public static void main(String[] args) {
        // 그래프 초기화
        Map<Integer, List<Integer>> graph = new HashMap<>();
        graph.put(1, Arrays.asList(2, 3, 7));
        graph.put(2, Arrays.asList(1));
        graph.put(3, Arrays.asList(1, 4, 6));
        graph.put(4, Arrays.asList(3));
        graph.put(5, Arrays.asList(5));
        graph.put(6, Arrays.asList(3));
        graph.put(7, Arrays.asList(1));
        graph.put(8, Arrays.asList(7));

        System.out.println("DFS using stack:");
        dfsStack(graph, 1);  // 0번 노드에서 DFS 시작
    }
}

알고리즘 : 퀵 정렬 (Java)

친환경 개발자
|2024. 9. 12. 19:00

퀵정렬

 

  • 분할정복 알고리즘의 일종!
  • 시간복잡도 : O(NlogN)
  • 피벗을 정한 후, 피벗을 기준으로 피벗보다 작은 쪽과 큰 쪽으로 분할하며 정렬하는 방식
  • 최악의 경우는 이미 배열이 정렬되어 있거나 역순으로 되어있을 경우, O(n^2)의 시간복잡도 발생 가능

 

 

 

 

정렬 방식

 

  1. 피벗을 선택 (맨 앞, 가운데 ,맨 끝, 랜덤 등...)

  2. 피벗을 기준으로 피벗보다 작은 수는 왼쪽으로, 큰 수는 오른쪽으로 정렬

  3. 정렬이 완료되면 또다른 피벗을 정해 같은 방법으로 정렬

 

 

 

 

 

 

	static void quickSort(int left, int right) {
		if (left < right) {
			int pivot = left;
			int L = left + 1;
			int R = right;

			while (L <= R) {
				// pivot보다 큰놈 찾을 떄까지
				while (L <= R && arr[pivot] >= arr[L]) {
					L++;
				}
				// pibot보다 작은놈 찾을 때까지
				while (arr[pivot] < arr[R]) {
					R--;
				}

				// L, R 자리 바꾸기
				if (L < R) {
					int tmp = arr[L];
					arr[L] = arr[R];
					arr[R] = tmp;
				}
			}
			// R과 자리 바꾸기
			int tmp = arr[R];
			arr[R] = arr[pivot];
			arr[pivot] = tmp;

			pivot = R;
			quickSort(left, pivot - 1);
			quickSort(pivot + 1, right);
		}
	}

 

 

알고리즘 : 병합정렬(Java)

친환경 개발자
|2024. 9. 11. 22:27

병합정렬

 

  • 분할정복 알고리즘의 일종!
  • 시간복잡도 : O(NlogN)
  • 배열을 더이상 나눠지지 않을 때까지 반으로 나눈 후 각각을 정렬하여 병합하는 방식
  • 별도의 배열을 생성하여 정렬한 요소들을 담아줘야 함
    => 정렬 과정에서 계속 정렬 상태가 유지되는 안정 정렬 방식

 

 

 

 

정렬 방식

 

  1. 배열을 반으로 나눈다.  (분할)

  2. 왼쪽 배열의 1번째 인덱스, 오른쪽 배열의 1번째 인덱스부터 비교 (정렬)
  3. 작은 것을 우선으로 새로운 배열에 옮긴다. (병합)

 

 

병합정렬 과정

 

 

 

 

 

import java.util.Arrays;

public class MergeSort {
    public static void mergeSort(int[] array, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;

            mergeSort(array, left, mid);
            mergeSort(array, mid + 1, right);

            merge(array, left, mid, right);
        }
    }

    private static void merge(int[] array, int left, int mid, int right) {
        int L = mid - left + 1;
        int R = right - mid;

        int[] leftArray = new int[L];
        int[] rightArray = new int[R];

        for (int i = 0; i < L; ++i) {
            leftArray[i] = array[left + i];
        }
        for (int j = 0; j < R; ++j) {
            rightArray[j] = array[mid + 1 + j];
        }

        int i = 0, j = 0;
        int k = left;
        while (i < L && j < R) {
            if (leftArray[i] <= rightArray[j]) {
                array[k] = leftArray[i];
                i++;
            } else {
                array[k] = rightArray[j];
                j++;
            }
            k++;
        }

        while (i < L) {
            array[k] = leftArray[i];
            i++;
            k++;
        }

        while (j < R) {
            array[k] = rightArray[j];
            j++;
            k++;
        }
    }

    public static void main(String[] args) {
        int[] data = {12, 11, 13, 5, 6, 7};
        mergeSort(data, 0, data.length - 1);
        System.out.println(Arrays.toString(data));
    }
}

 


[백준] 10158번 개미

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

 

 

 

문제

가로 길이가 w이고 세로 길이가 h인 2차원 격자 공간이 있다. 이 격자는 아래 그림처럼 왼쪽 아래가 (0,0)이고 오른쪽 위가 (w,h)이다. 이 공간 안의 좌표 (p,q)에 개미 한 마리가 놓여있다. 개미는 오른쪽 위 45도 방향으로 일정한 속력으로 움직이기 시작한다. 처음에 (p,q)에서 출발한 개미는 1시간 후에는 (p+1,q+1)로 옮겨간다. 단, 이 속력으로 움직이다가 경계면에 부딪치면 같은 속력으로 반사되어 움직인다.

 

예를 들어 6×4 격자에서 처음에 (4,1)에 있는 개미는 2시간 후에 (6,3)에 있으며 8시간 후에 (0,1)에 있다. 만일 그 개미가 처음에 (5,3)에 있었다면 매 시간마다 (6,4), (5,3), (4,2), (3,1)로 움직인다. 

여러분은 크기 w×h인 격자 공간에서 처음에 (p,q)에서 출발하는 개미의 t시간 후의 위치 (x,y)를 계산하여 출력해야 한다. 개미는 절대 지치지 않고 같은 속력으로 이동한다고 가정한다. 

문제에서 w와 h는 자연수이며 범위는 2 ≤ w,h ≤ 40,000이다. 그리고 개미의 초기 위치 p와 q도 자연수이며 범위는 각각 0 < p < w과 0 < q < h이다. 그리고 계산할 시간 t의 범위는 1 ≤ t ≤ 200,000,000이다. 

 

입력 :

첫줄에는 w와 h가 공백을 사이에 두고 주어진다. 그 다음 줄에는 초기 위치의 좌표값 p와 q가 공백을 사이에 두고 주어진다. 3번째 줄에는 개미가 움직일 시간 t가 주어진다. 

출력 :

출력은 t 시간 후에 개미의 위치 좌표 (x,y)의 값 x와 y를 공백을 사이에 두고 출력한다. 

 

 

나의 답안

 

 1번째 시도 (실패)

개미가 (p, q) 좌표에서 시작, 초기엔 시간마다 (+1, +1) 씩 이동하다가

 

x한계선에 부딪히면 (-1, +1)이동,  

y한계선에 부딪히면 (+1, -1) 이동,

x, y 한계선에 부딪히면 (-1, -1) 이동하도록 설계했다.


이동할 때마다 이동할 위치를 미리 확인하여 한계선 여부를 확인하고,
확인된 한계선의 좌표 이동값만 -1을 곱하여 바꿔주도록 했다.

 

<1차 시도(실패 코드)>

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

public class Main {

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		// 상하좌우 확인용 배열
//		int[] dr = { -1, 1, 0, 0 };
//		int[] dc = { 0, 0, -1, 1 };
		// 시간당 이동량
		int[] move = { 1, 1 };
		// 개미 현재위치 변수
		int r, c;
		// 개미가 이동할 위치 변수
		int nr, nc;
		// 좌표상으로 개미의 이동 가능 범위가 x : 0 ~ w, y : 0 ~ h 이므로
		// => 배열로는 w+1행 h+1열 지도 생성해야 함
		int w = Integer.parseInt(st.nextToken());
		int h = Integer.parseInt(st.nextToken());

		st = new StringTokenizer(br.readLine());
		// 개미 위치 입력받음
		r = Integer.parseInt(st.nextToken());
		c = Integer.parseInt(st.nextToken());
		// 시간 입력
		int time = Integer.parseInt(br.readLine());

		for (int t = 1; t <= time; t++) {
			// 사방 탐색
			nr = r + move[0];
			nc = c + move[1];
			// nr값이 범위를 넘으면 => 이동량 (-1, 1)
			if (nr < 0 || nr > w) move[0] *= -1;
			// nc값이 범위를 넘으면 => 이동량 (1, -1)
			if (nc < 0 || nc > h) move[1] *= -1;
			// 개미 이동
			r += move[0];
			c += move[1];
		}

		// time시간 후 개미 위치 출력
		System.out.println(r + " " + c);
	}
}

 

 

 

2번째 시도 (성공)

 

1차 시도에서 시간 초과 발생. time이 최대 2억이므로 for문을 사용하면 안될 것이라고 판단했다.

 

  1.  time을 최대 좌표값으로 나눴을 때 

    몫이 짝수이면 => 2바퀴 돌고 제자리로 돌아오고, 이동량은 그대로 +1

    몫이 홀수이면 => 1바퀴 돌아 원래 자리의 대칭점으로 이동. 이동량은 반대인 -1

  2. time을 좌표값으로 나눈 나머지만큼 이동해주면 최종 위치값이 된다.

<2차 코드(성공)>

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

public class S3_10158_개미 {

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		// 개미 현재위치 변수
		int r, c;
        
		st = new StringTokenizer(br.readLine());
        
		// 개미 위치 입력받음
		r = Integer.parseInt(st.nextToken());
		c = Integer.parseInt(st.nextToken());
        
		// 시간 입력
		int time = Integer.parseInt(br.readLine());
        
		// 몫이 짝수이면 다시 제자리로 돌아온 것과 같음
		if (time / w % 2 == 0) {
			// 제자리에서 (+1 * 나머지) 만큼 이동
			r = r + (time % w) > w ? w - (r + (time % w) - w) : r + (time % w);
		// 몫이 홀수이면 원래 자리의 대칭점으로 이동한 것과 같음
		} else {
			// 대칭점에서 (-1 * 나머지) 만큼 이동
			r = r + (time % w - w) < 0 ? - (r + (time % w - w)) : r + (time % w - w);
		}
        
		// y좌표도 동일하게 이동 처리
		if (time / h % 2 == 0) {
			c = c + (time % h) > h ? h - (c + (time % h) - h) : c + (time % h);
		} else {
			c = c + (time % h - h) < 0 ? - (c + (time % h - h)) : c + (time % h - h);
		}
        
		System.out.println(r + " " + c);
	}
}

 

개선 사항

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String[] size = br.readLine().split(" ");
        int w = Integer.parseInt(size[0]);
        int h = Integer.parseInt(size[1]);
        
        String[] point = br.readLine().split(" ");
        int p = Integer.parseInt(point[0]);
        int q = Integer.parseInt(point[1]);
        
        int t = Integer.parseInt(br.readLine());
        
        int x = (t + p) % (2 * w);
        int y = (t + q) % (2 * h);
        
        if (x > w) {
            x = 2 * w - x;
        }
        
        if (y > h) {
            y = 2 * h - y;
        }
        
        System.out.println(x + " " + y);
    }
    
}
  • x, y좌표의 계산을 매우 간결하게 처리했다.
  • x, y좌표가 범위를 넘었을 경우를 별도 if문으로 처리하여 가독성이 좋아졌다.

 

문제에 좌표가 있으면 일단 무조건 배열을 생성하고

 

시간별로 일일이 처리하도록 계산했었는데,

 

이렇게 간단한 계산식으로 처리할 수가 있다는 것을

 

바로 생각해내지 못했다.

 

항상 정해진 답은 없다는 것을 생각하고

 

더 효율적인 방법이 있을지 생각해봐야 하겠다.

알고리즘: 병합 정렬 (JAVA)

친환경 개발자
|2024. 8. 12. 23:45

병합정렬

 

  • 분할정복 알고리즘의 일종!
  • 시간복잡도 : O(NlogN)
  • 배열을 더이상 나눠지지 않을 때까지 반으로 나눈 후 각각을 정렬하여 병합하는 방식
  • 별도의 배열을 생성하여 정렬한 요소들을 담아줘야 함
    => 정렬 과정에서 계속 정렬 상태가 유지되는 안정 정렬 방식

 

 

 

 

정렬 방식

 

  1. 배열을 반으로 나눈다.  (분할)

  2. 왼쪽 배열의 1번째 인덱스, 오른쪽 배열의 1번째 인덱스부터 비교 (정렬)
  3. 작은 것을 우선으로 새로운 배열에 옮긴다. (병합)

 

 

병합정렬 과정

 

 

 

 

 

import java.util.Arrays;

public class MergeSort {
    public static void mergeSort(int[] array, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;

            mergeSort(array, left, mid);
            mergeSort(array, mid + 1, right);

            merge(array, left, mid, right);
        }
    }

    private static void merge(int[] array, int left, int mid, int right) {
        int L = mid - left + 1;
        int R = right - mid;

        int[] leftArray = new int[L];
        int[] rightArray = new int[R];

        for (int i = 0; i < L; ++i) {
            leftArray[i] = array[left + i];
        }
        for (int j = 0; j < R; ++j) {
            rightArray[j] = array[mid + 1 + j];
        }

        int i = 0, j = 0;
        int k = left;
        while (i < L && j < R) {
            if (leftArray[i] <= rightArray[j]) {
                array[k] = leftArray[i];
                i++;
            } else {
                array[k] = rightArray[j];
                j++;
            }
            k++;
        }

        while (i < L) {
            array[k] = leftArray[i];
            i++;
            k++;
        }

        while (j < R) {
            array[k] = rightArray[j];
            j++;
            k++;
        }
    }

    public static void main(String[] args) {
        int[] data = {12, 11, 13, 5, 6, 7};
        mergeSort(data, 0, data.length - 1);
        System.out.println(Arrays.toString(data));
    }
}

 

알고리즘: 버블 정렬 (JAVA)

친환경 개발자
|2024. 8. 5. 16:21

버블정렬

 

  • 정렬 방식이 마치 물속 거품이 수면으로 올라가는 듯하다고 하여 붙여진 이름
  • 가장 단순하고 이해하기 쉬운 방식 중 하나이다
  • 시간복잡도 : O(n^2)

 

 

 

 

정렬 방식

  1. 첫 번째 값과 두 번째 값 비교
  2. 첫 번째 값이 크면 서로 위치 교환. 아니면 그대로!
  3. 두 번째 값과 세 번째 값 비교하여 같은 작업 반복
  4. 끝 값까지 같은 작업을 반복하면, 가장 큰 값이 가장 끝 배열 요소로 들어감
  5. 같은 과정을 반복하면 오름차순 정렬이 완성

 

 

 

 

장단점

  • 장점: 구현이 간단하고 코드가 직관적이다!
  • 단점: 시간복잡도가 O(n^2)로, 배열 크기가 커지면 비효율적이다.

 

 

 

 

 

 

import java.util.Arrays;

public class BubbleSort {

	public static void main(String[] args) {
		int[] arr = {22, 42, 31, 10, 35};
		
		// 버블 정렬
		for (int i=0; i<arr.length; i++) {
			for (int j=1; j<arr.length-i; j++) {
				if (arr[j-1] > arr[j]) {
					int tmp = arr[j-1];
					arr[j-1] = arr[j];
					arr[j] = tmp;
				}
			}
		}
		
		System.out.println(Arrays.toString(arr));
	}

}

 


[백준] 2164번 카드2

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

 

 

 

문제

N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다.

이제 다음과 같은 동작을 카드가 한 장 남을 때까지 반복하게 된다. 우선, 제일 위에 있는 카드를 바닥에 버린다. 그 다음, 제일 위에 있는 카드를 제일 아래에 있는 카드 밑으로 옮긴다.

예를 들어 N=4인 경우를 생각해 보자. 카드는 제일 위에서부터 1234 의 순서로 놓여있다. 1을 버리면 234가 남는다. 여기서 2를 제일 아래로 옮기면 342가 된다. 3을 버리면 42가 되고, 4를 밑으로 옮기면 24가 된다. 마지막으로 2를 버리고 나면, 남는 카드는 4가 된다.

N이 주어졌을 때, 제일 마지막에 남게 되는 카드를 구하는 프로그램을 작성하시오.

 

입력 :

첫째 줄에 정수 N(1 ≤ N ≤ 500,000)이 주어진다.

출력 :

첫째 줄에 남게 되는 카드의 번호를 출력한다.

 

 

나의 답안

 

처음엔 다이나믹프로그래밍인가 하고

D(1) = 1

D(2) = 2

D(3) = 2

...

풀었으나, 일정 규칙이 보이질 않았다.

 

한참 헛다리 짚다가, 리스트를 사용해 직접 처리하는 코드를 작성했지만,

 

시간 초과로 실패.

 

알고보니 큐(Queue)를 이용해 푸는 문제였다.

 

<List를 사용한 코드>
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        List<Integer> list = new ArrayList<>();

        for (int i = 1; i <= N; i++) {
            list.add(i);
        }

        while (list.size() > 1) {
            list.remove(0);
            list.add(list.get(0));
            list.remove(0);
        }

        System.out.println(list.get(0));
    }
}

 

 

 

 

개선 사항

<Queue를 사용한 코드>
import java.io.*;
import java.util.LinkedList;
import java.util.Queue;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        Queue<Integer> queue = new LinkedList<>();

        for (int i = 1; i <= N; i++) {
            queue.add(i);
        }

        while (queue.size() > 1) {
            queue.poll();
            queue.add(queue.poll());
        }

        System.out.println(queue.peek());
    }
}
  • List에서의 remove(0)은 첫 번째 요소를 제거하고 나머지 요소를 1칸씩 당겨와야 하기 때문에
    O(N)의 시간복잡도를 가진다.
  • Queue의 poll()은 FIFO구조로 맨 앞의 것만 빼오면 알아서 정렬되기에 O(1)의 시간복잡도를 가진다.

 

큐는 거의 사용해보질 않아서 생각을 못했다.

 

가독성은 둘 다 비슷하지만, 

 

효율성 측면에서 큰 차이가 있었다.

 

각 타입의 성질을 잘 파악해서 활용해야겠다.


[백준] 1436번 영화감독 숌

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

 

 

 

 

 

 

문제

숫자 6이 연속으로 3개 이상 포함되는 수 중 가장 작은 수는 666이다.

그렇다면 숫자 6이 연속으로 3개 이상 포함되는 수 중  N번째로 작은 수를 구하는 프로그램을 작성하라.
 

입력 :

첫째 줄에 N이 주어진다. N은 10,000보다 작거나 같은 자연수이다.

출력 :

첫째 줄에 N번째 영화의 제목에 들어간 수를 출력한다.

 

 

나의 답안

처음에 문제를 잘못 읽었다.

 

6이 연속해서 3개 이상 포함되어야 하는데, 연속하지 않아도 3개 이상 포함되기만 하면

 

되는 줄 알고 charAt() 메서드를 사용해서 코드를 작성했다...

 

왜 안되나 계속 들여다 보다가 한참 뒤에 문제를 다시 읽고 원인을 발견했다 ㅋㅋㅋ

 

 

import java.util.*;

public class Main {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int count = 0;
        int i = 666;

        while (count < n) {
            String s = String.valueOf(i);
            if (s.contains("666")) count++;
            if (count == n) break;
            i++;
        }

        System.out.println(i);
    }
}

 

어떻게 하면 효율적으로 풀까 하다가 생각이 나지 않아 무식하게(?) 작성해봤는데 통과되었다.

 

count 변수를 이용해 몇 번째 숫자인지를 카운트했고,

 

666부터 시작해 수를 1씩 올려가며

 

contains()메서드를 이용해 해당 숫자에 6이 연속 3회 이상 포함되는지 확인했다.

 

 

 

개선 사항

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException{
        // Scanner에 비해 처리속도 높음
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
        int n = Integer.parseInt(br.readLine());

        int count = 1;
        int num = 666;

        while (count < n) {
            num++;    //조건문 말미에 넣는 대신 처음에 넣음으로써 break;문 제거
            if (Integer.toString(num).contains("666")) count++;
        }

        System.out.println(num);
    }
}
  • Scanner보다 BufferedReader를 통해 입력 받는 것이 처리 속도 면에서 유리
  • 문자열 변수를 별도로 생성하는 대신 Integer.toString(num)을 사용
  • while문 첫부분에 num++를 넣음으로써 불필요한 break문 제거

 

※브루트포스 알고리즘:

문제를 해결하기 위해 가능한 모든 경우의 수를 탐색하는 방법

규모가 작거나, 다른 최적화된 알고리즘을 사용하기 어려울 때 유용하다.

 

장점

1. 확실성: 모든 가능성을 탐색하기 때문에 100%의 정확도를 가짐

2. 단순성: 구현이 쉽고 직관적임

 

단점

1. 비효율성: 경우의 수가 많아질수록 시간이 오래 걸림

2. 고비용: 데이터소모가 매우 많아짐

 

브루트포스 알고리즘을 쓰더라도, 확실하게 정답이 아닌 것들은 최대한

제거해주는 코딩을 하는 것이 바람직할 것으로 보인다!


[백준] 1920번 수 찾기

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

 

 

 

 

 

문제

육각형으로 이루어진 벌집이 있다. 중앙의 방 1부터 시작해서 이웃하는 방에 돌아가면서 1씩 증가하는 번호를 주소로 매길 수 있다. 숫자 N이 주어졌을 때, 벌집의 중앙 1에서 N번 방까지 최소 개수의 방을 지나서 갈 때 몇 개의 방을 지나가는지(시작과 끝을 포함하여)를 계산하는 프로그램을 작성하시오. 예를 들면, 13까지는 3개, 58까지는 5개를 지난다.


입력 :

첫째 줄에 N(1 ≤ N ≤ 1,000,000,000)이 주어진다.

출력 :

입력으로 주어진 방까지 최소 개수의 방을 지나서 갈 때 몇 개의 방을 지나는지 출력한다.

 

 

나의 답안

 

import java.util.*;

public class Main {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();

        int count = 1;
        int a = 1;

        for (int i=0; i<N/6; i++) {
            a += 6*i;
            if(a >= N) {
                System.out.println(count);
                break;
            }
            count++;
        }
    }
}

 

계차수열처럼, 1칸씩 바깥으로 나갈수록 그전 값이랑 6*n만큼 차이가 나는 것을 알 수 있다.

 

따라서 N=1 이면 1칸, N=2~7이면 2칸, N=8~19이면 3칸 이런 방식.

 

for문을 사용해 a값을 6*i만큼 늘려가며 범위를 넓혔고,

 

count를 사용해 값을 반환하도록 작성했다.

 

다만, for문을 사용할 경우 i의 값을 N/6까지로 설정했는데,

 

break;를 사용하긴 했지만 불필요한 반복이 발생할 수도 있고

 

그렇게 보기 좋은 코드는 아닌 것 같다.

 

 

 

개선 사항

 

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        sc.close();
        
        int count = 1; 
        int range = 1; 
        
        while (N > range) {
            range += 6 * count;
            count++;
        }
        
        System.out.println(count);
    }
}
  • 변수를 a 대신 range로 바꿔 가독성을 높였다.
  • for문 대신 while문을 사용하여 불필요한 반복의 가능성을 제거했다.
  • 코드가 한결 간결해졌다.

 

계차수열을 어떻게 반복문에 표현할까를 생각보다 오래 고민했다.

 

바로바로 나오는 수준이 되어야 할텐데..

 

더 열공하자


[백준] 1920번 수 찾기

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

 

 

 

 

 

문제

N개의 정수 A[1], A[2], …, A[N]이 주어져 있을 때, 이 안에 X라는 정수가 존재하는지 알아내는 프로그램을 작성하라

입력 :

첫째 줄에 자연수 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 N개의 정수 A[1], A[2], …, A[N]이 주어진다. 다음 줄에는 M(1 ≤ M ≤ 100,000)이 주어진다. 다음 줄에는 M개의 수들이 주어지는데, 이 수들이 A안에 존재하는지 알아내면 된다. 모든 정수의 범위는 -231 보다 크거나 같고 231보다 작다.

출력 :

M개의 줄에 답을 출력. 존재하면 1, 존재하지 않으면 0을 출력

 

나의 답안

 

import java.util.*;

public class Main {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        StringBuilder sb = new StringBuilder();

        for (int i=0; i<N; i++) {
            sb.append(sc.next());
        }
        String str = sb.toString();

        int M = sc.nextInt();
        for (int i=0; i<M; i++) {
            System.out.println(str.contains(sc.next())? 1 : 0);
        }
    }
}

 

처음엔 StringBuilder를 사용하여 답을 출력하는 코드를 작성했으나 시간초과되었다.

 

contains() 메서드를 사용했는데,

 

찾아보니 해당 메서드는 O(N*M)의 시간 복잡도(여기서는 O(5*5)를 가져 시간 초과를 유발한 것으로 보인다.

 

 

import java.util.*;

public class Main {

    static int[] A;
    public static int binarySearch(int start, int end, int target) {
        int mid = (start + end) / 2;
        if (mid > end || mid < start) return 0;
        if (A[mid] == target) return 1;
        if (A[mid] < target) return binarySearch(mid+1, end, target);
        if (A[mid] > target) return binarySearch(start, mid-1, target);
        return 0;
    }

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        A = new int[N];

        for (int i=0; i<N; i++) {
            A[i] = sc.nextInt();
        }
        Arrays.sort(A);

        int M = sc.nextInt();
        for (int i=0; i<M; i++) {
            System.out.println(binarySearch(0, N-1, sc.nextInt()));
        }
    }
}

 

이진탐색 방법을 사용하여 구했다.

입력을 A배열에 담고, 오름차순 정렬한 후,

입력받은 값과 A배열의 중간값을 비교하여 더 크면

중간값 이후의 값들과 비교, 작으면 중간값 이전의 값들과 비교해가며

탐색 영역을 반씩 줄여나가는 방법.

 

첫 번째 코드의 시간 복잡도는 contains메서드를 사용해 O(5*5)정도라고 하면,

이진탐색의 시간복잡도는 O(5*log5) 이기 때문에,

두번째 코드가 시간 초과를 면할 수 있던 것으로 보인다.

 

개선 사항

 

import java.util.*;

public class Main {

    static int[] A;

    public static int binarySearch(int start, int end, int target) {

        // while문으로 가독성 UP
        while (start <=end) {
            int mid = (start + end) / 2;
            if (A[mid] == target) return 1;
            if (A[mid] < target) start = mid + 1;
            else end = mid - 1;
        }
        return 0;
    }

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        A = new int[N];

        for (int i=0; i<N; i++) {
            A[i] = sc.nextInt();
        }
        Arrays.sort(A);

        int M = sc.nextInt();
        for (int i=0; i<M; i++) {
            System.out.println(binarySearch(0, N-1, sc.nextInt()));
        }
    }
}
  • 재귀호출 대신 while 조건문을 사용하여 가독성이 좋아진 듯하다.
  • 성능 면에서는 차이가 크게 없을 것으로 보임

 

Arrays.sort를 사용하면 무조건 비효율적일 것이다 생각했는데,

오히려 contains메서드를 사용한 코드가 더 비효율적일 수 있다는 것을 깨달았던 문제이다.