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

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

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

排序学习

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

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

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

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

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

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

在这里插入图片描述

在这里插入图片描述

单调栈

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值