Python - 深夜数据结构与算法之 DP

一.引言

常规算法介绍的差不多,最不喜欢的动态规划 Dynamic Programming 还是来啦,前面介绍贪心算法时以及一些最大最小收益等等的问题,其实都可以套用动态规划的思路来实现的,下面我们看看动态规划的思路与模版要点。

二.动态规划的简介

1.递归 Recur 

分治、递归、回溯以及本节的动态规划都有相似之处,介绍动态规划前我们再继续回顾下前面的数据结构以及模版,这些都要做到不断地重复不断地重复,有相关的问题直接模版就能够出来,再修改细节。 

- terminator 终止条件

- process 当前层处理逻辑

- drill down 到下一层继续处理

- restore 需要回复一些状态变量

2.分治 Divide Conquer

同理,分治也有其模版套路,主要是 Split Sub Problem 以及 Merget Sub Problem。同样有其对应的终止条件,一般是子问题无法再向下细分时的最小问题,最终将多个 sub result 进行合并得到全局的 result。

- terminator 终止条件

- split 大问题拆分为小问题

- conquer 分别递归将小问题继续拆分

- merge 合并多个 sub_result 结果

3.动态规划 DP

动态规划 DP 也是在一定分治的基础上寻找最优子结构的过程。中间这句英语的意思是 '将一个复杂的问题分解成一个简单的子问题',这个也印证了前面说的分治。而最优子结构则是 DP 算法一般用于求解最优路线、最短路线、最值等等,其需要存储一个最优的状态并不断更新。

这里本质上其实和递归分治没有太大区别,核心都是找到问题的重复性并不断尝试,同时最优子结构可以做到在比较中淘汰不好的留下更好的。 

3.经典算法实战

1.Fib [509]

斐波那契数列: https://leetcode.cn/problems/fibonacci-number/description/

◆ 题目分析

典中典的题目,分治回溯的鼻祖,这里我们别使用之前的方法和基于 DP 的方法进行解答,正所谓经不经典我不管,我只需要这一款啊。

◆ 双指针实现

class Solution(object):
    def fib(self, n):
        """
        :type n: int
        :rtype: int
        """
        a, b = 0, 1

        # 向右移动
        for i in range(n):
            a , b = b, a+b
        
        return a

前面介绍过很多次了,这里不再赘述,下面看下 DP 的思路。 

◆ DP 实现

class Solution(object):
    def fib(self, n):
        """
        :type n: int
        :rtype: int
        """
        cache = {0:0, 1:1}
        
        return self.fib_by_dp(n, cache)

    def fib_by_dp(self, n, cache):
        if n not in cache:
            cache[n] = self.fib_by_dp(n-1, cache) + self.fib_by_dp(n-2, cache)
        return cache[n]

DP 就是在实现过程中存储状态变量并更新至最优,我们这里最优其实就是到达最终目标值即可。

上面的图可以看到,直接 f(n-1) + f(n-2) 计算时,随着 level 的增加,需要计算的问题呈指数级展开,采用缓存后,问题得到剪枝,只需要 o(n) 的时间复杂度即可。

不过这个方法由于加入了 cache 的 dict,所以空间复杂度相较之前的方案有一定增加,但执行速度上大同小异。 第一种方法其实是一种自下而上的思维,而下面的方法则是自上而下,在过程中添加缓存。我们的人脑一般多是下面这种思维结构,拿到问题 f(n) 然后 f(n-1) 这样,以后遇到问题也可以想想自下而上的思路是否可行。

2.Unique-Paths [62]

不同路径: https://leetcode.cn/problems/unique-paths

◆ 题目分析

对于第一行和第一列的所有位置,机器人只有1种走法可以到达,一路向南和一路向东,所以这两路都标注为 1,其余每个位置的走法都等于其上面和左边走法之和,对于 (1,1) 这个点,可以 (1,0) -> (1,1) 也可以 (0, 1) -> (1,1),所以其走法等于二者之和,其余的点同理。

◆ DP 实现 

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """

        # m 行 n 列的矩阵
        mat = [[1] * n]
        for i in range(m-1):
            mat.append([1] + [0] * (n-1))
    
        # 进行累加
        for i in range(1, m):
            for j in range(1, n):
                mat[i][j] = mat[i-1][j] + mat[i][j-1]
    
        return mat[m-1][n-1]

按照上面的思路,初始化行和列的 1 后,向左右累加即可。 

◆ DP 实现 - 内存优化 

class Solution:
    def uniquePaths(self, m, n):
        # m 行 n 列的矩阵
        cur, next = [1] * n, [1] * n

        # 进行累加
        for i in range(1, m):
            for j in range(1, n):
                next[j] = cur[j] + next[j - 1]
            cur = next[:]

        return next[-1]

上面我们 for i in range(n) 遍历行,起始有上下两行的内存即可,相当于双指针不断向下走即可。运行速度和上面一致,但是内存节省了很多。上面提到了自上而下和自下而上,对于本题这个思想也适用,机器人从左上角到右下角终点有多少种走法,右下角终点到机器人所在起点就有多少种回去的方法,有兴趣的同学也可以反过来实现一遍,体会这两种思维方式。

◆ 排列组合

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """

        re = 1
        st = m + n - 2
        down = 1
        for i in range(1, n):
            re *= st
            st -= 1
            down *= i

        return re // down

这个题还有一个思路就是使用排列组合,对于一个 mxn 的网格,机器人想要到达终点一点要向下 n-1 步,向右 m - 1 步,所以可以看作是组合问题,M+N-2 个格子,n-1 个放 👇🏻,m-1 个位置放 👉🏻,直接套用组合公式即可。

3.Unique-Paths 2 [63]

不同路径2: https://leetcode.cn/problems/unique-paths-ii/

 ◆ 题目分析

与上题目标一致,是机器人从 A 点到 B 点,区别是这次添加了障碍物,当遇到障碍物时,此路为死路,此时路径的可能性就不是其上面和左面的和了,而是 0。

◆ 动态规划

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):

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

        dp = [0] * m
        #起点可能有障碍物
        dp[0] = 1 if obstacleGrid[0][0] == 0 else 0

        for i in range(n):
            for j in range(m):
                #有障碍物的格子直接赋0
                if obstacleGrid[i][j] == 1:
                    dp[j] = 0
                #否则dp[j]的值由左方和上一次迭代的dp[j]累加而来    
                elif obstacleGrid[i][j] == 0 and j - 1 >= 0:
                    dp[j] = dp[j] + dp[j - 1]
                    
        return dp[-1]

 对于 [i][j] == 1 的障碍物格子,其值为 0 不会有走法走到这里。

◆ 递归实现

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):

        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        d = {}
        def dfs(i, j):
            if (i,j) in d:
                return d[i,j]
            #边界/障碍物检查    
            if i >= m or j >= n or obstacleGrid[i][j] == 1:
                return 0		
            #达到重点了    
            if i == m - 1 and j == n - 1:
                return 1
            #继续往右(i,j+1)、往下(i+1,j)递归调用    
            d[i,j] = dfs(i + 1, j) + dfs(i, j + 1)
            return d[i,j]
        return dfs(0, 0)

这个思路就是自底向上的,终点不会有障碍物,其值为 1,我们一步一步加回去即可。每个题都没有固定的解法,还是不能养成惯性思维。

4.LCS [1143]

最长公共子序列: https://leetcode.cn/problems/longest-common-subsequence 

◆ 题目分析

最长公共子序列是典型的动态规划问题,对于字符串 s 和 t 而言,其有两种情况,如果两个索引位置的字符相同,子序列公共部门 +1,然后计算 s[i-1] 和 t[j-1] 的最长公共子串即可,如果二者不相等,则最长公共子串需要判断两个方向,要么 s[i-1] 和 t[j] 或者 s[i] 和 t[j-1]:

 ◆ 动态规划

class Solution(object):
    def longestCommonSubsequence(self, text1, text2):
        """
        :type text1: str
        :type text2: str
        :rtype: int
        """
        m, n = len(text1), len(text2)

        # 构建 m+1 x n+1 的网格
        dp = [[0] * (n+1) for _ in range(m+1)]

        # 从 (1,1) 开始,防止越界
        for i in range(1, m+1):
            for j in range(1, n+1):
                if text1[i - 1] == text2[j - 1]:
                    dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])
        
        return dp[m][n]

下图展示了如何通过状态转移计算 s 与 t 的最长公共子串长度。 当我们遇到子序列类型的问题,穷举出所有可能的结果都不容易,而动态规划算法做的就是穷举 + 剪枝,它俩天生一对儿。所以可以说只要涉及子序列问题,十有八九都需要动态规划来解决,往这方面考虑就对了。

 ◆ 暴力解法

class Solution(object):
    def longestCommonSubsequence(self, text1, text2):
        """
        :type text1: str
        :type text2: str
        :rtype: int
        """
    
        m, n = len(text1), len(text2)

        def dfs(i, j):

            # str = "" 的 bad case
            if i == -1 or j == -1:
                return 0
        
            if text1[i] == text2[j]:
                # 一样就都截取一个字符
                return dfs(i - 1, j - 1) + 1
            else:
                # 取更大的可能
                return max(dfs(i - 1, j), dfs(i, j - 1))
        
        return dfs(m - 1, n - 1)

这个思路其实就是最原始的解法,但是遍历的情况很多会导致运行超时,所以才有了上面我们使用 DP Table 进行缓存的方法,这里动态规划要学会使用缓存结果,构建 i/j 之间的转换关系:

5.Triangle [120]

三角形最小路径和: https://leetcode-cn.com/problems/triangle/description/

◆ 题目分析

根据最下层的 m 个数,可以遍历更新上一层 m-1 个数里的最小值,持续向上更新即可。

◆ 自下而上 DP

class Solution(object):
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """

        " re = min([row, j], [row, j+1]) "

        dp = triangle

        for i in range(len(triangle) - 2, -1, -1):
            for j in range(len(triangle[i])):
                # 每一个位置,都更新为其最小值
                dp[i][j] += min(dp[i+1][j], dp[i+1][j+1])
        
        return dp[0][0]

◆ 自上而下 DFS 

class Solution(object):
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """

        " re = min([row, j], [row, j+1]) "
        # 
        row = len(triangle)
        cache = {}

        def dfs(level, col, triangle):
            # 有缓存直接返回
            if ((level, col) in cache):
                return cache[(level, col)]
            
            # 到达最后一层了
            if (level == row - 1):
                return triangle[level][col]
            
            # 获取分叉的两个值
            left = dfs(level + 1, col, triangle)
            right = dfs(level + 1, col + 1, triangle)
            
            # 增加 Cache
            cache[(level, col)] = min(left, right) + triangle[level][col]
            return cache[(level, col)]
        
        return dfs(0, 0, triangle)

在暴力 DFS 的基础上,增加了 Cache 存储,不过效率一般,思路其实也是从下而上求最小,但是 DFS 是先自顶而下,再从下逐渐返回结果。

6.Max-Sub-Array[53]

最大子数组和: https://leetcode.cn/problems/maximum-subarray/

◆ 题目分析

假设我们的问题结果为 F(i) 代表以当前索引处为结尾最大连续子数组,则其等于 F(i-1) + a[i],即子问题的最大连续子数组 + 当前的值,这里需要考察 F(i-1) 的正负,如果其值 >0,则 F(i) = F(i-1) + a[i] 否则 F(i) = a[i],因为前面最大的和都是负的,那还不如不加。

◆ 动态规划

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        for i in range(1, len(nums)):
            # dp[i] = max(nums[i], dp[i-1] + nums[i])
            # 最大子序列和 => 当前元素最大 or 之前+现在最大
            nums[i] = max(nums[i], nums[i] + nums[i-1])
        
        return max(nums)

对应的 DP 转移方程为 dp[i] = max(nums[i], dp[i-1] + nums[i]),我们也可以把 nums[i] 抽出来,nums[i] = nums[i] + max(0, nums[i-1]),相当于位置 i 要看你左边是不是小于 0。

7.Max-Product-Sub-Array [152]

最大乘积子序列: https://leetcode.cn/problems/maximum-product-subarray/

◆ 题目分析

和上题类似,也是求最大子序列,只不过加法换做了乘法。此时如果我们还复用之前的状态公式: F(i) = F(i-1) * a[i] 就不对了,因为这里乘法可能存在负数,如果 F(i-1) < 0,而 a[i] > 0 或者有其他情况,这里的 F(i-1) 是推不到 F(i) 的,我们在考虑正数最大的情况下,还要考虑负数最小的情况,因为负负得正的问题,所以状态方程如下:

◆ 动态规划

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        mi, ma, res = nums[0], nums[0], nums[0]

        for i in range(1, len(nums)):
            # mx、mn 为上一步的最大与最小
            mx, mn = ma, mi
            # 更新本次的最大、最小
            ma = max(mx * nums[i], nums[i], mn * nums[i])
            mi = min(mx * nums[i], nums[i], mn * nums[i])
            res = max(ma, res)
        
        return res

根据当前值和上一轮的 max、min,更新当前的 max、min,并更新最终的 res。

 

◆ 动态规划-优化

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        mi, ma, res = nums[0], nums[0], nums[0]

        for i in range(1, len(nums)):
            # mx、mn 为上一步的最大与最小
            if nums[i] < 0:
                ma, mi = mi, ma
            # 更新本次的最大、最小
            ma = max(ma * nums[i], nums[i])
            mi = min(nums[i], mi * nums[i])
            res = max(ma, res)
        
        return res

上面的方法每次比较三个数,通过判断当前值的正负,我们可以减少一个比较。这里如果 nums[i] 是正数,则大的更大小的更小,不用修改;反之,大的越小,小的越大,这样每次只需要比较两个数字即可。 

8.Coin-Change [322]

硬币找零: https://leetcode-cn.com/problems/coin-change/description/

◆ 题目分析

每次可以选取不同的硬币,这里我们可以将其理解为一颗状态树,也可以进行动态的递归,下面我们介绍下两种方法。

◆ BFS

class Solution(object):
    cur_min = 9999999999

    def coinChange(self, nums, target, cur):

        if target == 0:
            self.cur_min = min(self.cur_min, len(cur[:]))
            return
        elif target < 0:
            return
        else:
            for i in nums:
                cur.append(i)
                # 目标值减当前 coin,继续寻找
                self.coinChange(nums, target - i, cur)
                cur.pop()

        return self.cur_min

这里遍历完我们可以获得所有题目的子集解答,找到路径最短的数组即可。 该做法的时间复杂度为指数级,所以无法通过。

◆ 递归实现

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        cache = {}
        # 要凑金额 n,需要最少的结果
        def dp(n):
            if n in cache:
                return cache[n]
            
            # base case
            if n == 0:
                return 0
            if n < 0:
                return -1
            
            res = float('INF')

            for coin in coins:
                sub = dp(n - coin)
                if sub == -1:
                    continue
                res = min(res, 1 + sub)
            
            cache[n] = res if res != float('INF') else -1

            return cache[n]
        
        return dp(amount)

上面的方法进行了多次重复计算,这里我们增加 cache 记录提高计算速度。

◆ 动态规划

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        # 初始化一个较大的值
        dp = [amount + 1] * (amount + 1)
        dp[0] = 0

        for coin in coins: # 遍历硬币
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j-coin] + 1) # DP 方程
        
        ans = dp[amount]

        return ans if ans != amount + 1 else -1
        

前面 Fib 递推公式为 F(n) = F(n-1) + F(n-2),这里我们可以考虑为:

F(n) = min(F(n-coin)+1 for coin in coins)

即 coins 对应的多个子问题的最小值加最后一个硬币即可。

四.总结

动态规划的题目主要涉及到最优、 子问题等不方便穷举出所有可能的结果的问题。说只要涉及子序列问题,十有八九都需要动态规划来解决,往这方面考虑就对了。注意在 DFS 的过程中适当添加缓存,即空间换时间或者采用剪枝策略,因为 DP 在不优化的情况下,复杂度往往是指数级。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

案例分享:Qt多国语言输入法软键盘

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/135346374 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

HTML制作暴雨特效

🎀效果展示 🎀代码展示 <body> <!-- partial:index.partial.html --> <canvas id="canvas-club">

猫粮有营养吗?性价比高的主食冻干猫粮测评

随着养猫的人越来越多&#xff0c;铲屎官们对猫咪的饮食也越来越注重。除了猫粮&#xff0c;很多铲屎官还会给猫咪准备小零食。那么&#xff0c;猫咪是不是除了猫粮就没有其他可吃的了呢&#xff1f;答案当然不是。猫咪还有猫冻干、冻干猫粮、猫条等可以选择。每个铲屎官都希望…

PostgreSQL荣获DB-Engines 2023年度数据库

数据库流行度排名网站 DB-Engines 2024 年 1 月 2 日发布文章宣称&#xff0c;PostgreSQL 荣获 2023 年度数据库管理系统称号。 PostgreSQL 在过去一年中获得了比其他 417 个产品更多的流行度增长&#xff0c;因此获得了 2023 年度 DBMS。 DB-Engines 通过计算每种数据库 2024 …

opencv期末练习题(5)附带解析

根据R、G、B的值实时修改图像的颜色 import cv2 import numpy as np""" 滑动块调整图像灰度1. 读取图片&#xff0c;并转为灰度图 2. 定义启动滑块和R、G、B滑块 3. 只有启动滑块的值为1时&#xff0c;拖动R、G、B滑块才生效 4. 根据R、G、B的值实时对修改图片的…

【算法设计与分析】期末复习

文章目录 复习大纲第一章算法概述1.1算法与程序1.2 算法复杂性分析 第二章递归与分治策略分治法的基本思想递归与分治的关系&#xff1a;用分治法解决的问题的几个特征&#xff1a;例题&#xff1a; 第三章动态规划动态规划的基本思想&#xff1a;分治与动态规划算法的异同&…

【小白刷机】Pixel手机刷Magick模块不兼容重启卡开机logo解决方式

目录 关于Pixel为什么要刷机&#xff1f;刷机流程1. 手机进入bootloader2. 电脑准备好系统包和SDK工具包下载系统包下载SDK工具包 3. 手机连接电脑4. 修改配置文件&#xff0c;刷入系统小结彩蛋 关于Pixel Pixel作为一台谷歌手机&#xff0c;在国内不使用魔法是基本无法使用的…

【AI故事】灵感的源泉还是知识的盗窃?

灵感的源泉还是知识的盗窃&#xff1f; ——ChatGPT Robot在一个漆黑的夜晚&#xff0c;年轻的作家艾米丽坐在书桌前&#xff0c;手里紧握着一支笔&#xff0c;思绪万千。她一直在寻找创作的灵感&#xff0c;但却毫无头绪。 突然&#xff0c;她听到了一声巨响&#xff0c;仿佛…

计算机基础面试题 |05.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

本地计算机 上的 My5OL808 服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止

客户反馈说mysql启动不了&#xff0c;报错信息&#xff1a; 本地计算机 上的 My5OL808 服务启动后停止&#xff0c;某些服务在未由其他服务或程序使用时将自动停止。 查了不少资料&#xff0c;最后分析问题是这样的&#xff0c;手动或者重复安装mysql时&#xff0c;创建了多个…

大数据规模存储的几个核心问题

文章目录 三个关键问题RAID&#xff08;独立磁盘冗余阵列&#xff09;RAID是如何解决关于存储的三个关键问题&#xff1f;水平伸缩 大规模数据存储都需要解决几个核心问题&#xff0c;这些问题都是什么呢&#xff1f; 三个关键问题 1.数据存储容量的问题 既然大数据要解决的…

Python高级并发编程的实例详解

更多Python学习内容&#xff1a;ipengtao.com Python中的高效并发编程&#xff0c;有几个重要的概念和工具可以帮助大家充分利用多核处理器和提高程序性能。本文将介绍一些关键的概念和示例代码&#xff0c;以帮助大家更好地理解Python中的高效并发编程。 多线程 vs. 多进程 在…

PyTorch中常用的工具(3)TensorBoard

文章目录 前言3 可视化工具3.1 TensorBoard 前言 在训练神经网络的过程中需要用到很多的工具&#xff0c;最重要的是数据处理、可视化和GPU加速。本章主要介绍PyTorch在这些方面常用的工具模块&#xff0c;合理使用这些工具可以极大地提高编程效率。 由于内容较多&#xff0c…

Jenkins的Transfers路径怎么配置,解决Transfers配置不生效的问题

Transfers配置: 1.配置Source files: 要填写jar包的相对路径,从当前项目工作空间的根目录开始,看看我的工作空间你就懂了 !如图 我填的是 parent/build/libs/parent-1.0.0.jar,即不要 fdw1/ 的前缀 2.配置Remote directory: 远程目标文件夹,也就是你jar包要放到远程…

Linux实用指令篇

目录结构 Linux文件系统结构是从Unix文件结构演进过来的。在Linux文件系统中&#xff0c;通用的目录名用于表示一些常见的功能。 Linux 的文件系统是采用层级式的树状目录结构&#xff0c;在此结构中的最上层是 根目录 “/”&#xff0c;然后在此目录下再创建其他的目录。在L…

Python 彩虹色映射【cm.rainbow()方法】(Matplotlib篇-10)

Python 彩虹色映射【cm.rainbow()方法】(Matplotlib篇-10)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…

使用GPTs+Actions自动获取第三方数据

目录 安装插件与GPT对话联网插件首先,创建GPTs。 Voxscript 官网:https://voxscript.awt.icu/index.htmlOpenAI Schema:https://voxscript.awt.icu/swagger/v1/swagger.yamlServer URL: servers: url: https://voxscript.awt.icu安装插件 要使用这个插件&

鸿蒙原生应用再添新丁!中国移动 入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;中国移动 入局鸿蒙 来自 HarmonyOS 微博1月2日消息&#xff0c;#中国移动APP启动鸿蒙原生应用开发#&#xff0c;拥有超3亿用户的中国移动APP宣布&#xff0c;正式基于HarmonyOS NEXT启动#鸿蒙原生应用#及元服务开发。#HarmonyOS#系统的分布式…

解决grafana模板分区展示数值和使用率不准的问题

我们看监控数据无非就是要准确&#xff0c;但grafana的模板上有时候会遇到数值计算不够准、显示位数不够全的问题&#xff0c;经过自己摸索与实践已经达到了目的&#xff0c;收录到此文分享。 下文分两个方面&#xff0c;我们来看看怎样解决. 目录 1&#xff0c;小数位数修改 …

【心得】PHP文件包含高级利用攻击面个人笔记

目录 一、nginx日志文件包含 二、临时文件包含 三、php的session文件包含 四、pear文件包含 五 、远程文件包含 文件包含 include "/var/www/html/flag.php"; 一 文件名可控 $file$_GET[file]; include $file.".php"; //用php伪协议 &#xff0…