[코딩테스트] 브루트 포스(N과 M)
reference : 2021 코딩테스트 기초(최백준) 강의를 공부하며 정리한 내용입니다.
- N과 M은 재귀에 많이 사용되기 때문에 중요하다.
- 브루트 포스에 재귀로 풀 수 있는 문제
- 순서 : N가지 M개 뽑을 때 순서가 중요한 문제 O(N!)
- 선택 : N가지를 다 할 수 있는데 M개를 어떤 순서로 할지 O(2ⁿ)
N과 M 1 (15649번)
https://www.acmicpc.net/problem/15649
문제
1부터 N까지 자연수 중에서 중복 없이 M개를 고른 수열을 모두 구하는 문제
입력
첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 8)
출력
한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.
풀이
- 순서와 관련된 브루트포스 문제, 재귀 이용해서 풀 수 있음.
- 재귀 함수 : 각각 자리에 올 수 있는 수를 정하게 만들어 주면 된다. 어떤 위치에 올 수 있는 수를 결정해주는 것. 이 문제에선 1~N 중에서 앞에서 사용하지 않은 수.
- 다음 값으로 갈 때 변하는 값이 무엇인지 정해줘야 한다. 이 문제에선 위치가 변한다. 위치 : 함수의 인자 / 사용한 수가 변한다. 앞에서 사용한 숫자를 잘 관리해줘야 한다.
- 재귀함수에서 가장 중요한 것 : 함수 호출하기 전 그 함수 호출을 위한 준비를 미리 하는 것과 종료조건.
bool c[10]; // i를 사용했으면 true, 사용 안했으면 false
int a[10]; // 수열을 저장
void go(int index, int n, int m){ // index 번째의 수를 결정
if(index==m){
//수열을 출력
return; // 종료
}
for(int i=1; i<n; i++){ //1-n 중에서 앞에서 사용하지 않은 수를 찾는다
if(c[i]) continue; // 사용했으니 건너뛰기
//수 i를 사용할 수 있다
c[i] = true; //index에서 수를 사용했으니 index+1로 가기 전에 사용했다 표시
a[index] = i;
go(index+1, n, m); // index에 i가 온 뒤 일어날 모든 일들 재귀로 여기서 다 처리, index엔 현재 i가 아닌 다른 i가 와야 한다.
c[i] = false; //그래서 i 사용하지 않았다고 표현 바꿔줌.
}
}
- 종료조건 : 0~M-1 (M개의 수) 까지 다 진행하고 M번째 호출한 경우. 위치가 M이 되면 M개의 수를 다 골랐다 = 종료조건이 된다.
- 시간복잡도 : O(N!)
-
자바코드
import java.util.*; public class Main { static boolean check[] = new boolean[10]; static int arr[] = new int[10]; public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int m = sc.nextInt(); go(0, n, m); } private static void go(int index, int n, int m) { if (index == m) { for(int i=0; i<m; i++) { System.out.print(arr[i]+" "); } System.out.println(); return; } for (int i = 1; i <= n; i++) { if (check[i]) continue; check[i] = true; arr[index] = i; go(index + 1, n, m); check[i] = false; } } }
N과 M 2 (15650번)
https://www.acmicpc.net/problem/15650
문제
1부터 N까지 자연수 중에서 중복 없이 M개를 고른 수열을 모두 구하는 문제(오름차순)
입력
첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 8)
출력
한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.
풀이
- 함수 역할이 어떤 위치에 올 수 있는지 결정하는 거라면, 위치 하나 바뀌었을 때 변하는 게 뭔지 확인해야 한다. 1번과 비교해서 변하는 값이 하나 더, 값의 시작점이 더 있어야 함.
- start : index번째에 올 수 있는 수는 start부터 N까지
import java.util.*;
public class Main {
static boolean check[] = new boolean[10];
static int arr[] = new int[10];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
go(0, 1, n, m);
}
private static void go(int index, int start, int n, int m) { //start 추가
if (index == m) {
for (int i = 0; i < m; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
return;
}
for (int i = start; i <= n; i++) { // i = start
arr[index] = i;
go(index + 1, i + 1, n, m); // index+1, i+1
// start로 다음수를 검사하게 했으니 check 없애도 됨
}
}
}
- 순서가 아닌 선택 관련된 문제로 볼 수 있음(오름차순이니 어떤 수를 사용할지 안할지 결정하면 그 수열은 하나밖에 안 나오니까)
- 선택일 땐 위치가 중요한 게 아니라 어떤 수를 사용할지 결정하면 되기에 기준은 그 수!
- 선택한지, 선택 안 하는지 / 선택한 수의 갯수
- index : 수 index, selected : 선택한 수의 개수, O(N²)
N과 M 3 (15651번)
https://www.acmicpc.net/problem/15651
문제
1부터 N까지 자연수 중에서 M개를 고른 수열을 모두 구하는 문제(중복선택 가능)
입력
첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 7)
출력
한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.
풀이
import java.util.*;
public class Main {
static int arr[] = new int[10];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
System.out.println(go(0, n, m));
}
private static StringBuilder go(int index, int n, int m) {
if (index == m) {
StringBuilder add = new StringBuilder();
for (int i = 0; i < m; i++) {
add.append(arr[i]+" ");
}
add.append("\n");
return add;
}
StringBuilder result = new StringBuilder();
for (int i = 1; i <= n; i++) {
arr[index] = i;
result.append(go(index + 1, n, m));
}
return result;
}
}
-> 1번 풀이에서 중복체크만 없애주면 되는데 문제는 StringBuilder 사용하지 않고 printout을 사용하면 시간초과가 뜬다. BufferedReader를 사용하지 않아도 StringBuilder만 사용하면 시간초과가 뜨지 않는다. 15줄에서 StringBuilder를 생성하는 건 초기화를 위해서임.
N과 M 4 (15652번)
https://www.acmicpc.net/problem/15652
문제
1부터 N까지 자연수 중에서 M개를 고른 수열을 모두 구하는 문제(중복선택 가능, 비내림차순)
입력
첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 8)
출력
한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.
풀이
import java.util.*;
public class Main {
static int arr[] = new int[10];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
System.out.println(go(0, 1, n, m));
}
private static StringBuilder go(int index, int start, int n, int m) {
if (index == m) {
StringBuilder add = new StringBuilder();
for (int i = 0; i < m; i++) {
add.append(arr[i]+" ");
}
add.append("\n");
return add;
}
StringBuilder result = new StringBuilder();
for (int i = start; i <= n; i++) {
arr[index] = i;
result.append(go(index + 1, i, n, m)); //중복 허용이기 때문에 i+1였던 2번과 달리 i라고 작성
}
return result;
}
}
- 문제 2번처럼 선택 개념으로 풀이 가능. 중복선택 가능이라 사용/사용x 경우로 나누면 안됨. 수를 사용했으면 몇 개를 사용했는지 기록해야함.
N과 M 5 (15654번)
https://www.acmicpc.net/problem/15654
문제
N개의 서로 다른 자연수 중에서 M개를 고른 수열을 모두 구하는 문제
입력
첫째 줄에 자연수 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 8)
둘째 줄에 N개의 수가 주어진다. 입력으로 주어지는 수는 10,000보다 작거나 같은 자연수이다.
출력
한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.
풀이
- 출력할 때 입력값을 인덱스로 가정해서 실제 수로 바꿔 출력해주면 된다. 3번 응용문제.
import java.util.*;
public class Main {
static int arr[] = new int[10];
static boolean check[] = new boolean[10];
static int input[];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int input[] = new int[n];
for(int i=0; i<n; i++) {
input[i] = sc.nextInt();
}
Arrays.sort(input);
System.out.println(go(0,n,m,input));
}
private static StringBuilder go(int index, int n, int m, int[] input) {
if (index == m) {
StringBuilder add = new StringBuilder();
for (int i = 0; i < m; i++) {
add.append(arr[i]+" ");
}
add.append("\n");
return add;
}
StringBuilder result = new StringBuilder();
for (int i = 1; i <= n; i++) {
if(check[i])
continue;
check[i] = true;
arr[index] = input[i-1];
result.append(go(index + 1, n, m, input));
check[i] = false;
}
return result;
}
}
N과 M 6,7,8번은 주말에 풀고 업로드 할 예정임.
댓글남기기