최적 부분 구조를 가짐
큰 문제의 해답이 작은 문제의 해답들로 구성되는 경우!
집 ~ 목표 지점까지의 최단 경로의 경우의 수는 그 이전 지점들로 오는 경우의 수들의 합으로 결정됨
문제의 제약 조건: 오른쪽 또는 아래쪽으로만 이동 가능
=> 해당 칸으로 이동하는 경우의 수 = 왼쪽에서 오는 경우 + 위쪽에서 오는 경우
DFS 탐색으로 푸는 경우
모든 경로를 단순 재귀 호출 (DFS)로 탐색한다면, 여러 경로를 탐색하는 과정에서 동일한 중간지점까지 도달하는 경우의 수를 반복해서 계산하게 됨
DP를 사용할 경우 한 번 계산된 결과를 메모이제이션해두고, 필요할 때마다 저장된 값을 재활용하여 계산 속도를 높임.
// 1. dp 배열 선언
int[][] dp = new int[n][m];
// 2. 물 웅덩이에 -1 표시 (0-based index)
for(int[] puddle: puddles){
int x = puddle[0]-1;
int y= puddle[1]-1;
dp[y][x] =-1;
}
// 3. 시작위치 표시: 집(0,0)에 1 표시
if(dp[0][0]!=-1){
dp[0][0]=1;
}else{
return 0;
}
for(int i=0; i< n; i++){
for(int j=0; j<m; j++){
int leftPaths=0;
int topPaths=0;
// 0) 시작점 채워져 있음 -> pass
if(i==0 && j==0) continue;
// 1) 물 웅덩이라면
if(dp[i][j]==-1){
dp[i][j]=0; // 경로 개수 = 0
continue; // 다음 칸으로
}
// 3) + 위쪽 칸 경로 개수
if(i>0){
topPaths = dp[i-1][j];
}
// 4) + 왼쪽 칸 경로 개수
if(j>0){
leftPaths=dp[i][j-1];
}
// 5) !! 합산 & 모듈러 연산
dp[i][j] = (topPaths + leftPaths) % mod;
}
}
answer = dp[n-1][m-1];
return answer;
class Solution {
public int solution(int m, int n, int[][] puddles) {
final int mod = 1000000007;
int answer = 0;
// !!
// m : 가로 길이 => 열
// n : 세로 길이 => 행
// 학교의 위치: (m,n)-> (4,3) => (3,4)
// 1. dp 배열 선언
int[][] dp = new int[n][m];
// 2. 물 웅덩이에 -1 표시 (0-based index)
for(int[] puddle: puddles){
int x = puddle[0]-1;
int y= puddle[1]-1;
dp[y][x] =-1;
}
// 3. 시작위치 표시: 집(0,0)에 1 표시
if(dp[0][0]!=-1){
dp[0][0]=1;
}else{
return 0;
}
// 4. dp 테이블 채우기
for(int i=0; i< n; i++){
for(int j=0; j<m; j++){
int leftPaths=0;
int topPaths=0;
// 0) 시작점 채워져 있음 -> pass
if(i==0 && j==0) continue;
// 1) 물 웅덩이라면
if(dp[i][j]==-1){
dp[i][j]=0; // 경로 개수 = 0
continue; // 다음 칸으로
}
// 3) + 위쪽 칸 경로 개수
if(i>0){
topPaths = dp[i-1][j];
}
// 4) + 왼쪽 칸 경로 개수
if(j>0){
leftPaths=dp[i][j-1];
}
// 5) !! 합산 & 모듈러 연산
dp[i][j] = (topPaths + leftPaths) % mod;
}
}
answer = dp[n-1][m-1];
return answer;
}
}
ArrayIndexOutOfBoundsException 발생.O(N*M))O(1))