代码随想录算法训练营第二十四天 | 单调栈和额外题目中的从未接触过的难题

单调栈和额外题目中的从未接触过的难题

  • 欠债:排序学习和二分法学习
  • 排序学习
  • 二分法学习,主要是针对有重复元素,求给定target的左右边界
    • 应该是懂了,对于有重复元素,求给定target的左右边界的问题,要处理的细节更多一点,这里我固定一套编写风格,采用左闭右闭区间风格。牢记切记
    • 求左边界:就是在nums[middle]=target时,让right更新,最后退出循环,左边界=right。
    • 求右边界:就是在nums[middle]=target时,让left更新,最后退出循环,右边界=left。
    • 最后,target值所在的左闭右闭区间就是 [ 左边界+1 ,右边界-1 ] .
  • 单调栈
  • 739. 每日温度
    • 未看解答自己编写的青春版
    • 理解后自己尝试的代码
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 496.下一个更大元素 I
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 503. 下一个更大元素 II
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 单调递增栈,求右边第一个比当前大的元素;单调递减栈,求右边第一个比当前小的元素;求左边,倒序遍历。
  • 在使用栈时,有弹出操作,就要判断栈是否为空
  • 42. 接雨水
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 84. 柱状图中最大的矩形
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 不管是,接雨水,还是求矩形最大面积,使用单调栈的核心都是:首先确定,以谁为中心,在本题就是当前遍历的元素。第二个就是,明确该中心两侧,需要的元素分别在哪!这两道题都需要左右两个元素,但是依然只需要一个单调栈,一个是当前要比较的元素,一个是栈中的上一个元素!一定要体会!
  • 这两题的关键就是,先想清楚,如何利用 middle 值,去计算题目的要求值。学会这种,横向计算的思路。
  • 图论 841.钥匙和房间
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 图论 127. 单词接龙
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 并查集 684. 冗余连接
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 并查集 685.冗余连接II
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 并查集对我来说太难了,没接触过,代码随想录也没有讲解视频,讲解的文章不够细致。先跳过吧。
  • 模拟 31.下一个排列
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 模拟 463. 岛屿的周长
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)
  • 位运算 1356. 根据数字二进制下 1 的数目排序
    • 未看解答自己编写的青春版
    • 重点
    • 代码随想录的代码
    • 我的代码(当天晚上理解后自己编写)

欠债:排序学习和二分法学习

排序学习

从网上找了篇博客。
互联网大厂笔试面试必考10大排序算法

二分法学习,主要是针对有重复元素,求给定target的左右边界

应该是懂了,对于有重复元素,求给定target的左右边界的问题,要处理的细节更多一点,这里我固定一套编写风格,采用左闭右闭区间风格。牢记切记

求左边界:就是在nums[middle]=target时,让right更新,最后退出循环,左边界=right。

求右边界:就是在nums[middle]=target时,让left更新,最后退出循环,右边界=left。

最后,target值所在的左闭右闭区间就是 [ 左边界+1 ,右边界-1 ] .

代码随想录算法训练营第二十四天 | 单调栈和额外题目中的从未接触过的难题_第1张图片

代码随想录算法训练营第二十四天 | 单调栈和额外题目中的从未接触过的难题_第2张图片

单调栈

739. 每日温度

未看解答自己编写的青春版

不会,现在还不知道单调栈是什么,直接看题解。

理解后自己尝试的代码

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        result = [0]*n
        if n <= 1:
            return result

        stack = []
        stack.append(0)
        for i in range(1,n):
            
            while stack != [] and temperatures[i] > temperatures[stack[-1]]:   
                # 注意,下面这句不能这样写:
                # result[stack[-1]] = i - stack.pop()
                # 会报一个 out of index 的错            
                result[stack[-1]] = i - stack[-1]
                stack.pop()

            stack.append(i)

        return result

重点

确定好单调栈的顺序,以及当前加入元素,和栈顶元素的大小比较的所有情况。

文章没看,太长了,看了视频。

代码随想录的代码

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        answer = [0]*len(temperatures)
        stack = [0]
        for i in range(1,len(temperatures)):
            # 情况一和情况二
            if temperatures[i]<=temperatures[stack[-1]]:
                stack.append(i)
            # 情况三
            else:
                while len(stack) != 0 and temperatures[i]>temperatures[stack[-1]]:
                    answer[stack[-1]]=i-stack[-1]
                    stack.pop()
                stack.append(i)

        return answer
class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        answer = [0]*len(temperatures)
        stack = []
        for i in range(len(temperatures)):
            while len(stack)>0 and temperatures[i] > temperatures[stack[-1]]:
                answer[stack[-1]] = i - stack[-1]
                stack.pop()
            stack.append(i)
        return answer

我的代码(当天晚上理解后自己编写)

496.下一个更大元素 I

未看解答自己编写的青春版

单调栈 + 字典数据结构

lass Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = {}
        n = len(nums2)
        stack = []
        for i in range(0,n):
            while stack and nums2[i] > nums2[stack[-1]] :
                result[nums2[stack[-1]]] = nums2[i]
                stack.pop()
            stack.append(i)
        m = len(nums1)
        res = [-1]*m
        for i in range(m):
            if result.get(nums1[i],0):
                res[i] = result[nums1[i]]

        return res

重点

没啥可说的。

代码随想录的代码

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = [-1]*len(nums1)
        stack = [0]
        for i in range(1,len(nums2)):
            # 情况一情况二
            if nums2[i]<=nums2[stack[-1]]:
                stack.append(i)
            # 情况三
            else:
                while len(stack)!=0 and nums2[i]>nums2[stack[-1]]:
                    if nums2[stack[-1]] in nums1:
                        index = nums1.index(nums2[stack[-1]])
                        result[index]=nums2[i]
                    stack.pop()                 
                stack.append(i)
        return result

我的代码(当天晚上理解后自己编写)

503. 下一个更大元素 II

未看解答自己编写的青春版

循环数组,不会处理。

重点

成环,如何判断下一个最大元素?本题思想要学习。

两个数组拼在一起,就在线性结构中模拟了成环。

但凡是成环的题,都可以用取模的方式来模拟成环后的遍历过程。

代码随想录的代码

本题的最核心代码是,将数组复制,扩充为原来的两倍,然后做顺序单调栈。

但是可以精简,进行代码优化,就是用取模操作。

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

我的代码(当天晚上理解后自己编写)

单调递增栈,求右边第一个比当前大的元素;单调递减栈,求右边第一个比当前小的元素;求左边,倒序遍历。

在使用栈时,有弹出操作,就要判断栈是否为空

42. 接雨水

未看解答自己编写的青春版

不会。

重点

三种解法,双指针是列向计算雨水,单调栈是横向计算雨水,利用单调栈的关键在于,想清楚,左边最近的高于当前元素的元素位置在哪里。右边的更大值,是利用单调栈获得的,左边的就在栈中,当前元素的前一个元素!

看了视频,没看文字讲解版。

双指针思想在于,先进行两次遍历,分别得到每个元素的,临近右大值数组和临近左大值数组,注意这是两个不同的数组。

代码随想录的代码

暴力解法:

class Solution:
    def trap(self, height: List[int]) -> int:
        res = 0
        for i in range(len(height)):
            if i == 0 or i == len(height)-1: continue
            lHight = height[i-1]
            rHight = height[i+1]
            for j in range(i-1):
                if height[j] > lHight:
                    lHight = height[j]
            for k in range(i+2,len(height)):
                if height[k] > rHight:
                    rHight = height[k]
            res1 = min(lHight,rHight) - height[i]
            if res1 > 0:
                res += res1
        return res

双指针:

class Solution:
    def trap(self, height: List[int]) -> int:
        leftheight, rightheight = [0]*len(height), [0]*len(height)

        leftheight[0]=height[0]
        for i in range(1,len(height)):
            leftheight[i]=max(leftheight[i-1],height[i])
        rightheight[-1]=height[-1]
        for i in range(len(height)-2,-1,-1):
            rightheight[i]=max(rightheight[i+1],height[i])

        result = 0
        for i in range(0,len(height)):
            summ = min(leftheight[i],rightheight[i])-height[i]
            result += summ
        return result

单调栈

class Solution:
    def trap(self, height: List[int]) -> int:
        # 单调栈
        '''
        单调栈是按照 行 的方向来计算雨水
        从栈顶到栈底的顺序:从小到大
        通过三个元素来接水:栈顶,栈顶的下一个元素,以及即将入栈的元素
        雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
        雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度)
        '''
        # stack储存index,用于计算对应的柱子高度
        stack = [0]
        result = 0
        for i in range(1, len(height)):
            # 情况一
            if height[i] < height[stack[-1]]:
                stack.append(i)

            # 情况二
            # 当当前柱子高度和栈顶一致时,左边的一个是不可能存放雨水的,所以保留右侧新柱子
            # 需要使用最右边的柱子来计算宽度
            elif height[i] == height[stack[-1]]:
                stack.pop()
                stack.append(i)

            # 情况三
            else:
                # 抛出所有较低的柱子
                while stack and height[i] > height[stack[-1]]:
                    # 栈顶就是中间的柱子:储水槽,就是凹槽的地步
                    mid_height = height[stack[-1]]
                    stack.pop()
                    if stack:
                        right_height = height[i]
                        left_height = height[stack[-1]]
                        # 两侧的较矮一方的高度 - 凹槽底部高度
                        h = min(right_height, left_height) - mid_height
                        # 凹槽右侧下标 - 凹槽左侧下标 - 1: 只求中间宽度
                        w = i - stack[-1] - 1
                        # 体积:高乘宽
                        result += h * w
                stack.append(i)
        return result

# 单调栈压缩版
class Solution:
    def trap(self, height: List[int]) -> int:
        stack = [0]
        result = 0
        for i in range(1, len(height)):
            while stack and height[i] > height[stack[-1]]:
                mid_height = stack.pop()
                if stack:
                    # 雨水高度是 min(凹槽左侧高度, 凹槽右侧高度) - 凹槽底部高度
                    h = min(height[stack[-1]], height[i]) - height[mid_height]
                    # 雨水宽度是 凹槽右侧的下标 - 凹槽左侧的下标 - 1
                    w = i - stack[-1] - 1
                    # 累计总雨水体积
                    result += h * w
            stack.append(i)
        return result


我的代码(当天晚上理解后自己编写)

84. 柱状图中最大的矩形

未看解答自己编写的青春版

不会

重点

去找,每一个位置,左边和右边,第一个比当前位置矮的位置。

这里找“矮”,很关键。

有一个很关键的技巧:前后补0 。

代码随想录的代码


# 暴力解法(leetcode超时)
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        # 从左向右遍历:以每一根柱子为主心骨(当前轮最高的参照物),迭代直到找到左侧和右侧各第一个矮一级的柱子
        res = 0

        for i in range(len(heights)):
            left = i
            right = i
            # 向左侧遍历:寻找第一个矮一级的柱子
            for _ in range(left, -1, -1):
                if heights[left] < heights[i]:
                    break
                left -= 1
            # 向右侧遍历:寻找第一个矮一级的柱子
            for _ in range(right, len(heights)):
                if heights[right] < heights[i]:
                    break
                right += 1
                
            width = right - left - 1
            height = heights[i]
            res = max(res, width * height)

        return res

# 双指针 
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        size = len(heights)
        # 两个DP数列储存的均是下标index
        min_left_index = [0] * size
        min_right_index = [0] * size
        result = 0

        # 记录每个柱子的左侧第一个矮一级的柱子的下标
        min_left_index[0] = -1  # 初始化防止while死循环
        for i in range(1, size):
            # 以当前柱子为主心骨,向左迭代寻找次级柱子
            temp = i - 1
            while temp >= 0 and heights[temp] >= heights[i]:
                # 当左侧的柱子持续较高时,尝试这个高柱子自己的次级柱子(DP
                temp = min_left_index[temp]
            # 当找到左侧矮一级的目标柱子时
            min_left_index[i] = temp
        
        # 记录每个柱子的右侧第一个矮一级的柱子的下标
        min_right_index[size-1] = size  # 初始化防止while死循环
        for i in range(size-2, -1, -1):
            # 以当前柱子为主心骨,向右迭代寻找次级柱子
            temp = i + 1
            while temp < size and heights[temp] >= heights[i]:
                # 当右侧的柱子持续较高时,尝试这个高柱子自己的次级柱子(DP
                temp = min_right_index[temp]
            # 当找到右侧矮一级的目标柱子时
            min_right_index[i] = temp
        
        for i in range(size):
            area = heights[i] * (min_right_index[i] - min_left_index[i] - 1)
            result = max(area, result)
        
        return result

# 单调栈
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        # Monotonic Stack
        '''
        找每个柱子左右侧的第一个高度值小于该柱子的柱子
        单调栈:栈顶到栈底:从大到小(每插入一个新的小数值时,都要弹出先前的大数值)
        栈顶,栈顶的下一个元素,即将入栈的元素:这三个元素组成了最大面积的高度和宽度
        情况一:当前遍历的元素heights[i]大于栈顶元素的情况
        情况二:当前遍历的元素heights[i]等于栈顶元素的情况
        情况三:当前遍历的元素heights[i]小于栈顶元素的情况
        '''

        # 输入数组首尾各补上一个0(与42.接雨水不同的是,本题原首尾的两个柱子可以作为核心柱进行最大面积尝试
        heights.insert(0, 0)
        heights.append(0)
        stack = [0]
        result = 0
        for i in range(1, len(heights)):
            # 情况一
            if heights[i] > heights[stack[-1]]:
                stack.append(i)
            # 情况二
            elif heights[i] == heights[stack[-1]]:
                stack.pop()
                stack.append(i)
            # 情况三
            else:
                # 抛出所有较高的柱子
                while stack and heights[i] < heights[stack[-1]]:
                    # 栈顶就是中间的柱子,主心骨
                    mid_index = stack[-1]
                    stack.pop()
                    if stack:
                        left_index = stack[-1]
                        right_index = i
                        width = right_index - left_index - 1
                        height = heights[mid_index]
                        result = max(result, width * height)
                stack.append(i)
        return result

# 单调栈精简
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights.insert(0, 0)
        heights.append(0)
        stack = [0]
        result = 0
        for i in range(1, len(heights)):
            while stack and heights[i] < heights[stack[-1]]:
                mid_height = heights[stack[-1]]
                stack.pop()
                if stack:
                    # area = width * height
                    area = (i - stack[-1] - 1) * mid_height
                    result = max(area, result)
            stack.append(i)
        return result

我的代码(当天晚上理解后自己编写)

不管是,接雨水,还是求矩形最大面积,使用单调栈的核心都是:首先确定,以谁为中心,在本题就是当前遍历的元素。第二个就是,明确该中心两侧,需要的元素分别在哪!这两道题都需要左右两个元素,但是依然只需要一个单调栈,一个是当前要比较的元素,一个是栈中的上一个元素!一定要体会!

这两题的关键就是,先想清楚,如何利用 middle 值,去计算题目的要求值。学会这种,横向计算的思路。

图论 841.钥匙和房间

未看解答自己编写的青春版

DFS

class Solution:
    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
        n = len(rooms)
        self.visited = [False]*n
        
        idx = 0
        self.dfs(rooms,idx)
        for i in range(n):
            if self.visited[i]==False :
                return False
        return True


    def dfs(self,rooms,idx):
        if self.visited[idx]==True :
            return

        self.visited[idx] = True
        for i in rooms[idx]:
            self.dfs(rooms,i)

重点

这种题就练习一下,DFS,怎么写吧,BFS还没看。

本题的重点就是:想明白终止条件是什么(当前房间已被访问过),想明白需不需要回溯(不需要)

代码随想录的解答链接

代码随想录的代码

class Solution:
    def dfs(self, key: int, rooms: List[List[int]]  , visited : List[bool] ) :
        if visited[key] :
            return
        visited[key] = True
        keys = rooms[key]
        for i in range(len(keys)) :
            # 深度优先搜索遍历
            self.dfs(keys[i], rooms, visited)

    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
        visited = [False for i in range(len(rooms))]

        self.dfs(0, rooms, visited)

        # 检查是否都访问到了
        for i in range(len(visited)):
            if not visited[i] :
                return False
        return True

我的代码(当天晚上理解后自己编写)

图论 127. 单词接龙

未看解答自己编写的青春版

不会

重点

这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。

本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路。 而广搜只要达到终点,一定是最短路。

另外需要有一个注意点:

本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
本题给出集合是数组型的,可以转成set结构,查找更快一些

代码随想录的代码

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        wordSet = set(wordList)
        if len(wordSet)== 0 or endWord not in wordSet:
            return 0
        mapping = {beginWord:1}
        queue = deque([beginWord]) 
        while queue:
            word = queue.popleft()
            path = mapping[word]
            for i in range(len(word)):
                word_list = list(word)
                for j in range(26):
                    word_list[i] = chr(ord('a')+j)
                    newWord = "".join(word_list)
                    if newWord == endWord:
                        return path+1
                    if newWord in wordSet and newWord not in mapping:
                        mapping[newWord] = path+1
                        queue.append(newWord)                      
        return 0

我的代码(当天晚上理解后自己编写)

并查集 684. 冗余连接

未看解答自己编写的青春版

重点

代码随想录的代码

我的代码(当天晚上理解后自己编写)

并查集 685.冗余连接II

未看解答自己编写的青春版

重点

代码随想录的代码

我的代码(当天晚上理解后自己编写)

并查集对我来说太难了,没接触过,代码随想录也没有讲解视频,讲解的文章不够细致。先跳过吧。

模拟 31.下一个排列

未看解答自己编写的青春版

重点

为什么排列的流程是那样的?不解

代码随想录的代码

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        length = len(nums)
        for i in range(length - 2, -1, -1): # 从倒数第二个开始
            if nums[i]>=nums[i+1]: continue # 剪枝去重
            for j in range(length - 1, i, -1):
                if nums[j] > nums[i]:
                    nums[j], nums[i] = nums[i], nums[j]
                    self.reverse(nums, i + 1, length - 1)
                    return  
        self.reverse(nums, 0, length - 1)
    
    def reverse(self, nums: List[int], left: int, right: int) -> None:
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 1

我的代码(当天晚上理解后自己编写)

模拟 463. 岛屿的周长

未看解答自己编写的青春版

重点

思路一好理解,思路二不好理解,思路二注意只能计算一半,比如,左边和上边,不然就会重复。

代码随想录的代码

class Solution:
    def islandPerimeter(self, grid: List[List[int]]) -> int:

        m = len(grid)
        n = len(grid[0])

        # 创建res二维素组记录答案
        res = [[0] * n for j in range(m)]

        for i in range(m):
            for j in range(len(grid[i])):
                # 如果当前位置为水域,不做修改或reset res[i][j] = 0
                if grid[i][j] == 0:
                    res[i][j] = 0
                # 如果当前位置为陆地,往四个方向判断,update res[i][j]
                elif grid[i][j] == 1:
                    if i == 0 or (i > 0 and grid[i-1][j] == 0):
                        res[i][j] += 1
                    if j == 0 or (j >0 and grid[i][j-1] == 0):
                        res[i][j] += 1
                    if i == m-1 or (i < m-1 and grid[i+1][j] == 0):
                        res[i][j] += 1
                    if j == n-1 or (j < n-1 and grid[i][j+1] == 0):
                        res[i][j] += 1

        # 最后求和res矩阵,这里其实不一定需要矩阵记录,可以设置一个variable res 记录边长,舍矩阵无非是更加形象而已
        ans = sum([sum(row) for row in res])

        return ans

我的代码(当天晚上理解后自己编写)

位运算 1356. 根据数字二进制下 1 的数目排序

未看解答自己编写的青春版

重点

简直是闻所未闻。

代码随想录的代码

class Solution:
    def sortByBits(self, arr: List[int]) -> List[int]:
        arr.sort(key=lambda num: (self.count_bits(num), num))
        return arr

    def count_bits(self, num: int) -> int:
        count = 0
        while num:
            num &= num - 1
            count += 1
        return count

我的代码(当天晚上理解后自己编写)

你可能感兴趣的:(算法)