algorithm/problem solving

leetcode 935(dp), 503(monotonic stack), 1004(sliding window), 340(sliding window)

qkqhxla1 2021. 12. 27. 21:55

935 https://leetcode.com/problems/knight-dialer/
미리 다음에 갈수 있는 길들을 저장해놓는다. 그리고 n번만큼 반복해주면서 메모이제이션으로 한번 했던 계산은 하지 않도록 조절한다. 

class Solution:
    def knightDialer(self, n: int) -> int:
        #n=3131
        d = {-1: range(10), 0: [4,6], 1: [6,8], 2: [7,9], 3: [4,8], 4: [0,3,9], 5: [],
             6: [0,1,7], 7: [2,6], 8: [1,3], 9: [2,4]}
        cache = {}
        def dp(n, path):
            if n == 0:
                return 1
            key = (n, path)
            if key in cache:
                return cache[key]
            count = 0
            for _next in d[path]:
                count += dp(n-1, _next)
            cache[key] = count
            return cache[key]
        s = dp(n, -1) % (10**9+7)
        #print(cache)
        return s

근데 이게 통과는 하는데 그렇게 빠르지는 않다. 와 근데 얘는 천재인듯. 
https://leetcode.com/problems/knight-dialer/discuss/189252/O(logN) 첫번째 O(N) time, O(1) space 인 코드도 dp이긴 한데 저렇게 쉽게 풀어써버리네.. 

503 https://leetcode.com/problems/next-greater-element-ii/
바로 이전글에서 monotonic이라는 증가하는 스택을 언급했었는데 예시 문제가 바로 나왔다. 문제에 예시가 부족해서 애매한 부분을 이해하기가 힘들었다. [1,2,3,4,3]의 경우 [2,3,4,-1,4]인데 이것은 '현재 인덱스의 숫자보다 큰 숫자이면서 오른쪽에 있는 숫자를 가져오는거다.'
그러니까.. [5,4,3,2,1]이면 [-1,5,4,3,2]가 아니라 [-1,5,5,5,5]라는거다. (나같은경우 단순히 현재 인덱스의 숫자보다 더큰 최소 숫자의 인덱스를 리턴하는것으로 이해했는데 이게 아님.) 

이걸 해결하려면 [5,4,3,2,1]같은경우 한번 더 붙여서[5,4,3,2,1,5,4,3,2,1]로 만든 후에 %로 인덱스를 조정하면서 monotonic stack을 만들어서 풀면 된다.

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        len_nums = len(nums)
        ret = [-1]*len_nums
        stack = []
        for i in range(len_nums * 2):
            while stack and stack[-1][0] < nums[i % len_nums]:
                ret[stack.pop()[1]] = nums[i % len_nums]
            stack.append([nums[i % len_nums], i % len_nums])
        return ret


1004 https://leetcode.com/problems/max-consecutive-ones-iii/
아주 전형적인 슬라이딩 윈도우 문제다. k를 써서 윈도우를 최대한 넓힌 후 오른쪽으로 한칸씩 가면서 그 윈도우를 유지해준다. 그리고 매번 최대값을 갱신해준다.

class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        l,r = 0,0
        ret = 0
        while r < len(nums):
            if nums[r] == 0:
                if k > 0:  # k가 남아있으면 k를 쓴다.
                    k -= 1
                else:
                    while nums[l] != 0:  # l위치가 0이면 1까지 그냥 지나가준다.
                        l += 1
                    l += 1
            ret = max(ret, r-l+1)  # 최대 넓이를 매번 갱신해준다.
            r += 1
        return ret


340 https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/
위와 거의 비슷하다.

class Solution:
    def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
        #s="abaccc"
        #k=2
        
        l,r = 0,0
        d = {}
        ret = 0
        while r < len(s):
            d[s[r]] = r  # 현재 단어 s[r]의 최신 위치를 계속 갱신해준다.
            if len(d) > k:  # 윈도우보다 커지면 가장 처음에 삽입되었던(인덱스가 가장 앞쪽의) 문자를 뺀다. 
                            # 그리고 빠진 문자의 인덱스가 윈도우의 왼쪽이다.
                c, l = sorted(d.items(), key=lambda x: x[1])[0]  
                del d[c]
                l += 1
            ret = max(ret, r-l+1)  # 윈도우의 왼쪽과 오른쪽 길이를 잰다.
            r += 1
        return ret