PS/LeetCode

[LeetCode] 42. Trapping Rain Water

램램 2023. 1. 21. 21:11

https://leetcode.com/problems/trapping-rain-water

Stack

문제를 보고 valid parenthesis 같은 느낌이 나서 stack으로 풀어야겠다!

라고 접근했는데 대략적인 방법은 맞았지만 실제로 완전히 이해하고 구현하기까지 엄청 오래 걸렸던 문제다..🥲
엄청 헷갈려서 그냥 다 프린트를 찍어보고 나서야 좀 감이 왔다.

일단 스택으로 풀 때 중요한 것은

1) 내려갔다가 다시 올라올 때 물의 양을 계산하는 것
2) 물의 양은 왼쪽, 오른쪽 기둥 중 더 낮은 기둥을 기준으로 계산하는 것
이다.

class Solution:
    def trap(self, height: List[int]) -> int:
        stack = []
        water = 0

        for i in range(len(height)):
            while stack and height[stack[-1]] < height[i]:
                last = stack.pop()

                if len(stack) == 0:
                    print("break here! stack is empty ", stack)
                    break

                # 왼쪽의 가장 높은 기둥
                left = stack[-1] 
                # 왼쪽과 오른쪽 사이의 너비
                width = i - left -1
                depth = min(height[i], height[left]) - height[last]

                water += depth * width
                print(f'   i : {i}, water : {water} , stack: {stack}, last: {last}, width: {width}, depth : {depth}')

            stack.append(i)
            print(f'i : {i}, water : {water} , stack: {stack}')



        return water

프린트 결과 값을 확인해보자.

# input : [0,1,0,2,1,0,0,1,1,3]

i : 0, water : 0 , stack: [0]
break here! stack is empty  []
i : 1, water : 0 , stack: [1]
i : 2, water : 0 , stack: [1, 2]
   i : 3, water : 1 , stack: [1], last: 2, width: 1, depth : 1
break here! stack is empty  []
i : 3, water : 1 , stack: [3]
i : 4, water : 1 , stack: [3, 4]
i : 5, water : 1 , stack: [3, 4, 5]
i : 6, water : 1 , stack: [3, 4, 5, 6]
   i : 7, water : 1 , stack: [3, 4, 5], last: 6, width: 1, depth : 0
   i : 7, water : 3 , stack: [3, 4], last: 5, width: 2, depth : 1
i : 7, water : 3 , stack: [3, 4, 7]
i : 8, water : 3 , stack: [3, 4, 7, 8]
   i : 9, water : 3 , stack: [3, 4, 7], last: 8, width: 1, depth : 0
   i : 9, water : 3 , stack: [3, 4], last: 7, width: 4, depth : 0
   i : 9, water : 8 , stack: [3], last: 4, width: 5, depth : 1
break here! stack is empty  []
i : 9, water : 8 , stack: [9]

우선 현재 기둥이 이전보다 더 높을 때 물의 양을 계산해야하고 (= 기둥높이가 내려갔다가 올라올 때), 이전 값이 있어야하기 때문에 while 문 조건은 비교적 직관적으로 찾을 수 있었다.

가장 헷갈렸던 것은 그러면 현재 기준 기둥이 아닌 왼쪽의 기둥 높이는 어떻게 구하지? 였다.

 

빠른 이해를 위해 그림을 보자..

6번-7번으로 넘어갈 때 물의 양이 한번 계산되고, 다시 8번에서 9번으로 넘어갈 때 한 번더 2층에 해당하는 물의 양이 계산된다.

 

프린트 문을 보면 stack중 pop 된 값을 제외한 마지막 값을 left로 쓰면 다시 올라가는 구간에서만 height가 인식되는 것을 볼 수 있다.

height에 변화가 생길 때만 depth에 값이 들어가는 것이다.

 

역시 헷갈릴 땐 프린트로 다 찍어보는게 좋은 것 같다..

Two Pointer

찾아보니 투 포인터로 푸는 방법이 좀 더 간결해 보인다.
왼쪽, 오른쪽에서 각각 출발해 더 높이가 낮은 쪽이 한칸씩 전진해서 가장 높은 기준점에서 만나는 방식이다.
가장 높은 점에서 만나는 것이 보장되어 있고, 둘 중 더 낮은 쪽이 이동하기 때문에 지나오는 깊이들을 모두 다 더할 수 있다.

각 포인터는 지나오면서 각자의 최고 높이 값과 현재 지나는 높이 값 만큼의 차이를 더하면 된다.

class Solution:
    def trap(self, height: List[int]) -> int:
        water = 0

        left = 0
        right = len(height) -1

        left_max = height[left]
        right_max = height[right]

        while left < right:
            left_max = max(left_max, height[left])
            right_max = max(right_max, height[right])
            if left_max < right_max:
                water += (left_max - height[left])
                left += 1
            else:
                water += (right_max - height[right])
                right -= 1
               
        return water