차곡차곡 성 쌓기
article thumbnail

1. 문제

 

 

요약

양옆, 밑으로 가면서 얻을 수 있는 최대 가치의 합 구하기

 

 

2. 접근

몇일 전 코테 봤을 때 문제랑 비슷해서 다시 풀어봤다. 과거에 풀었던건데 코테 때는 생각도 안났다.

 

고민했던 부분

- BFS하면 무조건 시간초과 날 것 같음. 그러면 DP인데 어떻게 구하지?

- DP로 중복 방문을 어떻게 체크하지?

- 다음 칸으로 가는 경우가 3가지인데, 어떤 기준으로 갱신을 해주지?

 

이런 문제 처럼 그래프 문제 같은데, 그렇게 하다간 분명 시간초과가 날것 같을 때. 

문제에 힌트가 있다. 바로 위로는 못 간다는 것이다. 무조건 아래로만 갈 수 있다. 

 

 

3. 알고리즘

위로는 안가니깐 밑으로 내려가기 전에, 해당 층에서 얻을 수 있는 최대 이익을 구한다.

 

방문했던 칸은 다시 안간다고 했다. 그러면 갈 수 있는 방법은, 

1. 위에서 아래로 내려온다.
2. 오른쪽으로 쭉 간다.
3. 왼쪽으로 쭉 간다.

이다.

 

오른쪽 갔다가 왼쪽가는 경우는 절대 없다는 것이다. 그러므로 3가지 경우에 대해서 구현하면 된다.

 

1. 위에서 아래로

// 위에꺼 밑으로 그대로 전달
for(int j=0; j<M; j++){
    dp[i][j] = dp[i-1][j] + map[i][j];
}

(i는 i층을 나타낸다.)

 

우선 밑으로는 무조건 내려와야하니, dp 값을 `dp[i-1][j] + map[i][j]`로 초기화 해준다.

 

2.  오른쪽으로 쭉 간다.

여기부터 기존의 DP랑 좀 다르다. 우선 오른쪽으로 쭉 갔을 경우의 값을 별도로 저장할 수 있도록 `leftToRight` 배열을 만든다. 

// -> 방향으로 전진할 때 갱신
int [] leftToRight = new int[M];
leftToRight[0] = dp[i][0];
for(int j=1; j<M; j++){
    // 여기서 멈추거나, 여기까지 전진해오거나
    leftToRight[j] = Math.max(dp[i][j], leftToRight[j-1] + map[i][j]);
}

여기서 목표는 `leftToRight[j]`값에 최대값을 갱신하는 것이다. 그러므로 최대이익의 경우를 따져봐야 한다.

왼쪽에서 오던 칸을 받을 것인지, 받지 않을 것인지 선택한다. 굳이 더 높은 값이 아니라면 위에서 아래온 내려온 값을 선택한다.

 

3. 왼쪽으로 쭉 간다.

마찬가지로 왼쪽으로 쭉 갔을 경우의 값을 저장할 수 있도록 별도의 배열을 선언한다.

// <- 방향으로 전진할 때 갱신
int [] rightToLeft = new int[M];
rightToLeft[M-1] = dp[i][M-1];
for(int j=M-2; j>=0; j--){
    // 여기서 멈추거나, 여기까지 전진해오거나
    rightToLeft[j] = Math.max(dp[i][j], rightToLeft[j+1] + map[i][j]);
}

 

 

그리고 중요한 것은, 두 배열 중 최대값으로 갱신해주는 것이다.

for(int j=0; j<M; j++){
    dp[i][j] = Math.max(leftToRight[j], rightToLeft[j]);
}

 

이로써 i층까지의 탐색이 완료됐다. 각 dp[i][j]칸에는 중복을 방문하지 않으면서 현재 칸에서 얻을 수 있는 최대 이익이 저장된다.

 

 

4. 전체 코드

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class Main{

    public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String[] args) throws IOException {
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());

        int [][] map = new int[N][M];

        for(int i=0; i<N; i++){
            st = new StringTokenizer(br.readLine());
            for(int j=0; j<M; j++){
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        int [][] dp = new int[N][M];
        // 1층 초기화
        dp[0][0] = map[0][0];
        for(int i=1; i<M; i++){
            dp[0][i] = dp[0][i-1] + map[0][i];
        }

        for(int i=1; i<N; i++){
            // 위에꺼 밑으로 그대로 전달
            for(int j=0; j<M; j++){
                dp[i][j] = dp[i-1][j] + map[i][j];
            }

            // -> 방향으로 전진할 때 갱신
            int [] leftToRight = new int[M];
            leftToRight[0] = dp[i][0];
            for(int j=1; j<M; j++){
                // 여기서 멈추거나, 여기까지 전진해오거나
                leftToRight[j] = Math.max(dp[i][j], leftToRight[j-1] + map[i][j]);
            }
_
            // <- 방향으로 전진할 때 갱신
            int [] rightToLeft = new int[M];
            rightToLeft[M-1] = dp[i][M-1];
            for(int j=M-2; j>=0; j--){
                // 여기서 멈추거나, 여기까지 전진해오거나
                rightToLeft[j] = Math.max(dp[i][j], rightToLeft[j+1] + map[i][j]);
            }

            for(int j=0; j<M; j++){
                dp[i][j] = Math.max(leftToRight[j], rightToLeft[j]);
            }
        }

        System.out.println(dp[N-1][M-1]);
    }

}

profile

차곡차곡 성 쌓기

@nagrang

포스팅이 좋았다면 좋아요