
정렬 문제를 딸깍 하고 싶은 마음이 크다. 특히 이번에 병합정렬을 구현하는 과정이 너무 힘들었다.
링크 : https://www.acmicpc.net/problem/11650
문제 설명
2차원 평면 위의 점 N개가 주어진다. 좌표를 x좌표가 증가하는 순으로, x좌표가 같으면 y좌표가 증가하는 순서로 정렬한 다음 출력하는 프로그램을 작성하시오.
문제 풀이
첫번째로 버블정렬을 사용해서 풀어봤다.
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
//좌표 정렬하기 11650
public class boj_11650 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//input
int n = Integer.parseInt(br.readLine());
int[][] arr = new int[n][2];
for (int i = 0; i < n; i++) {
arr[i] = Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
}
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j][0] > arr[j + 1][0]) {
int[] temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
} else if (arr[j][0] == arr[j + 1][0]) {
if (arr[j][1] > arr[j + 1][1]) {
int[] temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
for (int i = 0; i < n; i++) {
System.out.println(arr[i][0] + " " + arr[i][1]);
}
System.out.println();
}
}
당연히 시간초과가 난다. O(n^2) 이고 주어진 n 이 10^5 이므로 아주 해당 알고리즘은 효율적이지 않다.
강의에서 배운 합병 정렬을 응용해서 풀어봤다.
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
//좌표 정렬하기 11650
public class boj_11650 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//input
int n = Integer.parseInt(br.readLine());
int[][] arr = new int[n][2];
for (int i = 0; i < n; i++) {
String[] input = br.readLine().split(" ");
arr[i][0] = Integer.parseInt(input[0]);
arr[i][1] = Integer.parseInt(input[1]);
}
// 합병 정렬을 사용.
mergeSort(arr, 0, n - 1);
for (int i = 0; i < n; i++) {
System.out.println(arr[i][0] + " " + arr[i][1]);
}
System.out.println();
}
public static void mergeSort(int[][] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
public static void merge(int[][] arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[][] leftArr = new int[n1][2];
int[][] rightArr = new int[n2][2];
for (int i = 0; i < n1; ++i) {
leftArr[i][0] = arr[left + i][0];
leftArr[i][1] = arr[left + i][1];
}
for (int j = 0; j < n2; ++j) {
rightArr[j][0] = arr[mid + 1 + j][0];
rightArr[j][1] = arr[mid + 1 + j][1];
}
int i = 0, j = 0;
int k = left;
while (i < n1 && j < n2) {
if (leftArr[i][0] < rightArr[j][0] || (leftArr[i][0] == rightArr[j][0] && leftArr[i][1] <= rightArr[j][1])) {
arr[k][0] = leftArr[i][0];
arr[k][1] = leftArr[i][1];
i++;
} else {
arr[k][0] = rightArr[j][0];
arr[k][1] = rightArr[j][1];
j++;
}
k++;
}
while (i < n1) {
arr[k][0] = leftArr[i][0];
arr[k][1] = leftArr[i][1];
i++;
k++;
}
while (j < n2) {
arr[k][0] = rightArr[j][0];
arr[k][1] = rightArr[j][1];
j++;
k++;
}
}
}
합병 정렬은 배열을 분할하고 병합하는 방식으로 정렬하는 알고리즘에서 주어진 mergeSort와 merge 함수를 이용하여 배열을 정렬하는 과정을 아래 기술했다.
mergeSort 함수분할 단계:
mergeSort 함수는 배열을 재귀적으로 분할한다. 먼저, 배열을 절반으로 나누어 각각을 정렬한다.left가 right보다 작을 때, 배열을 분할한다. 중간 인덱스 mid를 계산하여 왼쪽 부분(left부터 mid)과 오른쪽 부분(mid + 1부터 right)으로 나눈다.mergeSort를 재귀적으로 호출한다.public static void mergeSort(int[][] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
merge 함수병합 단계:
merge 함수는 두 개의 정렬된 부분 배열을 하나의 정렬된 배열로 병합한다.leftArr)과 오른쪽 배열(rightArr)을 생성하고, 각각의 요소들을 복사한다.arr)에 넣는다. 이 과정은 두 배열 중 하나가 완료될 때까지 계속된다.public static void merge(int[][] arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[][] leftArr = new int[n1][2];
int[][] rightArr = new int[n2][2];
for (int i = 0; i < n1; ++i) {
leftArr[i][0] = arr[left + i][0];
leftArr[i][1] = arr[left + i][1];
}
for (int j = 0; j < n2; ++j) {
rightArr[j][0] = arr[mid + 1 + j][0];
rightArr[j][1] = arr[mid + 1 + j][1];
}
int i = 0, j = 0;
int k = left;
while (i < n1 && j < n2) {
if (leftArr[i][0] < rightArr[j][0] || (leftArr[i][0] == rightArr[j][0] && leftArr[i][1] <= rightArr[j][1])) {
arr[k][0] = leftArr[i][0];
arr[k][1] = leftArr[i][1];
i++;
} else {
arr[k][0] = rightArr[j][0];
arr[k][1] = rightArr[j][1];
j++;
}
k++;
}
while (i < n1) {
arr[k][0] = leftArr[i][0];
arr[k][1] = leftArr[i][1];
i++;
k++;
}
while (j < n2) {
arr[k][0] = rightArr[j][0];
arr[k][1] = rightArr[j][1];
j++;
k++;
}
}
이 병합 정렬 방식은 배열의 크기에 상관없이 O(n log n)의 시간 복잡도로 효율적으로 정렬할 수 있다.
링크 : https://www.acmicpc.net/problem/5648
문제 설명

밑처럼 입력 줄에 여러 원소값이 들어갈 수있기에 까다롭다.

문제 풀이
일단 입력값을 파싱한다.
파싱한 값을 arr에 넣는다.
sort 할때 함수를 정렬한다.
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;
// 역원소 정렬 5648
public class boj_5648 {
public static void main(String[] args) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
List<Integer> arr = new ArrayList<>();
int n = Integer.parseInt(st.nextToken());
// arr input
while (arr.size() != n) {
while (st.hasMoreTokens()) {
arr.add(Integer.parseInt(st.nextToken()));
}
st = new StringTokenizer(br.readLine());
}
Integer[] arrArray = arr.toArray(new Integer[0]);
Arrays.sort(arrArray, new Comparator<Integer>(){
@Override
public int compare(Integer i1, Integer i2){
String s1 = new StringBuilder(i1.toString()).reverse().toString();
String s2 = new StringBuilder(i2.toString()).reverse().toString();
return Long.compare(Long.parseLong(s1), Long.parseLong(s2));
}
});
for (Integer num : arrArray) {
sb.append(num).append("\n");
}
System.out.print(sb);
br.close();
}
}
이렇게 정렬할 경우 출력은 뒤집힌 원소가 아니라 원래 원소가 나온다. 다시 뒤집어줘야하는데 비효율적인 것 같아 처음 파싱할때 바꿔주는 것으로 바꿨다.
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;
// 역원소 정렬 5648
public class boj_5648 {
public static void main(String[] args) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
List<Long> arr = new ArrayList<>();
int n = Integer.parseInt(st.nextToken());
// arr input
while (arr.size() != n) {
while (st.hasMoreTokens()) {
String original = st.nextToken();
String reversed = new StringBuilder(original).reverse().toString();
arr.add(Long.parseLong(reversed));
}
if (arr.size() < n) {
st = new StringTokenizer(br.readLine());
}
}
arr.sort(Comparator.naturalOrder());
for (Long num : arr) {
sb.append(num).append("\n");
}
System.out.print(sb);
br.close();
}
}
// arr input
while (arr.size() != n) {
while (st.hasMoreTokens()) {
String original = st.nextToken();
String reversed = new StringBuilder(original).reverse().toString();
arr.add(Long.parseLong(reversed));
}
if (arr.size() < n) {
st = new StringTokenizer(br.readLine());
}
}
링크 : https://www.acmicpc.net/problem/2910
문제 설명

문제 풀이
카운팅 소트를 구현하는 문제는 아니다. C 가 10억이기 때문.
→ 계수행렬을 딕셔너리로 구현하면 되지 않을까?
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.StringTokenizer;
// 빈도 정렬 2910
// 카운팅 소트를 구현하는 문제가 아니다. C 가 10억이기 때문.
// 계수행렬을 딕셔너리로 구현하면 되지 않을까?
public class boj_2910 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
HashMap<Integer, Integer> freArr = new HashMap<>(); // key : 해당 수, value : 빈도
// 계수 딕셔너리 초기화
while (st.hasMoreTokens()) {
int cur = Integer.parseInt(st.nextToken());
freArr.put(cur, freArr.getOrDefault(cur, 0) + 1);
}
// c를 준 이유가 있을 듯. HashMap은 정렬이 되지 않으니 c~1 까지 한번 돌리면서 값이 있으면 해당 수를 출력하는 식으로 진행하면 될듯. 그러면 시간복잡도 O(2n) = O(n)
StringBuilder sb = new StringBuilder();
for (int i = c; i > 0; i--) {
for (int j = 0; j < freArr.getOrDefault(i,0); j++) { // 빈도 수 만큼 반복
sb.append(i).append(' ');
}
}
System.out.println(sb.toString());
br.close();
}
}
흠 뭔가 이상하다.
알고보니 dictionary에 들어간 수들의 빈도를 기준을 정렬하는거였다.ㅎㅎㅎ;;
package 정렬;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class boj_2910 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
// 입력받은 수와 그 빈도를 저장할 맵
LinkedHashMap<Integer, Integer> frequencyMap = new LinkedHashMap<>();
// 입력받은 순서대로 리스트에 추가
List<Integer> numbers = new ArrayList<>();
// 빈도 계산 및 입력 순서 유지
st = new StringTokenizer(br.readLine());
while (st.hasMoreTokens()) {
int num = Integer.parseInt(st.nextToken());
numbers.add(num);
frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
}
// 리스트 정렬
numbers.sort((a, b) -> {
int freqCompare = frequencyMap.get(b).compareTo(frequencyMap.get(a));
if (freqCompare == 0) {
return numbers.indexOf(a) - numbers.indexOf(b);
}
return freqCompare;
});
// 출력
StringBuilder sb = new StringBuilder();
for (int num : numbers) {
sb.append(num).append(' ');
}
System.out.println(sb.toString().trim());
br.close();
}
}
input 을 ArrayList 에 초기화시켜준 sort함수를 사용했다.
sort의 comparator 는 다음 1차로 빈도 기준으로 정리하고 빈도가 같을 경우 처음 들어온 시점을
// 계수 딕셔너리 초기화
while (st.hasMoreTokens()) {
int cur = Integer.parseInt(st.nextToken());
freArr.put(cur, freArr.getOrDefault(cur, 0) + 1);
}
링크 : https://www.acmicpc.net/problem/7795
문제 설명
두 집합 A, B가 주어졌을 때 A→B에서 a가 b 보다 큰 개수를 구하는 문제.
문제 풀이
이렇게 두 쌍의 배열의 주어졌을 때
A : 8 1 7 3 1
B : 3 6 1
A, B를 정렬한 이후 A를 순차적으로 비교하기 시작해보자.
A : 8 7 3 1 1
B : 6 3 1
a[0] : 8 과 b[0] 6 비교 → A 가 더 큼 (뒤에 있는 수들은 모두 6보다 작으므로 result += b.length - b.index 인 3을 더해준다.)
a[1] : 7 과 b[0] 6 비교 → A 가 더 큼 (뒤에 있는 수들은 모두 6보다 작으므로 result += b.length - b.index 인 3을 더해준다.)
a[2] : 3 과 b[0] 6 비교 → B 가 더 큼 (b.index++)
a[2] : 3 과 b[1] 3 비교 → 같음 (b.index++)
a[2] : 3 과 b[2] 1 비교 → A 가 더 큼 (뒤에 있는 수들은 모두 1보다 작으므로 result += b.length - b.index 인 1을 더해준다.)
흠 이제 원리는 알았따.
bIndex를 증가bIndex가 가리키는 인덱스까지의 B 배열의 요소 개수를 result에 더함.result 값을 출력
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
public class boj_7795 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int tc = Integer.parseInt(br.readLine());
StringTokenizer st;
for (int i = 0; i < tc; i++) {
st = new StringTokenizer(br.readLine());
// a, b 초기화
int lengthOfA = Integer.parseInt(st.nextToken());
int lengthOfB = Integer.parseInt(st.nextToken());
List<Integer> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
st = new StringTokenizer(br.readLine());
for (int j = 0; j < lengthOfA; j++) {
a.add(Integer.parseInt(st.nextToken()));
}
st = new StringTokenizer(br.readLine());
for (int j = 0; j < lengthOfB; j++) {
b.add(Integer.parseInt(st.nextToken()));
}
Collections.sort(a, Collections.reverseOrder());
Collections.sort(b, Collections.reverseOrder());
int result = 0;
int bIndex = 0;
for (int j = 0; j < lengthOfA; j++) {
while (bIndex < lengthOfB && a.get(j) <= b.get(bIndex)) { // 만약 A의 요소가 B의 요소보다 크다면 bIndex를 증가
bIndex++;
}
if (bIndex < lengthOfB) { // 작다면 bIndex가 가리키는 인덱스까지의 B 배열의 요소 개수를 result에 더함.
result += (lengthOfB - bIndex);
}
}
sb.append(result).append('\n');
}
System.out.println(sb.toString());
br.close();
}
}
문제를 한번에 적으니 조금 보기 힘든 감이 있다. 나눌 필요가 있는듯...