1. 알고리즘 문제에서 다루는 수학을 익히고, 적재적소에 활용할 수 있는 능력을 기를 수 있다.
2. 수학적 사고를 통해 컴퓨팅 사고를 할 수 있다.
3. GCD/LCM(최대공약수, 최소공배수), 순열/조합, 멱집합에 대해 이해하고 활용할 수 있다.
- 순열(Permutation)과 조합(Combination)
✔︎ 요소 n개 중에 m개를 선택하여 순서에 상관있게 뽑는 경우의 수
✔︎ 순서에 상관없이 요소 n개 중에 m개를 뽑는 경우의 수
✔︎ n!
: n에서부터 1씩 감소하여 1까지의 모든 정수의 곱 (n보다 작거나 같은 모든 양의 정수의 곱)
✔︎ 0!
= 1!
= 1
✔︎ A, B, C, D, E 5장의 카드
✔︎ 5장 중 3장을 선택하여 나열할 때 경우의 수
✔︎ case1: 순서를 생각하며 3장을 선택
// 반복문 코드
public static ArrayList<String[]> permutationLoop() {
// 순열 요소가 인자로 주어질 경우, 인자 그대로 사용하면 되지만, 인자가 주어지지 않고
// 문제 안에 포함되어 있을 경우 이런 식으로 직접 적어서 사용합니다.
String[] lookup = new String[]{"A", "B", "C", "D", "E"};
ArrayList<String[]> result = new ArrayList<>();
for (int i = 0; i < lookup.length; i++) {
for (int j = 0; j < lookup.length; j++) {
for (int k = 0; k < lookup.length; k++) {
if (i == j || j == k || k == i) continue;
String[] input = new String[]{lookup[i], lookup[j], lookup[k]};
result.add(input);
}
}
}
return result;
}
반복문의 개수
== 요소를 뽑는 개수
// 반복문 1개당 1개의 요소를 뽑습니다.
for (int i = 0; i < lookup.length; i++) {
String pick1 = lookup[i];
for (int j = 0; j < lookup.length; j++) {
String pick2 = lookup[j];
for (letintk = 0; k < lookup.length; k++) {
String pick3 = lookup[k];
if (i.equals(j) || j.equals(k) || k.equals(i)) continue;
result.add(new String[]{pick1, pick2, pick3});
}
}
}
중복된 요소
는 제거
✔︎ case2: 순서를 생각하지 않고 3장을 선택
// 반복문 코드
public static ArrayList<String[]> combinationLoop() {
// 조합 요소가 인자로 주어질 경우, 인자 그대로 사용하면 되지만, 인자가 주어지지 않고
// 문제 안에 포함되어 있을 경우 이런 식으로 직접 적어서 사용합니다.
String[] lookup = new String[]{"A", "B", "C", "D", "E"};
ArrayList<String[]> result = new ArrayList<>();
for(int i = 0; i < lookup.length; i++) {
for(int j = i + 1; j < lookup.length; j++) {
for(int k = j + 1; k < lookup.length; k++) {
String[] input = new String[]{lookup[i], lookup[j], lookup[k]};
result.add(input);
}
}
}
return result;
}
반복의 조건
(i=0, j=i+1, k=j+1)🚨 반복문으로 순열과 조합으로 만들 수 있지만, 분명 한계점이 존재❗️❗️❗️
✔︎ 개수가 늘어나면 반복문의 수도 늘어남
✔︎ 뽑아야 되는 개수가 변수로 들어왔을 때 대응이 어려움
∴ 순열과 조합은 재귀
를 사용하여 풀이하는 경우가 多❗️
- Algorithm with Math (페어프로그래밍)
✔︎ 문제
✔︎ 입력
int
타입의 게임 횟수 rounds✔︎ 출력
ArrayList가 담고 있는 배열
은 전체 경우의 수 중 한 가지 경우(총 세번의 선택)를 의미하는 배열String[]
은 rock
, paper
, sicssors
중 한 가지 이상을 요소로 갖는 배열✔︎ 주의사항
rock
>paper
>scissors
순✔︎ 입출력 예시
ArrayList<String[]> output = rockPaperScissors(5);
System.out.println(output);
/*
[
["rock", "rock", "rock", "rock", "rock"],
["rock", "rock", , "rock", "rock", "paper"],
["rock", "rock", , "rock", "rock", "scissors"],
["rock", "rock", "rock", "paper", "rock"],
["rock", "rock", "rock", "paper", "paper"],
["rock", "rock", "rock", "paper", "scissors"],
["rock", "rock", "rock", "scissors", "rock"],
// ...etc ...
]
*/
✔︎ 도출한 답
import java.util.*;
public class Solution {
public ArrayList<String[]> rockPaperScissors(int rounds) {
// TODO:
// 중복배열은 뽑은 것은 또 뽑을 수 있음
// 결과를 담을 ArrayList 선언
ArrayList<String[]> outcomes = new ArrayList<>();
return permutation(0, rounds, new String[rounds], outcomes);
}
// 순열 ArrayList<String[]> 메서드 선언 (현재 라운드, 끝 라운드, String[] 선택요소 담은 배열, ArrayList<String[]> 결과담을 리스트)
public ArrayList<String[]> permutation(int curRounds, int finRounds, String[] select, ArrayList<String[]> result){
// 현재라운드 = 끝라운드 같을 때
if(curRounds == finRounds){
// 새로운 배열에 선택요소 담은 배열 깊은 복사
String[] arr = Arrays.copyOf(select, select.length);
// 결과 담을 리스트에 새로운 배열 추가
result.add(arr);
// 결과담을 리스트 반환
return result;
}
// 가위바위보 배열 선언
String[] rps = new String[]{"rock", "paper", "scissors"};
// 가위바위보 배열 순회
for(int i=0; i<rps.length; i++){
// 새로운 배열에 i번째 요소 담음
String curPlay = rps[i];
// 선택요소 담은 배열의 현재 라운드에 i번째 값 추가
select[curRounds] = curPlay;
// 결과 담을 리스트에 라운드를 +1해서 재귀
result = permutation(curRounds+1, finRounds, select, result);
}
return result;
}
}
✔︎ 문제
1. N 가지의 재료 중에 단 M 가지만을 사용하여 조합한 모든 경우의 수 중 하나이다.
2. 재료는 0과 1로만 이루어진 숫자로 암호화가 되어 있고, 항상 1로 시작하며 복호화를 할 수 없다. (단, 0이 3개 이상인 재료는 상한 재료이기 때문에 제외한다.)
3. 재료의 순서에 따라 맛이 달라지기 때문에, 재료를 넣는 순서가 다르다면 다른 레시피이다.
✔︎ 입력
int
타입의 재료를 담은 배열111, 110, 1010, 10, 10110
int
타입의 1 이상 stuffArr 길이 이하의 자연수2
✔︎ 출력
ArrayList<Integer[]>
타입을 반환[1, 10, 11000, 1111]
이고, choiceNum은 2
라면 사용 가능한 재료는 [1, 10, 1111]
6
가지✔︎ 주의사항
null
을 반환null
을 반환[1, 10, 11000, 1111]
이 요소로 들어왔다면, 0이 세 개인 11000을 제외하고 [1, 10, 1111]
순서가 되어야 하며,[ [1, 10], [1, 1111], [10, 1], [10, 1111], [1111, 1], [1111, 10] ]
을 반환✔︎ 입출력 예시
ArrayList<Integer[]> output1 = newChickenRecipe(new int[]{1, 10, 1100, 1111}, 2);
System.out.println(output1);
/*
[
[1, 10], [1, 1100], [1, 1111],
[10, 1], [10, 1100], [10, 1111],
[1100, 1], [1100, 10], [1100, 1111],
[1111, 1], [1111, 10], [1111, 1100]
];
*/
ArrayList<Integer[]> output2 = newChickenRecipe(new int[]{10000, 10, 1}, 3);
System.out.println(output2); // []
ArrayList<Integer[]> output3 = newChickenRecipe(new int{11, 1, 10, 1111111111, 10000}, 4);
System.out.println(output3);
/*
[
[1, 10, 11, 1111111111],
[1, 10, 1111111111, 11],
[1, 11, 10, 1111111111],
[1, 11, 1111111111, 10],
[1, 1111111111, 10, 11],
[1, 1111111111, 11, 10],
[10, 1, 11, 1111111111],
[10, 1, 1111111111, 11],
[10, 11, 1, 1111111111],
[10, 11, 1111111111, 1],
[10, 1111111111, 1, 11],
[10, 1111111111, 11, 1],
[11, 1, 10, 1111111111],
[11, 1, 1111111111, 10],
[11, 10, 1, 1111111111],
[11, 10, 1111111111, 1],
[11, 1111111111, 1, 10],
[11, 1111111111, 10, 1],
[1111111111, 1, 10, 11],
[1111111111, 1, 11, 10],
[1111111111, 10, 1, 11],
[1111111111, 10, 11, 1],
[1111111111, 11, 1, 10],
[1111111111, 11, 10, 1],
]
*/
✔︎ 도출한 답
import java.util.*;
public class Solution {
public ArrayList<Integer[]> newChickenRecipe(int[] stuffArr, int choiceNum) {
//사용 가능한 재료만 넣을 변수를 선언합니다
ArrayList<Integer> freshArr = new ArrayList<>();
//stuffArr를 순회하며 사용 가능한 재료만 freshArr리스트에 추가합니다
for(int i = 0; i < stuffArr.length; i++) {
//stuffArr[i]를 String타입으로 변환한 이후
String str = String.valueOf(stuffArr[i]);
//해당 값을 char타입의 배열로 바꾼 이후, 0이 들어간 갯수만큼 element 배열에 추가합니다.
int[] element = str.chars().filter(c -> c == '0').toArray();
//element 배열의 숫자가 2 이하인 경우('0'이 2번 이하인 재료의 경우)
if(element.length <= 2) {
//freshArr에 해당 재료를 넣어줍니다.
freshArr.add(stuffArr[i]);
}
}
//재료가 들어간 List를 오름차순으로 정렬합니다.
Collections.sort(freshArr);
//사용할 수 있는 재료가 없거나, 재료의 양보다 사용해야 할 갯수가 더 많은 경우 null을 반환합니다.
if (freshArr.size() == 0 || freshArr.size() < choiceNum) return null;
//결과를 담을 리스트를 선언합니다.
ArrayList<Integer[]> result = new ArrayList<>();
//해당 재료의 사용 여부를 확인할 배열을 선언합니다.
boolean[] visited = new boolean[freshArr.size()];
//순열 메소드를 사용하여 모든 경우의 수를 구하고, 해당 자료를 반환합니다.
return permutation(choiceNum, new Integer[]{}, result, freshArr, visited, 0);
}
public ArrayList<Integer[]> permutation(int choiceNum, Integer[] bucket, ArrayList<Integer[]> result, ArrayList<Integer> freshArr, boolean[] visited, int depth) {
//사용한 재료의 숫자가 choiceNum에 도달한다면(재귀의 종료)
if(depth == choiceNum) {
//result에 재료가 저장된 bucket 배열을 넣어준 이후
result.add(bucket);
//해당 result를 반환합니다.
return result;
}
//사용할 수 있는 신선한 재료의 수만큼 반복합니다.
for(int i = 0; i < freshArr.size(); i++) {
//해당 재료를 사용하지 않았다면
if(!visited[i]) {
//해당 재료의 사용 여부를 체크하고
visited[i] = true;
//bucket에 사용한 재료를 넣어줍니다. (새로운 배열 선언)
Integer[] concatArray = Arrays.copyOf(bucket, bucket.length + 1);
concatArray[concatArray.length - 1] = freshArr.get(i);
//다시 재귀를 사용합니다.
result = permutation(choiceNum, concatArray, result, freshArr, visited, depth + 1);
//한번 재귀를 순회한 이후, 반복문을 다시 시작하기 위해 첫 시작재료의 사용여부를 false로 변경합니다.
visited[i] = false;
}
}
return result;
}
}
✔︎ 문제
1. 숫자로 이루어진 여러 장의 카드를 받음
2. 3장씩 카드를 고르고, 3장에 적힌 숫자들의 합이 소수인지 확인
3. 받아든 카드로 만들 수 있는 소수의 개수가 많은 사람이 이기게 됨
✔︎ 입력
int
타입을 요소로 가지는 배열2 < cards.length < 51
✔︎ 출력
int
타입을 리턴✔︎ 주의사항
✔︎ 입출력 예시
int output = boringBlackjack(new int[]{1, 2, 3, 4});
System.out.println(output); // 1
int output = boringBlackjack(new int[]{2, 3, 4, 8, 13});
System.out.println(output); // 3
✔︎ 도출한 답
import java.util.*;
public class Solution {
public int boringBlackjack(int[] cards) { // 1,2,3,4
// TODO:
// 소수가 되는 경우의 수를 선언
int count = 0;
// 고르는 카드 수만큼 반복문 생성
for(int i=0; i<cards.length-2; i++){ // i=1
// for(int i=0; i<cards.length; i++){ // 어차피 끝까지 돌아도 아무것도 안하고 나와서 상관없음
for(int j=i+1; j<cards.length-1; j++){ // j=2
// for(int j=i+1; j<cards.length; j++){ // 어차피 끝까지 돌아도 아무것도 안하고 나와서 상관없음
for(int k=j+1; k<cards.length; k++){ // k=3
// 3장의 합을 나타내는 변수 선언
int sum = cards[i]+cards[j]+cards[k]; // 1+2+3=6 1+2+4=7(count) 1+3+4=8 2+3+4=9
// 변수가 소수 판별에서 참일 경우 count 추가
if(isPrime(sum)) count++; // count = 1
}
}
}
return count;
}
// 소수 판별 boolean타입 선언
public boolean isPrime(int num){ // num = 9
// i = 2부터 비교해 나누어 떨어지면 소수 x
for(int i=2; i<=Math.sqrt(num); i++){ // i=2; i<=3; i++;
if(num % i == 0) return false; // 9%2 == 1, 9%3 == 0
}
return true;
}
}
✔︎ 문제
✔︎ 입력
int
타입의 양의 정수 (1 ≦ M ≦ 1,000,000,000)int
타입의 양의 정수 (1 ≦ N ≦ 1,000,000,000)✔︎ 출력
ArrayList<Integer[]>
타입의 output
을 리턴output[i]
은 다음과 같은 순서를 가진 길이 3의 배열[빼빼로를 받게 되는 직원의 수, 나누어 주는 아몬드 빼빼로의 수, 나누어 주는 누드 빼빼로의 수]
output
은 output[i][0]
, 즉 빼빼로를 받게 되는 직원의 수를 기준으로 오름차순으로 정렬✔︎ 입출력 예시
int M = 4;
int N = 8;
ArrayList<Integer[]> output = divideChocolateStick(M, N);
System.out.println(output);
// [[1, 4, 8], [2, 2, 4], [4, 1, 2]]
✔︎ 도출한 답
import java.util.*;
public class Solution {
public ArrayList<Integer[]> divideChocolateStick(int M, int N) {
// TODO:
// 최대공약수 -> 유클리드 호제법 사용
// 직원수, 아몬드 빼빼로수=M, 누드 빼빼로수=N
ArrayList<Integer[]> output = new ArrayList<>();
Integer[] arr = new Integer[3];
int member = 1;
while(output.size()!=3){
int temp = gcd(M, member);
int temp2 = gcd(N, member);
if(temp == -1 || temp2 == -1) continue;
arr = new Integer[] {member, M/temp, N/temp2};
output.add(arr);
}
return output;
}
public int gcd(int a, int b) {
int r = a % b;
if(r==0) {
return a;
}
else {
return gcd
}
}
/**
* 유클리드 호제법 구현 메서드
* @param bn : 큰 숫자
* @param sn : 작은 숫자
* @return
* 큰 숫자를 작은숫자로 나눈 값이 0이면 작은숫자 리턴, 아니면 재귀형태로 자신을 호출
*/
public int eucd(int bn, int sn) {
// 큰숫자를 작은숫자로 나눈 나머지를 계산
int r = bn % sn;
// 나머지가 0이면 작은숫자가 최대공약수이므로 작은숫자 리턴
if (r == 0) {
return sn;
} else {
// 나머지가 0 이상이면 재귀형태로 호출
// 이때 파라미터는 작은숫자와 나머지를 넣어줌
return eucd(sn, r);
}
}
}
✔︎ 문제
✔︎ 입력
String
타입의 영문으로 된 반찬이 나열되어 있는 배열✔︎ 출력
ArrayList<String[]>
타입을 리턴해야 함ArrayList
✔︎ 주의사항
✔︎ 입출력 예시
ArrayList<String[]> output = missHouseMeal(new String[]{"eggroll", "kimchi", "fishSoup"});
System.out.println(output);
/*
[ [],
[eggroll, fishSoup, kimchi],
[eggroll, fishSoup],
[eggroll, kimchi],
[eggroll],
[fishSoup, kimchi],
[fishSoup],
[kimchi],
]
*/
✔︎ 도출한 답
import java.util.*;
public class Solution {
public ArrayList<String[]> missHouseMeal(String[] sideDishes) {
//search 함수에서 사용할 빈 스택을 선언합니다.
Stack<String> stack = new Stack<>();
//결과를 담을 ArrayList를 선언합니다.
ArrayList<String[]> result = new ArrayList<>();
//재료들을 오름차순으로 정렬합니다.
Arrays.sort(sideDishes);
// 빈 스택과 0 번째 인덱스, 정렬된 재료로 구성된 배열, 결과를 담을 List를 인자로 받는 재귀 함수를 실행합니다.
result = search(stack, 0, sideDishes, result);
// 결과를 오름차순 순서로 정렬합니다.
result.sort((o1, o2) -> Arrays.toString(o1).compareTo(Arrays.toString(o2)));
//결과를 반환합니다.
return result;
}
// 모든 조합을 검사하는 재귀 함수를 작성합니다.
public ArrayList<String[]> search(Stack<String> stack, int idx, String[] sideDishes, ArrayList<String[]> result) {
// 재귀 함수이기 때문에 탈출 조건을 만듭니다.
if (idx >= sideDishes.length) {
// 만약, idx와 sideDishes의 길이가 같거나 크다면(마지막까지 검토한 경우) 스택을 배열로 변환한 후, 해당 스택을 result에 넣어줍니다.
String[] arr = stack.toArray(new String[0]);
result.add(arr);
return result;
} else {
// idx가 부분집합에 포함된 경우
stack.push(sideDishes[idx]);
search(stack, idx + 1, sideDishes, result);
// idx가 부분집합에 포함되지 않은 경우
stack.pop();
search(stack, idx + 1, sideDishes, result);
}
return result;
}
}
☞ 기초적인 수학적 지식을 활용하여 알고리즘 문제를 푸는 연습을 했다. 혼자서 했다면 정말 시간도 오래 걸리고 머리가 터졌었겠지만, 페어 프로그래밍을 통해 그나마 선방했다 🥲 서로 어떻게 알고리즘을 적용시켜 문제를 해결할지 아이디어를 공유하고 실수들을 잡아주며 시간을 꽤나 단축시킨 느낌이 들었다. 물론 30분 이상 고민하며 막힌 부분에 대해서는 크루님께 질문도 드려가며 문제를 해결하기도 했다 😅 나중에 코딩 테스트를 준비할 때 얼마나 힘들지 미리 경험해보는 느낌이다.. 알고리즘 파트가 현업에서 필요한 개발을 위해 필수적인 부분은 아니지만, 컴퓨팅적 사고를 익히며 보다 성장할 수 있는 요소라고 생각하며 크게 부담감을 갖진 않지만, 몰두하며 해결해나갔던 것 같다.
오늘로써 자료구조 및 알고리즘 파트를 마무리 짓고 내일부터는 네트워크 파트로 넘어간다. 정말 많이 어렵고 머리가 하얘질만큼 고생했던 파트이기도 했기에 교육을 마무리 짓고도 반드시 복습이 필요한 파트인 것 같다.
・ 웹 애플리케이션 작동 원리