

T
N: even num (4 ≤ N ≤ 16)
N개의 줄에는 N * N개의 시너지 Sij값
10
4
0 5 3 8
4 0 4 1
2 5 0 3
7 2 3 0
1. A 요리에 사용할 수 있는 재료의 조합 찾기
2. B 요리에 사용할 수 있는 재료의 조합 찾기
3. 음식의 맛
i=j 경우를 제외한 순열을 만들어 더하기4. 최적의 조합 찾기
public class Solution {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine());
for(int t=1; t<=T; t++) {
// [input]
int N = Integer.parseInt(br.readLine());
int[][] sTable = new int[N+1][N+1];
for(int r=1; r<=N; r++) {
String[] temp = br.readLine().split(" ");
for(int c=1; c<=N; c++) {
sTable[r][c] = Integer.parseInt(temp[c-1]);
}
}
// [logic]
List<List<Integer>> IngredientsForA = choosenIngredientsForA(N);
List<List<Integer>> IngredientsForB = choosenIngredientsForB(N, IngredientsForA);
List<Integer> tastesOfFoodA = tastesOfFood(sTable,IngredientsForA);
List<Integer> tastesOfFoodB = tastesOfFood(sTable,IngredientsForB);
int min = findBestCombi(tastesOfFoodA, tastesOfFoodB);
System.out.printf("#"+t+" "+min+"\n");
}// for: t
}// main
// A 요리 사용할 수 있는 재료의 조합 : N개의 식재료 중 N/2개 선택
public static List<List<Integer>> choosenIngredientsForA (int N) {
List<List<Integer>> ij = new ArrayList<>();
int r = N/2;
dfs(N,r,1, new ArrayList<>(), ij);
return ij;
}
public static void dfs(int N, int r, int start, List<Integer> current, List<List<Integer>> ij) {
// base-case
if(current.size()==r) {
// XX ij.add(current) XX
ij.add(new ArrayList<>(current));
}
// recursion
for(int i=start; i<=N; i++) {
current.add(i);
dfs(N,r,i+1, current,ij);
current.remove(current.size()-1);
}
}
// B요리에 사용할 수 있는 재료의 조합
public static List<List<Integer>> choosenIngredientsForB (int N, List<List<Integer>> choosenIngredientsForA ){
List<List<Integer>> choosenIngredientsForB = new ArrayList<>();
for( List<Integer> ingredientsforA : choosenIngredientsForA) {
List<Integer> ingredientsforB = new ArrayList();
for(int i=1; i<=N; i++) {
if(!ingredientsforA.contains(i)) {
ingredientsforB.add(i);
}
}
choosenIngredientsForB.add(ingredientsforB);
}
return choosenIngredientsForB;
}
// 음식의 맛
// 같은 식재료는 같이 사용 x
// 문제 조건: (1 ≤ i ≤ N, 1 ≤ j ≤ N, i ≠ j)
// 각 조합에서 (i=j)를 제외한 순열을 만들어 더하기
public static List<Integer> tastesOfFood (int[][] sTable, List<List<Integer>> choosenIngredients) {
List<Integer> result = new ArrayList<>();
for(List<Integer> ingredients: choosenIngredients) {
int sum = 0;
int n = ingredients.size();
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
// i,j 값이 아니라 그 번째의 값!
int ith = ingredients.get(i);
int jth = ingredients.get(j);
if(ith != jth) {
sum+= sTable[ith][jth];
}
}
}
result.add(sum);
}
return result;
}
public static int findBestCombi (List<Integer>tastesOfFoodA,List<Integer>tastesOfFoodB ) {
int min = Integer.MAX_VALUE;
for(int i=0; i<tastesOfFoodA.size();i++) {
int abs = Math.abs(tastesOfFoodA.get(i)-tastesOfFoodB.get(i));
if(abs < min) min = abs;
}
return min;
}
}
개선 포인트
백트래킹 활용 -> 계산 과정 최적화
1) 메모리 효율성
List<List>를 사용해 모든 조합을 저장했던 기존 코드와 달리,
이 코드는 재료의 사용 여부를 나타내는 boolean 배열(visited) 하나만 사용
2) 시간 효율성
findMinDifference 재귀 함수가 N개의 재료 중 N/2개를 선택하는 모든 조합을 생성
각 조합이 완성될 때마다 (count == N / 2), calculateTasteDifference 메서드를 호출하여 맛 점수를 계산하고 최솟값을 갱신
이 방식은 중간에 불필요한 리스트 생성 및 탐색 과정을 모두 제거할 수 있음
public class Solution {
static int N;
static int[][] sTable;
static boolean[] visited;
static int minDifference;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine());
for (int t = 1; t <= T; t++) {
N = Integer.parseInt(br.readLine());
sTable = new int[N + 1][N + 1];
visited = new boolean[N + 1];
minDifference = Integer.MAX_VALUE;
for (int r = 1; r <= N; r++) {
String[] temp = br.readLine().split(" ");
for (int c = 1; c <= N; c++) {
sTable[r][c] = Integer.parseInt(temp[c - 1]);
}
}
findMinDifference(1, 0);
System.out.printf("#" + t + " " + minDifference + "\n");
}
}
/**
* N개의 재료 중 N/2개를 선택하는 모든 조합을 탐색
* 요리 A와 B의 맛 점수 차이의 최솟값을 찾기
*
* @param start 현재 재료 선택을 시작할 인덱스
* @param count 현재까지 선택한 재료의 수
*/
public static void findMinDifference(int start, int count) {
if (count == N / 2) {
calculateTasteDifference();
return;
}
for (int i = start; i <= N; i++) {
visited[i] = true;
findMinDifference(i + 1, count + 1);
visited[i] = false;
}
}
/**
* 선택된 재료 (visited가 true)와 선택되지 않은 재료 (visited가 false)를
* 각각 요리 A와 B로 나누어 맛 점수를 계산하고 차이의 최솟값을 갱신
*/
public static void calculateTasteDifference() {
int tasteA = 0;
int tasteB = 0;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
// 요리 A의 맛 점수 계산
if (visited[i] && visited[j]) {
tasteA += sTable[i][j];
}
// 요리 B의 맛 점수 계산
else if (!visited[i] && !visited[j]) {
tasteB += sTable[i][j];
}
}
}
int diff = Math.abs(tasteA - tasteB);
minDifference = Math.min(minDifference, diff);
}
1. 참조 자료형: 리스트 추가 방법
ij.add(current)라고 하면 current 리스트의 참조(주소) 가 그대로 저장됨new ArrayList<>(current)를 사용해서 current의 복사본 넣기2. 백트래킹 패턴
for(int i=start; i<=N; i++) {
current.add(i);
dfs(N, r, i+1, current, ij);
current.remove(current.size()-1);
}