제일 약한 파트 3가지 백트래킹,dp,그리디에 대해 알아보자!
현재 상태에서 가능한 모든 후보군을 따라들어가며 탐색
정의는 이렇다. 생각나는대로 일단 정의해보자.
n x n 체스판 위에 n개의 퀸을 서로 공격할 수 없게 놓을 수 있는 경우의 수
어떻게 구할지 천천히 생각해보자. (4x4라고 일단 생각)
...모든 체스판 좌표에 퀸을 한번씩 놓아본다.
let cnt = 0;
const column = Array(n);
const leftBotToRightTop = Array(n*2 -1);
const leftTopToRightBot = Array(n*2 -1);
const queen = (j) => {
if(n === j){
cnt++;
return;
}
for(let i = 0; i<n; i++){
//같은 열, 좌하단에서 우상단으로 가는 대각선, 좌상단에서 우하단으로 가는 대각선에 퀸이 존재하는지 체크!
if(column[i] || leftBotToRightTop[i + j] || leftTopTopRightBot[i - j + n -1]) continue;
column[i] = 1;
leftBotToRightTop[i + j] = 0;
leftTopTopRightBot[i - j + n -1] = 1;
queen(j+1);
column[i] = 0;
leftBotToRightTop[i + j] = 0;
leftTopTopRightBot[i - j + n -1] = 0;
}
}
백트래킹의 핵심은 가지치기다. 어떤 조건을 탐색하지 않을 지가 핵심이다.
따라서 위의 경우 열, 대각선2개를 어떻게 방문처리 할 건지가 핵심이었다.
상태 공간 트리를 만드는 건 부차적일 뿐이다.
DynamicProgramming. 실제 뜻과는 많이 다르다. Dynamic이라는 이름이 멋있어보여서 그렇게 제출했다나 뭐라나...?
아무튼 정의로 들어가보자.
*하위 문제를 먼저 푼 후 결과를 쌓아올려 주어진 문제를 해결함.
혼자 생각해본 정의는 이렇다.
dp[1], dp[2]
등 초기값을 넣고 계산하는 방식이 bottom-up이다. top-down은 재귀적으로 푼다는데, 해본적이 없다.결국 점화식을 찾아낸 후, 첫 항 부터 천천히 찾아내면 된다. <<< 이게 제일 어려움...😫
dp로 푸는 문제면서, 과정을 기록해야한다.
다시말해 새로운 배열이 필요함.
//dp[i]는 i를 만드는 최소한의 연산 횟수
const dp = Array(10**6 + 1);
const pre = Array(10**6 + 1);
dp[1] = 0;
for(let i = 2; i <= n; i++){
dp[i] = dp[i-1] + 1;
pre[i] = i - 1; //정수 i가 i-1번째 정수에서 연산(+1)을 했다.
//i가 2로 나누어 떨어지며, dp[i]의 연산횟수가 dp[i/2] + 1보다 클때
if(i%2 === 0 && dp[i] > dp[i/2] + 1){
dp[i] = dp[i/2] + 1;
pre[i] = i/2; //정수 i가 i/2 번째 정수에서 연산(*2)을 했다.
}
//2나 3으로 나눠지는경우 3으로 나눠질때가 더 최솟값이므로 아래에서 if문을 걸어준다.
if(i%3 === 0 && dp[i] > dp[i/3] + 1){
dp[i] = dp[i/3] + 1;
pre[i] = i/3 //정수i가 i/3번째 정수에서 연산(*3)을 했다.
}
}
let cur = n;
const result = [];
//경로배열은 값이 곧 인덱스라서 이렇게 넣어줍니다.
while(cur !== 1){
result.push(cur);
cur = pre[cur];
}
dp로 풀리는지 안풀리는지 판단하는건 결국 경험이다. 조바심 내지 말고 계속 풀어보자.
그리디는 당장 선택할 수 있는 최적의 해를 선택해나가는 문제다.
항상 dp와 굉장히 헷갈린다. 시간복잡도가 1억에 가깝다면 dp일까? 잘 모르겠다.
수학(조합 순열 최소공배수 등), 이분탐색, 투포인터 로 만나뵙겠습니다