Python数据结构与算法篇(九)--单调栈与单调队列

news2024/10/8 18:51:00

1 单调栈

1.1 介绍

        栈(stack)是很简单的一种数据结构,先进后出的逻辑顺序,符合某些问题的特点,比如说函数调用栈。

        单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。

        用简洁的话来说就是:单调栈就是栈内元素单调递增或者单调递减的栈,单调栈只能在栈顶操作。

        听起来有点像堆(heap)?不是的,单调栈用途不太广泛,只处理一种典型的问题,叫做 Next Greater Element。本文用讲解单调队列的算法模版解决这类问题,并且探讨处理「循环数组」的策略。

        比如说给一个数组 arr = { 5,4,6,7,2,3,0,1 },我想知道每一个元素左边比该元素大且离得最近的元素和右边比该元素大且离得最近的元素都是什么。

        如果数组有 N N N 个元素,经典解法就是来到 i i i 位置,左边遍历直到比 arr[i] 大的元素为止,右边遍历直到比 arr[i] 大的元素为止。确定一个位置的时间复杂度为 O ( N ) O(N) O(N),确定 N N N 个位置的时间复杂度就是 O ( N 2 ) O(N^2) O(N2)

        能不能将确定 N N N 个位置的时间复杂度降到 O ( N ) O(N) O(N)?单调栈结构。

        同样,如果使用单调栈能够找到每一个元素左边和右边比该元素大且离得最近的元素,同样也能找到每个元素左边和右边比该元素小且离得最近的元素。

1.2 流程(无重复)

        单调栈本身是支持数组中有重复值的,但是我们为了讲清原理,举得例子中数组是没有重复值的。

        首先,准备一个栈。

         == 栈中存储的是数组中元素的下标。为什么不存储元素?是因为下标不仅仅能够表示元素,还能表示元素在数组中的位置,携带的信息更多。==

        如果要找到数组中每一个元素左右两边比该元素大且离得最近的元素,那么单调栈要保证从栈底到栈顶存储的下标对应的元素是从大到小的。

        如果要找到数组中每一个元素左右两边比该元素小且离得最近的元素,那么单调栈要保证从栈底到栈顶存储的下标对应的元素是从小到大的。

        本案例只找比该元素大且离得最近的元素。

        从头开始遍历数组:

  • 如果栈中没有元素,直接将元素的下标压栈。
  • 如果栈中有元素,当前元素和栈顶的下标所指向的元素进行比较:
    • 当前元素比栈顶的下标所指向的元素小,将当前元素的下标压栈。
    • 当前元素比栈顶的下标所指向的元素大,栈顶的下标弹栈,同时记录原栈顶下标对应的元素的信息。原栈顶下标对应的元素左边比该元素大且离得最近的元素就是在栈中原栈顶下标压在下面的相邻下标对应的元素;原栈顶下标对应的元素右边比该元素大且离得最近的元素就是让它的下标弹栈的下标对应的元素。记录完之后,当前元素继续和新栈顶下标对应的元素进行比较。如果栈中只有一个下标,则该下标左边没有比该下标对应的元素大且离得最近的元素,右边正常。

        当数组遍历完后,如果栈中还有下标,则进入清算阶段:

  • 如果不是最后一个下标,依次弹出栈顶下标,原栈顶下标对应的元素左边比该元素大的且离得最近的元素就是在栈中原栈顶下标压在下面的相邻下标;原栈顶下标对应的元素右边没有比该元素大的且离得最近的元素。
  • 是最后一个下标,弹出该下标,该下标对应的元素没有左边比该元素大的且离得最近的元素,也没有右边没有比该元素大的且离得最近的元素。

        设计这种规则实际上就是在严格维护单调栈的单调性。

1.3 流程(有重复)

        假设数组中有重复值,那么单调栈中存储的元素就不能只是一个下标了,可能会存储多个下标,这多个下标对应的数组中的值是一样的。

        因此在实现上,我们偏向去使用一个链表来作为单调栈的元素类型,同一个链表中所有下标指向的元素值是一样的

        这种结构可以处理有重复值的数组,也可以处理无重复值的数组,是万能的。

        流程上和无重复的大致相同,区别在于:

  • 当前元素比栈顶的下标链表所指向的元素大,栈顶的下标链表弹栈,同时记录原栈顶下标链表中每一个下标对应的元素的信息。原栈顶下标链表中每一个下标对应的元素左边比该元素大且离得最近的元素都是在栈中原栈顶下标链表压在下边的相邻下标链表的最后一个下标对应的元素;原栈顶下标链表中每一个下标对应的元素有右边比该元素大且离得最近的元素就是让它的下标链表弹栈的下标链表中的下标对应的元素(此时下标链表中只会有一个元素)。如果栈中只有一个下标链表,则该链表中所有下标左边没有比该下标对应的元素大且离得最近的元素,右边正常。
  • 当前元素与栈顶的下标链表所指向的元素相等,将该元素对应的下标连接到栈顶的下标链表的末尾。

        为什么说使用单调栈可以将时间复杂度降低至 O ( N ) O(N) O(N)
        假设有数组中有 N 个元素,在我们计算出了所有元素的左右边比该元素大或者小且离得最近的元素的整个过程中,无论是使用有重复的模型还是无重复的模型,每一个元素都只进栈一次,出栈一次。

1.4 应用

        单调栈最经典的应用,就是在一个数列里寻找距离元素最近的比其大/小的元素位置。

        比如以下问题:

        对数列中的每个元素,寻找其左侧第一个比它大的元素位置。

        显而易见的,我们可以遍历每个元素,然后从其位置往左寻找,这样的暴力做法时间复杂度是 O ( n 2 ) O(n^2) O(n2)

        但单调栈可以将时间复杂度降到 O ( n ) O(n) O(n) ————

        我们只需从右往左遍历数列,依次将元素加入单调栈中,维护一个从栈底到栈顶递减的单调栈;

        当某个元素被从栈内弹出时,代表它遇到了一个比它更大的元素,因为是从右往左遍历,所以该元素就是第一个比它大的元素,即所求。

        如果最后仍在栈内,则说明该元素左侧没有比它更大的元素。

        遍历的时间复杂度是 O ( n ) O(n) O(n),每个元素最多被加入单调栈一次、弹出来一次,所以总时间复杂度是 O ( n ) O(n) O(n)

        对于要求解的这类问题,我们可以列一个简单的表格——

求解的问题遍历方向维护单调性(栈底->栈顶)
左侧第一个更大从右到左单调递减
左侧第一个更小从右到左单调递增
右侧第一个更大从左到右单调递减
右侧第一个更小从左到右单调递增

1.5 维护单调栈

        单调栈:栈内的元素按照某种方式排序下单调递增或单调递减,如果新入栈的元素破坏的单调性,就弹出栈内元素,直到满足单调性。

        单调栈分为单调递增栈和单调递减栈:

  • 单调递增栈:栈中数据出栈的序列为单调递减序列;
  • 单调递减栈:栈中数据出栈的序列为单调递增序列。

(1) 维护单调递增栈

  • 遍历数组中每一个元素,执行入栈:每次入栈前先检验栈顶元素和进栈元素的大小。
  • 如果栈空或进栈元素大于栈顶元素则直接入栈;如果进栈元素小于等于栈顶元素,则出栈,直至进栈元素大于栈顶元素。
class Solution:
    def monostoneStack(self, arr: List[int]) -> List[int]:
        stack = []
        ans = 定义一个长度和 arr 一样长的数组,并初始化为 -1
        循环 i in  arr:
            while stack and arr[i] < arr[栈顶元素]:
                peek = 弹出栈顶元素
                ans[peek] = i - peek
            stack.append(i)
        return ans

(2) 维护单调递减栈

  • 遍历数组中每一个元素,执行入栈:每次入栈前先检验栈顶元素和进栈元素的大小。
  • 如果栈空或进栈元素小于栈顶元素则直接入栈;如果进栈元素大于等于栈顶元素,则出栈,直至进栈元素小于栈顶元素。
class Solution:
    def monostoneStack(self, arr: List[int]) -> List[int]:
        stack = []
        ans = 定义一个长度和 arr 一样长的数组,并初始化为 -1
        循环 i in  arr:
            while stack and arr[i] > arr[栈顶元素]:
                peek = 弹出栈顶元素
                ans[peek] = i - peek
            stack.append(i)
        return ans

2 单调队列

        单调队列:队列中元素之间的关系具有单调性,而且队首和队尾都可以进行出队操作,只有队尾可以进行入队操作。

        队尾入队的时候维护单调性:

  • 对于单调递增队列,设当前准备入队的元素为 e,从队尾开始把队列中的元素逐个与 e 对比,把比 e 大或者与 e 相等的元素逐个删除,直到遇到一个比 e 小的元素或者队列为空为止,然后把当前元素 e 插入到队尾。
  • 对于单调递减队列也是同样道理,只不过从队尾删除的是比 e 小或者与 e 相等的元素。

        若队列有大小限制,则每次插入新元素的时候,需要从队头开始弹出元素,直到队列至少有一个空间留给当前元素。

        举例来说,nums=[3,2,8,4,5,7,6,4],初始时,deque=[],限制队列长度不能超过 3,维护一个单增队列,

  • 3入队: d e q u e = [ 3 ] deque=[3] deque=[3]
  • 3从队尾出队,2 入队:deque=[2];
  • 8入队:ddeque=[2,8];
  • 8从队尾出队,4入队:deque=[2,4];
  • 5入队:deque=[2,4,5];
  • 2从队头出队,7入队:deque=[4,5,7];
  • 7从队尾出队,6入队:deque=[4,5,6];
  • 6从队尾出队,5从队尾出队,4从队尾出队,4入队:deque=[4]。

        单调队列的作用:区间最小(最大)值问题、优化动态规划、优化多重背包

单调栈和单调队列的区别和联系

相同点

  • 单调队列和单调栈的“头部”都是最先添加的元素,“尾部”都是最后添加的元素。
  • 递增和递减的判断依据是:从栈底(队尾)到栈顶(队首),元素大小的变化情况。队列和栈是相反的。
  • 操作非常相似。当队列长度为无穷大时,递增的单调队列和递减的单调栈,排列是一样的!这是因为,长度为无穷大的的队列不会在“头部”有出队操作,而在“尾部”的操作是一模一样的:数据都从“尾部”进入,并按照相同的规则进行比较。
  • 两者维护的时间复杂度都是O(n),因为每个元素都只操作一次。

区别

  • 队列可以从队列头弹出元素,可以方便地根据入队的时间顺序(访问的顺序)删除元素。这样导致了单调队列和单调栈维护的区间不同。当访问到第i个元素时,单调栈维护的区间为[0, i),而单调队列维护的区间为(lastpop, i)
  • 单调队列可以访问“头部”和“尾部”,而单调栈只能访问栈顶(也就是“尾部”)。这导致单调栈无法获取[0, i)的区间最大值/最小值。

        综上所述,单调队列实际上是单调栈的的升级版。单调栈只支持访问尾部,而单调队列两端都可以。


3 真题演练

3.1 单调栈

题号链接
496下一个更大元素 I(简单)
503下一个更大元素 II(中等)
739每日温度(中等)
901股票价格跨度(中等)
962最大宽度坡(中等)
1019链表中的下一个更大节点(中等)
42接雨水(困难)
84柱状图中最大的矩形(困难)
402移掉 K 位数字(中等)
316去除重复字母(中等)
321拼接最大数(困难)

496. 下一个更大元素 I

1. 暴力法

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        m, n = len(nums1), len(nums2)
        res = [0] * m
        for i in range(m):
            j = nums2.index(nums1[i])
            k = j + 1
            while k < n and nums2[k] < nums2[j]:
                k += 1
            res[i] = nums2[k] if k < n else -1
        return res

2. 单调栈 + 哈希表

方式一:借助栈,正序遍历

class Solution(object):
    def nextGreaterElement(self, nums1, nums2):
        # 因为题目说了没有重复元素,因此可以使用字典来维护元素的下一个更大元素:key-元素,value-下一个更大的元素
        hash_map, stack = {}, []        # 单调递减栈:用于找到下一个更大的元素
        for num in nums2:
                while stack and stack[-1] < num:       # 单调栈
                    hash_map[stack.pop()] = num
                stack.append(num)                          
        return [hash_map.get(num, -1) for num in nums1]

方式二:借助栈,逆序遍历

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        res, stack = {}, []
        for num in reversed(nums2):         # 由于是逆序遍历,所以栈中元素不可能是其之前某个元素的,下一个更大元素,所以将栈中元素清空
            while stack and num >= stack[-1]:
                stack.pop()
            res[num] = stack[-1] if stack else -1
            stack.append(num)
        return [res[num] for num in nums1]

503. 下一个更大元素 II

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        length = len(nums)
        res = [-1]*length
        stack = []
        for i in range(length*2):       # 变为2倍模拟循环数组
            ind = i % length
            while stack and nums[stack[-1]] < nums[ind]:
                res[stack.pop()] = nums[ind]
            stack.append(ind)
        return res

739. 每日温度

方式一:单调栈 正序遍历

class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:
        stack = []           # 这里定义一个栈就不用说了
        res = [0] * len(T)   # 这里是最后要返回的result,因为题目中说没有匹配的就返回0,
                             # 所以这里初始化一个全是0的list,然后把那些有匹配的替换掉即可。

        for idx, t in enumerate(T):  # 下面是关键
            while stack and t > T[stack[-1]]:       # 当stack为空时,运行stack.append(idx),则stack=[0]
                                                    # 然后仅当遍历元素 t 小于stack顶端的值时append进去,
                                                    # 这会导致stack中idx代表的元素是单调递减的,
                                                    # 如果此时遍历到一个 t,大于stack顶端的值,那这个t就是离stack
                                                    # 顶端值最近的那个大值。
                res[stack.pop()] = idx-stack[-1]    # 然后pop出来,还是要注意stack.pop出来的是idx,这样res这
                                                    # 一串0里对应位置的0就会被替换成应有的值。                                        
                                                    # 再进入while循环判断t和stack.pop后的新的顶端值哪个大。
                                                    # 如此反复。
            stack.append(idx)
        return res

901. 股票价格跨度

class StockSpanner:
    def __init__(self):
        # 单调递减栈:存放元素及其跨度
        self.stack = []

    def next(self, price: int) -> int:
        cnt = 1
        while self.stack and self.stack[-1][0] <= price:
            # 找到了一个递增对,将其出栈(因为其历史跨度已经记录在了下一个元素中),并将其跨度叠加
            cnt += self.stack.pop()[1]
        
        self.stack.append((price, cnt))     # 保持元素及其跨度,便于下一次直接计算历史跨度
        return cnt

962. 最大宽度坡

class Solution:
    def maxWidthRamp(self, A: List[int]) -> int:
        stack = []
        n = len(A)
        for i in range(n):
            if not stack or A[stack[-1]] > A[i]:
                stack.append(i)
        print(stack)
        i0 = stack[0]
        res = 0
        i = n - 1
        # while i > res:  # 当res大于等于i时没必要继续遍历了 
        while i-i0 > res:  # 这样更快一点 
            while stack and A[stack[-1]] <= A[i]:
                res = max(res, i - stack[-1])
                stack.pop()
            # print(i,res)
            i -= 1

        return res

1019. 链表中的下一个更大节点

class Solution:
    def nextLargerNodes(self, head: Optional[ListNode]) -> List[int]:
        ans = []
        st = []  # 单调栈(节点值,节点下标)
        while head:
            while st and st[-1][0] < head.val:
                ans[st.pop()[1]] = head.val  # 用当前节点值更新答案
            st.append((head.val, len(ans)))  # 当前 ans 的长度就是当前节点的下标
            ans.append(0)  # 占位
            head = head.next
        return ans

42. 接雨水

方法一:单调栈

class Solution:
    def trap(self, height: List[int]) -> int:
        stack = []
        N = len(height)
        ans = 0
        for i in range(N):
            while  stack and height[stack[-1]] < height[i]:
                cur_ind = stack.pop()
                # 如果栈顶元素一直相等,那么全都pop出去,只留第一个。
                while stack and height[stack[-1]] == height[cur_ind]:
                    stack.pop()
                if  stack:
                    stack_top = stack[-1] # stack_top 此时指向的是此次接住的雨水的左边界的位置。右边界是当前的柱体,即i。
                    # i - stack_top - 1 是雨水的宽度, min(height[stack_top], height[i]) 是左右柱子高度的min,减去height[cur_ind]就是雨水的高度。
                    ans += (min(height[stack_top], height[i])-height[cur_ind])*(i-stack_top-1)
            stack.append(i)
        return ans

84. 柱状图中最大的矩形

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        left, right = [0] * n, [n] * n

        mono_stack = list()
        for i in range(n):
            while mono_stack and heights[mono_stack[-1]] >= heights[i]:
                right[mono_stack[-1]] = i
                mono_stack.pop()
            left[i] = mono_stack[-1] if mono_stack else -1
            mono_stack.append(i)
        
        ans = max((right[i] - left[i] - 1) * heights[i] for i in range(n)) if n > 0 else 0
        return ans

402. 移掉 K 位数字

class Solution(object):
    def removeKdigits(self, num, k):
        stack = []
        remain = len(num) - k
        for digit in num:
            while k and stack and stack[-1] > digit:
                stack.pop()
                k -= 1
            stack.append(digit)
        return ''.join(stack[:remain]).lstrip('0') or '0'

316. 去除重复字母

class Solution:
    def removeDuplicateLetters(self, s) -> int:
        remain_counter = collections.Counter(s)         # 第 1 步:记录每个字符出现的次数
        stack = []      # 第 2 步:使用栈得到题目要求字典序最小的字符串

        for ch in s:
            if ch not in stack:
                while stack and ch < stack[-1] and  remain_counter[stack[-1]] > 0:
                    stack.pop()
                stack.append(ch)
            remain_counter[ch] -= 1
        return ''.join(stack)       # 第 3 步:此时 stack 就是题目要求字典序最小的字符串

321. 拼接最大数

方式一:单调栈合并 && 比较

class Solution:
    def maxNumber(self, nums1, nums2, k):

        def pick_max(nums, k):
            stack = []
            drop = len(nums) - k
            for num in nums:
                while drop and stack and stack[-1] < num:
                    stack.pop()
                    drop -= 1
                stack.append(num)
            return stack[:k]

        def merge(A, B):
            ans = []
            while A or B:
                bigger = A if A > B else B
                ans.append(bigger.pop(0))           # 把最大的取出来
            return ans

        return max(merge(pick_max(nums1, i), pick_max(nums2, k-i)) for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2))

456. 132 模式

class Solution(object):
    def find132pattern(self, nums):
        N = len(nums)
        left_min = [float("inf")] * N
        for i in range(1, N):
            left_min[i] = min(left_min[i - 1], nums[i - 1])
        stack = []
        for j in range(N - 1, -1, -1):
            mask = float("-inf")
            while stack and stack[-1] < nums[j]:
                mask = stack.pop()
            if left_min[j] < mask:
                return True
            stack.append(nums[j])
        return False

3.2 单调队列

题号链接
面试题 59-II 队列的最大值(单调队列模板题)
239滑动窗口最大值
862和至少为 K 的最短子数组
1438绝对差不超过限制的最长连续子数组

面试题 59-II. 队列的最大值

import queue

class MaxQueue:
    def __init__(self):
        self.queue = queue.Queue()
        self.deque = queue.deque()

    def max_value(self) -> int:
        return self.deque[0] if self.deque else -1

    def push_back(self, value: int) -> None:
        self.queue.put(value)
        while self.deque and self.deque[-1] < value:
            self.deque.pop()
        self.deque.append(value)

    def pop_front(self) -> int:
        if self.queue.empty(): return -1
        val = self.queue.get()
        if val == self.deque[0]:
            self.deque.popleft()
        return val

239. 滑动窗口最大值

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        q = collections.deque()
        for i in range(k):
            while q and nums[i] >= nums[q[-1]]:
                q.pop()
            q.append(i)

        ans = [nums[q[0]]]
        for i in range(k, n):
            while q and nums[i] >= nums[q[-1]]:
                q.pop()
            q.append(i)
            while q[0] <= i - k:
                q.popleft()
            ans.append(nums[q[0]])
        
        return ans

单调栈和单调队列暂时告一段落,后面在学习中持续补充,谢谢大家的鼓励和支持!


参考

  • 【数据结构与算法】单调栈详解:https://juejin.cn/post/7019648593694818334
  • 数据结构-单调栈:https://zhuanlan.zhihu.com/p/344988226
  • leetcode-屡试不爽的单调(递增\递减)栈:https://www.jianshu.com/p/f593d56adee7
  • 单调栈与单调队列算法详解及LeetCode经典题目(Python):https://blog.csdn.net/Hanx09/article/details/108434955
  • 单调队列/栈从入门到入土:https://www.cnblogs.com/wsyunine/p/14851199.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rhcse中配置DNS的正反向解析

实验一 正向解析 服务端ip 192.168.9.30 客户端ip 192.168.9.31 网址 www.openlab.com 安装DNS软件 服务端 [rootlocalhost ~]# yum install bind -y 客户端与服务端相同 编辑DNS主配置文件 修改其中的11和19行 listen-on port 53 { any; }; allow-query { a…

homeassistant配置MQTT集成以及传感器实体(STM32连接进入homeassistant)

大家可以看作者的小破站教学视频&#xff08;如果喜欢的话可以点个关注&#xff0c;给个三联&#xff01;啊哈哈哈哈哈哈&#xff09;&#xff1a; 【homeassistant配置MQTT集成以及传感器实体&#xff08;STM32连接进入homeassistant&#xff09;】 最近homeassistan更新之后…

Python VTK计算曲面的高斯曲率和平均曲率

introduction&#xff1a; Python VTK计算曲面的高斯曲率和平均曲率&#xff0c;如何使用户Python版本的VTK计算曲面的高斯曲率并映射在曲面上。使用了两个不同的表面&#xff0c;每个表面根据其高斯曲率和平均曲率着色. Display: Step: 本文介绍了 如何使用户Python版本的V…

什么是软件开发脚手架?为什么需要脚手架?常用的脚手架有哪些?

什么是软件开发脚手架&#xff1f;为什么需要脚手架&#xff1f;常用的脚手架有哪些&#xff1f; 微服务本身是一种架构风格&#xff0c;也是指导组织构建软件的一系列最佳实践集合。然而&#xff0c;业务团队在拆分应用后&#xff0c;会产生更多细粒度服务&#xff0c;并面临…

基于LINUX实现ping发送与接收

作用 Linux ping 命令用于检测主机&#xff1a;执行 ping 会使用 ICMP 传输协议&#xff0c;发出要求回应的信息&#xff0c;若远端主机的网络功能没有问题&#xff0c;就会回应该信息&#xff0c;因而得知该主机运作正常。 基础使用 #ping 192.168.1.1//ping 主机ip#ping -…

【项目】视频列表滑动,自动播放

自动播放 期望效果&#xff0c;当滑动列表结束后&#xff0c;屏幕中间的视频自动播放HTML页面data变量实践操作&#xff01;重点来了&#xff01;滚动获得的数据实现效果源码&#xff08;粘贴即可运行&#xff09; 期望效果&#xff0c;当滑动列表结束后&#xff0c;屏幕中间的…

C. Anna, Svyatoslav and Maps(floyd + 思维)

Problem - C - Codeforces 给你一个有n个顶点的无权图&#xff0c;以及由m个顶点的序列p1,p2,...,pm给出的路径&#xff08;该路径不一定简单&#xff09;&#xff1b;对于每个1≤i<m&#xff0c;有一个弧从pi到pi1。 如果v是p的子序列&#xff0c;v1p1&#xff0c;vkpm&a…

重学Java设计模式-行为型模式-命令模式

重学Java设计模式-行为型模式-命令模式 内容摘自&#xff1a;https://bugstack.cn/md/develop/design-pattern/2020-06-21-重学 Java 设计模式《实战命令模式》.html#重学-java-设计模式-实战命令模式「模拟高档餐厅八大菜系-小二点单厨师烹饪场景」 命令模式介绍 图片来自&a…

后端查询到数据,前端显示该数据为null

问题展示&#xff1a; 数据库可视化界面。我们要展示record属性里面的值。 前端form表单&#xff1a; 后端属性&#xff1a; 后端sql语句&#xff1a; 接下来我们查询订单详情&#xff0c;ID8的订单。 后端控制台&#xff1a; 我们明显的看到&#xff0c;record这个属…

CSS选择器进阶1.2

一&#xff0c;复合选择器 1.1后代选择器&#xff1a;Space 作用&#xff1a;根据HTML标签的嵌套关系&#xff0c;选择父元素后代中满足条件的元素。 选择器语法&#xff08;选择器1为父选择器&#xff0c;选择器2为后代选择器&#xff09;&#xff1a; 选择器1 选择器2{CSS…

【HTML5】HTML5 语义化标签 ( HTML5 简介 | 新增特性 | 语义化标签及代码示例 )

文章目录 一、HTML5 简介二、HTML5 语义化标签三、HTML5 语义化标签代码示例 一、HTML5 简介 HTML5 指的是 对 HTML 语言的第五次重大修改 , 新增了新的元素 / 属性 / 行为 ; HTML5 新增的特性 : 语义特性本地存储特性设备兼容特性连接特性网页多媒体特性三维特性图形及特效特…

故障重现, JAVA进程内存不够时突然挂掉模拟

背景&#xff0c;服务器上的一个JAVA服务进程突然挂掉&#xff0c;查看产生了崩溃日志&#xff0c;如下&#xff1a; # Set larger code cache with -XX:ReservedCodeCacheSize # This output file may be truncated or incomplete. # # Out of Memory Error (os_linux.cpp:26…

什么是跳表?

文章目录文章目的注意事项1.什么是跳表-skiplist2.skiplist的效率如何保证&#xff1f;2.1 一个节点的平均层数3. skiplist的实现文章目的 让你知道什么是跳表,梳理跳表跳表的设计思路及实现 注意事项 下面有数学公式,需要数学功底,只要弄清楚用来干嘛就行有兴趣的人可以了解…

FVCOM模型数值模拟流域、海洋水动力、水环境,解决水交换及污染物扩散问题、溢油及物质输运问题

目录 FVCOM流域、海洋水环境数值模拟方法及实践技术应用 第一章、FVCOM水动力相关理论 第二章、Linux系统下FVCOM运行环境搭建 第三章、FVCOM三维水动力数值模拟前处理 第四章、FVCOM三维水动力数值模拟 第五章、FVCOM三维水动力计算结果可视化及率定方法 第六章、FVCOM…

算法的空间复杂度

空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义 所以空间复杂度算的是变量的个数 空间复杂度计算规则基本跟实践复杂度类似&#xff0c;也使用大O渐进…

Ajax详解

1、什么是Ajax ajax 全名 async javascript and XML是前后台交互的能力也就是我们客户端给服务端发送消息的工具&#xff0c;以及接受响应的工具是一个 默认异步 执行机制的功能。 2、 AJAX 的优势 不需要插件的支持&#xff0c;原生 js 就可以使用用户体验好&#xff08;不…

学生成绩管理系统的设计与实现

一、系统需求分析 实现对一个有32个学生的班级&#xff0c;每个学生有7门课程&#xff0c;实现对他们的班级成绩进行添加、修改、删除、查找、统计输出等基本信息进行一系列的操作。每个学生包括如下信息&#xff1a;学号、姓名、7门课程名称。 二、系统功能模块设计 主要包含…

zookeeper 搭建 linux

jdk安装 1.从网盘里下载jkd 2.创建安装目录&#xff0c;然后将jdk包解压到目录中 mkdir jdktar -zxvf jdk-8u271-linux-x64.tar.gz -C /home/ubuntu/app/jdk/ 3.设置环境变量 修改 vi /etc/profile, 在 profile 文件中添加如下内容并保存&#xff1a; set java environment JAV…

OpenAI-ChatGPT最新官方接口《AI绘图》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(三)(附源码)

ChatGPT-AI绘图 Image generation Beta 图片生成前言IntroductionUsageGenerationsEdits 编辑 VariationsLanguage-specific tips 特定语言提示Python 语言Using in-memory image data 使用内存中的图像数据Operating on image data 操作图像数据Error handling Node.js 语言Us…

手把手教你使用Python调用 ChatGPT!支持http代理

手把手教你使用Python调用 ChatGPT&#xff01;支持http代理 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 前段时间OpenAI 开放了两个新模型的api接口&#xff0c;专门为聊天而生的…