解析动态规划

本文由 简悦 SimpRead 转码, 原文地址 juejin.cn

前言

我们刷 leetcode 的时候,经常会遇到动态规划类型题目。动态规划问题非常非常经典,也很有技巧性,一般大厂都非常喜欢问。今天跟大家一起来学习动态规划的套路,文章如果有不正确的地方,欢迎大家指出哈,感谢感谢~

  • 什么是动态规划?
  • 动态规划的核心思想
  • 一个例子走进动态规划
  • 动态规划的解题套路
  • leetcode 案例分析

公众号:捡田螺的小男孩

什么是动态规划?

动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

dynamic programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems.

以上定义来自维基百科,看定义感觉还是有点抽象。简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。

一般这些子问题很相似,可以通过函数关系式递推出来。然后呢,动态规划就致力于解决每个子问题一次,减少重复计算, 比如斐波那契数列就可以看做入门级的经典动态规划问题。

动态规划核心思想

动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算

我们来看下,网上比较流行的一个例子:

  • A : “1+1+1+1+1+1+1+1 =?”
  • A : “上面等式的值是多少”
  • B : 计算 “8”
  • A : 在上面等式的左边写上 “1+” 呢?
  • A : “此时等式的值为多少”
  • B : 很快得出答案 “9”
  • A : “你怎么这么快就知道答案了”
  • A : “只要在 8 的基础上加 1 就行了”
  • A : “所以你不用重新计算,因为你记住了第一个等式的值为 8! 动态规划算法也可以说是’记住求过的解来节省时间’”

一个例子带你走进动态规划 – 青蛙跳阶问题

暴力递归

leetcode 原题:一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法。

有些小伙伴第一次见这个题的时候,可能会有点蒙圈,不知道怎么解决。其实可以试想:

  • 要想跳到第 10 级台阶,要么是先跳到第 9 级,然后再跳 1 级台阶上去; 要么是先跳到第 8 级,然后一次迈 2 级台阶上去。
  • 同理,要想跳到第 9 级台阶,要么是先跳到第 8 级,然后再跳 1 级台阶上去; 要么是先跳到第 7 级,然后一次迈 2 级台阶上去。
  • 要想跳到第 8 级台阶,要么是先跳到第 7 级,然后再跳 1 级台阶上去; 要么是先跳到第 6 级,然后一次迈 2 级台阶上去。

假设跳到第 n 级台阶的跳数我们定义为 f(n),很显然就可以得出以下公式:

f(10= f(9+f(8)
f (9)  = f(8) + f(7)
f (8)  = f(7) + f(6)
...
f(3) = f(2) + f(1)

即通用公式为: f(n) = f(n-1) + f(n-2)

那 f(2) 或者 f(1) 等于多少呢?

  • 当只有 2 级台阶时,有两种跳法,第一种是直接跳两级,第二种是先跳一级,然后再跳一级。即 f(2) = 2;
  • 当只有 1 级台阶时,只有一种跳法,即 f(1)= 1;

因此可以用递归去解决这个问题:

class Solution {
    public int numWays(int n) {
    if(n == 1){
        return 1;
    }
     if(n == 2){
        return 2;
    }
    return numWays(n-1) + numWays(n-2);
    }
}

去 leetcode 提交一下,发现有问题,超出时间限制了

为什么超时了呢?递归耗时在哪里呢?先画出递归树看看:

  • 要计算原问题 f(10),就需要先计算出子问题 f(9) 和 f(8)
  • 然后要计算 f(9),又要先算出子问题 f(8) 和 f(7),以此类推。
  • 一直到 f(2) 和 f(1),递归树才终止。

我们先来看看这个递归的时间复杂度吧:

递归时间复杂度 = 解决一个子问题时间*子问题个数
  • 一个子问题时间 = f(n-1)+f(n-2),也就是一个加法的操作,所以复杂度是 O(1);
  • 问题个数 = 递归树节点的总数,递归树的总节点 = 2^n-1,所以是复杂度 O(2^n)。

因此,青蛙跳阶,递归解法的时间复杂度 = O(1) * O(2^n) = O(2^n),就是指数级别的,爆炸增长的,如果 n 比较大的话,超时很正常的了。

回过头来,你仔细观察这颗递归树,你会发现存在大量重复计算,比如 f(8)被计算了两次,f(7)被重复计算了 3 次… 所以这个递归算法低效的原因,就是存在大量的重复计算

既然存在大量重复计算,那么我们可以先把计算好的答案存下来,即造一个备忘录,等到下次需要的话,先去备忘录查一下,如果有,就直接取就好了,备忘录没有才开始计算,那就可以省去重新重复计算的耗时啦!这就是带备忘录的解法。

带备忘录的递归解法(自顶向下)

一般使用一个数组或者一个哈希 map 充当这个备忘录

  • 第一步,f(10)= f(9) + f(8),f(9) 和 f(8)都需要计算出来,然后再加到备忘录中,如下:

  • 第二步, f(9) = f(8)+ f(7),f(8)= f(7)+ f(6), 因为 f(8) 已经在备忘录中啦,所以可以省掉,f(7),f(6)都需要计算出来,加到备忘录中~

第三步, f(8) = f(7)+ f(6), 发现 f(8),f(7),f(6)全部都在备忘录上了,所以都可以剪掉。

所以呢,用了备忘录递归算法,递归树变成光秃秃的树干咯,如下:

备忘录的递归算法,子问题个数 = 树节点数 = n,解决一个子问题还是 O(1), 所以带备忘录的递归算法的时间复杂度是 O(n)。接下来呢,我们用带备忘录的递归算法去撸代码,解决这个青蛙跳阶问题的超时问题咯~,代码如下:

public class Solution {
    //使用哈希map,充当备忘录的作用
    Map<Integer, Integer> tempMap = new HashMap();
    public int numWays(int n) {
        // n = 0 也算1种
        if (n == 0) {
            return 1;
        }
        if (n <= 2) {
            return n;
        }
        //先判断有没计算过,即看看备忘录有没有
        if (tempMap.containsKey(n)) {
            //备忘录有,即计算过,直接返回
            return tempMap.get(n);
        } else {
            // 备忘录没有,即没有计算过,执行递归计算,并且把结果保存到备忘录map中,对1000000007取余(这个是leetcode题目规定的)
            tempMap.put(n, (numWays(n - 1) + numWays(n - 2)) % 1000000007);
            return tempMap.get(n);
        }
    }
}

去 leetcode 提交一下,如图,稳了:

其实,还可以用动态规划解决这道题。

自底向上的动态规划

动态规划跟带备忘录的递归解法基本思想是一致的,都是减少重复计算,时间复杂度也都是差不多。但是呢:

  • 带备忘录的递归,是从 f(10) 往 f(1)方向延伸求解的,所以也称为自顶向下的解法。
  • 动态规划从较小问题的解,由交叠性质,逐步决策出较大问题的解,它是从 f(1) 往 f(10)方向,往上推求解,所以称为自底向上的解法。

动态规划有几个典型特征,最优子结构、状态转移方程、边界、重叠子问题。在青蛙跳阶问题中:

  • f(n-1) 和 f(n-2) 称为 f(n) 的最优子结构
  • f(n)= f(n-1)+f(n-2)就称为状态转移方程
  • f(1) = 1, f(2) = 2 就是边界啦
  • 比如 f(10)= f(9)+f(8),f(9) = f(8) + f(7) ,f(8) 就是重叠子问题。

我们来看下自底向上的解法,从 f(1) 往 f(10)方向,想想是不是直接一个 for 循环就可以解决啦,如下:

带备忘录的递归解法,空间复杂度是 O(n),但是呢,仔细观察上图,可以发现,f(n)只依赖前面两个数,所以只需要两个变量 a 和 b 来存储,就可以满足需求了,因此空间复杂度是 O(1) 就可以啦

动态规划实现代码如下:

public class Solution {
    public int numWays(int n) {
        if (n<= 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        int a = 1;
        int b = 2;
        int temp = 0;
        for (int i = 3; i <= n; i++) {
            temp = (a + b)% 1000000007;
            a = b;
            b = temp;
        }
        return temp;
    }
    }

动态规划的解题套路

什么样的问题可以考虑使用动态规划解决呢?

如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。

比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。

动态规划的解题思路

动态规划的核心思想就是拆分子问题,记住过往,减少重复计算。 并且动态规划一般都是自底向上的,因此到这里,基于青蛙跳阶问题,我总结了一下我做动态规划的思路:

  • 穷举分析
  • 确定边界
  • 找出规律,确定最优子结构
  • 写出状态转移方程
1. 穷举分析
  • 当台阶数是 1 的时候,有一种跳法,f(1) =1
  • 当只有 2 级台阶时,有两种跳法,第一种是直接跳两级,第二种是先跳一级,然后再跳一级。即 f(2) = 2;
  • 当台阶是 3 级时,想跳到第 3 级台阶,要么是先跳到第 2 级,然后再跳 1 级台阶上去,要么是先跳到第 1 级,然后一次迈 2 级台阶上去。所以 f(3) = f(2) + f(1) =3
  • 当台阶是 4 级时,想跳到第 3 级台阶,要么是先跳到第 3 级,然后再跳 1 级台阶上去,要么是先跳到第 2 级,然后一次迈 2 级台阶上去。所以 f(4) = f(3) + f(2) =5
  • 当台阶是 5 级时…

2. 确定边界

通过穷举分析,我们发现,当台阶数是 1 的时候或者 2 的时候,可以明确知道青蛙跳法。f(1) =1,f(2) = 2,当台阶 n>=3 时,已经呈现出规律 f(3) = f(2) + f(1) =3,因此 f(1) =1,f(2) = 2 就是青蛙跳阶的边界。

3. 找规律,确定最优子结构

n>=3 时,已经呈现出规律 f(n) = f(n-1) + f(n-2) ,因此,f(n-1) 和 f(n-2) 称为 f(n) 的最优子结构。什么是最优子结构?有这么一个解释:

一道动态规划问题,其实就是一个递推问题。假设当前决策结果是 f(n), 则最优子结构就是要让 f(n-k) 最优, 最优子结构性质就是能让转移到 n 的状态是最优的, 并且与后面的决策没有关系, 即让后面的决策安心地使用前面的局部最优解的一种性质

4, 写出状态转移方程

通过前面 3 步,穷举分析,确定边界,最优子结构,我们就可以得出状态转移方程啦:

5. 代码实现

我们实现代码的时候,一般注意从底往上遍历哈,然后关注下边界情况,空间复杂度,也就差不多啦。动态规划有个框架的,大家实现的时候,可以考虑适当参考一下:

dp[0][0][...] = 边界值
for(状态1 :所有状态1的值){
    for(状态2 :所有状态2的值){
        for(...){
          //状态转移方程
          dp[状态1][状态2][...] = 求最值
        }
    }
}

leetcode 案例分析

我们一起来分析一道经典 leetcode 题目吧

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

我们按照以上动态规划的解题思路,

  • 穷举分析
  • 确定边界
  • 找规律,确定最优子结构
  • 状态转移方程
1. 穷举分析

因为动态规划,核心思想包括拆分子问题,记住过往,减少重复计算。 所以我们在思考原问题:数组 num[i] 的最长递增子序列长度时,可以思考下相关子问题,比如原问题是否跟子问题 num[i-1] 的最长递增子序列长度有关呢?

自顶向上的穷举

这里观察规律,显然是有关系的,我们还是遵循动态规划自底向上的原则,基于示例 1 的数据,从数组只有一个元素开始分析。

  • 当 nums 只有一个元素 10 时,最长递增子序列是 [10], 长度是 1.
  • 当 nums 需要加入一个元素 9 时,最长递增子序列是 [10] 或者[9], 长度是 1。
  • 当 nums 再加入一个元素 2 时,最长递增子序列是 [10] 或者 [9] 或者[2], 长度是 1。
  • 当 nums 再加入一个元素 5 时,最长递增子序列是 [2,5], 长度是 2。
  • 当 nums 再加入一个元素 3 时,最长递增子序列是 [2,5] 或者[2,3], 长度是 2。
  • 当 nums 再加入一个元素 7 时,, 最长递增子序列是 [2,5,7] 或者[2,3,7], 长度是 3。
  • 当 nums 再加入一个元素 101 时,最长递增子序列是 [2,5,7,101] 或者[2,3,7,101], 长度是 4。
  • 当 nums 再加入一个元素 18 时,最长递增子序列是 [2,5,7,101] 或者 [2,3,7,101] 或者 [2,5,7,18] 或者[2,3,7,18], 长度是 4。
  • 当 nums 再加入一个元素 7 时, 最长递增子序列是 [2,5,7,101] 或者 [2,3,7,101] 或者 [2,5,7,18] 或者[2,3,7,18], 长度是 4.
分析找规律,拆分子问题

通过上面分析,我们可以发现一个规律

如果新加入一个元素 nums[i], 最长递增子序列要么是以 nums[i] 结尾的递增子序列,要么就是 nums[i-1] 的最长递增子序列。看到这个,是不是很开心,nums[i] 的最长递增子序列已经跟子问题 nums[i-1] 的最长递增子序列有关联了。

原问题数组nums[i]的最长递增子序列 = 子问题数组nums[i-1]的最长递增子序列/nums[i]结尾的最长递增子序列

是不是感觉成功了一半呢?但是如何把 nums[i] 结尾的递增子序列也转化为对应的子问题呢?要是 nums[i] 结尾的递增子序列也跟 nums[i-1] 的最长递增子序列有关就好了。又或者 nums[i] 结尾的最长递增子序列,跟前面子问题 num[j](0=<j<i)结尾的最长递增子序列有关就好了,带着这个想法,我们又回头看看穷举的过程:

nums[i] 的最长递增子序列,不就是从以数组 num[i] 每个元素结尾的最长子序列集合,取元素最多(也就是长度最长)那个嘛,所以原问题,我们转化成求出以数组 nums 每个元素结尾的最长子序列集合,再取最大值嘛。哈哈,想到这,我们就可以用 dp[i] 表示以 num[i] 这个数结尾的最长递增子序列的长度啦,然后再来看看其中的规律:

其实,nums[i] 结尾的自增子序列,只要找到比 nums[i] 小的子序列,加上 nums[i] 就可以啦。显然,可能形成多种新的子序列,我们选最长那个,就是 dp[i] 的值啦

  • nums[3]=5, 以5结尾的最长子序列就是[2,5], 因为从数组下标0到3遍历,只找到了子序列[2]5小,所以就是[2]+[5]啦,即dp[4]=2
  • nums[4]=3, 以3结尾的最长子序列就是[2,3], 因为从数组下标0到4遍历,只找到了子序列[2]3小,所以就是[2]+[3]啦,即dp[4]=2
  • nums[5]=7,以7结尾的最长子序列就是[2,5,7][2,3,7], 因为从数组下标0到5遍历,找到2,5和3都比 7 小,所以就有[2,7],[5,7],[3,7],[2,5,7]和[2,3,7]这些子序列,最长子序列就是[2,5,7]和[2,3,7],它俩不就是以5结尾和3结尾的最长递增子序列 +[7] 来的嘛!所以,dp[5]=3 =dp[3]+1=dp[4]+1

很显然有这个规律:一个以 nums[i] 结尾的数组 nums

  • 如果存在 j 属于区间 [0,i-1], 并且 num[i]>num[j] 的话,则有,dp(i) =max(dp(j))+1,
最简单的边界情况

当 nums 数组只有一个元素时,最长递增子序列的长度 dp(1)=1, 当 nums 数组有两个元素时,dp(2) =2 或者 1, 因此边界就是 dp(1)=1。

确定最优子结构

从穷举分析,我们可以得出,以下的最优结构:

dp(i) =max(dp(j))+1,存在j属于区间[0,i-1],并且num[i]>num[j]。

max(dp(j)) 就是最优子结构。

状态转移方程

通过前面分析,我们就可以得出状态转移方程啦:

所以数组 num[i] 的最长递增子序列就是:

最长递增子序列 =max(dp[i])
代码实现
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        //初始化就是边界情况
        dp[0] = 1;
        int maxans = 1;
        //自底向上遍历
        for (int i = 1; i < nums.length; i++) {
            dp[i] = 1;
            //从下标0到i遍历
            for (int j = 0; j < i; j++) {
                //找到前面比nums[i]小的数nums[j],即有dp[i]= dp[j]+1
                if (nums[j] < nums[i]) {
                    //因为会有多个小于nums[i]的数,也就是会存在多种组合了嘛,我们就取最大放到dp[i]
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            //求出dp[i]后,dp最大那个就是nums的最长递增子序列啦
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}

参考与感谢

  • leetcode 官网
  • 《labuladong 算法小抄》

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

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

相关文章

Springboot静态资源与模板引擎Thymeleaf篇

一、导入静态资源 1.1 静态资源目录 只要静态资源放在类路径下&#xff1a; /static or /public or /resources or /META-INF/resources访问 &#xff1a; 当前项目根路径/ 静态资源名原理&#xff1a; 静态映射/**&#xff1b; "/**" 访问当前项目的任何资源 (静态…

基于JAVA的校园疫情防控管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、系统展示四、核心代码4.1 新增健康情况上报4.2 查询健康咨询4.3 新增离返校申请4.4 查询防疫物资4.5 查询防控宣传数据 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBoot…

Electron 调用Nodejs系统API提示 Error: module not found:xxx 解决方法

最近使用Electron 调用系统API时 居然提示模块为找到异常, 原因是 在Electron大于20版本时渲染进程系统默认启用了沙盒 sandbox. 当 Electron 中的渲染进程被沙盒化时&#xff0c;它们的行为与常规 Chrome 渲染器一样。 一个沙盒化的渲染器不会有一个 Node.js 环境。 所以, …

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Toast组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Toast组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Toast组件 Toast 的应用场景也非常广泛&#xff0c;比如网络请求出错了可以弹一个…

基于XLA_GPU的llama7b推理

环境 pytorch-tpu/llamapytorch 2.1.2(cuda117)torch-xla 2.1.1 # llama2 git clone --branch llama2-google-next-inference https://github.com/pytorch-tpu/llama.git # pytorch git clone https://github.com/pytorch/pytorch.git git checkout v2.1.2 # 部分仓库可能下载…

Jmeter 压测 —— 非GUI模式执行实例!

1、上传脚本 把在Windows下调试好的脚本上传的Linux系统/home目录下。 注意&#xff1a;只留测试脚本&#xff0c;屏蔽其它监控组件&#xff0c;比如&#xff1a;查看结果树、聚合报告、监听器等。 2、执行脚本 ①输入命令执行脚本 jmeter -n -t case.jmx -l case.jtl -n&…

书生·浦语大模型实战营——两周带你玩转微调部署评测全链路

引言 人工智能技术的发展日新月异&#xff0c;其中大模型的发展尤其迅速&#xff0c;已然是 AI 时代最炙手可热的当红炸子鸡。 然而&#xff0c;大模型赛道对于小白开发者来说还是有不小的门槛。面对内容质量参差不齐的课程和实际操作中遇到的问题&#xff0c;许多开发者往往…

2015年第四届数学建模国际赛小美赛C题科学能解决恐怖主义吗解题全过程文档及程序

2015年第四届数学建模国际赛小美赛 C题 科学能解决恐怖主义吗 原题再现&#xff1a; 为什么人们转向恐怖主义&#xff0c;特别是自杀性恐怖主义&#xff1f;主要原因是什么&#xff1f;这通常是大问题和小问题的结合&#xff0c;或者是一些人所说的“推拉”因素。更大的问题包…

MyBatis框架——MyBatis实现查询功能

一、简单查询 查询数据中的一条数据或多条数据&#xff0c;返回&#xff0c;有两种实现方式&#xff1a; 第一种&#xff0c;用注解的方式实现&#xff0c;方法如下图&#xff1a; 第二种&#xff0c;用mapper的方式实现&#xff0c;方法如下图&#xff1a; 注意&#xff1a;…

408计算机网络错题知识点拾遗

个人向错题相关部分整理&#xff0c;涵盖真题、模拟、课后习题等。 408相关&#xff1a; 408数据结构错题知识点拾遗 408计算机网络错题知识点拾遗 计网复习资料下载整合 已进行资源绑定&#xff0c;相关计网复习资料上方下载。 第一章 计算机网络体系结构 第二章 物理层 第三…

【我的方向】轻量化小目标检测

文章目录 轻量化1人工设计的轻量化方法1.1组卷积1.2深度可分离卷积1.3基于深度可分离卷积的MobileNet1.4 ShuffleNet1.5 ShuffleNet V2 基于 Octave 卷积的改进基线网络基于 Ghost特征的 GhostNet基于神经网络结构搜索的轻量化方法基于自动模型压缩的轻量化方法4 相关论文 小目…

【我与java的成长记】之面向对象的初步认识

系列文章目录 能看懂文字就能明白系列 C语言笔记传送门 &#x1f31f; 个人主页&#xff1a;古德猫宁- &#x1f308; 信念如阳光&#xff0c;照亮前行的每一步 文章目录 系列文章目录&#x1f308; *信念如阳光&#xff0c;照亮前行的每一步* 前言一、什么是面向对象面向过程…

C++力扣题目239--滑动窗口最大值

给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7], k 3 输…

基于ssm实验室预约管理系统论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

方舟开发框架(ArkUI)概述

目录 1、基本概念 2、两种开发范式 3、开发框架的特性 4、UI开发&#xff08;ArkTS声明式开发范式&#xff09;概述 4.1、特点 4.2、整体架构 4.3、开发流程 方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包…

深入解析 Flink CDC 增量快照读取机制

一、Flink-CDC 1.x 痛点 Flink CDC 1.x 使用 Debezium 引擎集成来实现数据采集&#xff0c;支持全量加增量模式&#xff0c;确保数据的一致性。然而&#xff0c;这种集成存在一些痛点需要注意&#xff1a; 一致性通过加锁保证&#xff1a;在保证数据一致性时&#xff0c;Debez…

LH7904C高压线太阳能警示灯

适用场所&#xff1a; 适用于高压线,塔吊,路政,船舶,种植,塔机,航海航道等场所起警示作用。 产品特点&#xff1a; 光控无开关&#xff0c;白天不闪&#xff0c;昏暗环境自动闪烁&#xff0c;无需手动操作&#xff0c;省时省事; 采用红色LED作光源&#xff0c;亮度高&#…

边缘计算云边端全览—边缘计算系统设计与实践【文末送书-10】

文章目录 一.边缘计算1.1边缘计算的典型应用 二.边缘计算 VS 云计算三.边缘计算系统设计与实践【文末送书-10】3.1 粉丝福利&#xff1a;文末推荐与福利免费包邮送书&#xff01; 一.边缘计算 边缘计算是指在靠近物或数据源头的一侧&#xff0c;采用网络、计算、存储、应用核心…

camunda-modeler画图入门

软件下载 camunda-modeler是camunda的工作流绘制桌面工具 5.9.0和5.18.0版本下载地址 https://storage.googleapis.com/downloads-camunda-cloud-release/camunda-modeler/5.9.0/camunda-modeler-5.9.0-win-x64.ziphttps://storage.googleapis.com/downloads-camunda-cloud-…

苹果证书p12和描述文件的创建方法

​ 苹果证书p12和描述文件的创建方法 在2020年之前&#xff0c;我们在使用appuploder创建苹果证书的时候&#xff0c;只需要注册苹果开发者账号&#xff0c;但不需要缴费成为开发者。 在2020年之后&#xff0c;需要先缴费成为苹果开发者。 假如你还没有注册苹果开发者账号&…