java算法学习索引之动态规划

一 斐波那契数列问题的递归和动态规划

【题目】给定整数N,返回斐波那契数列的第N项。

补充问题 1:给定整数 N,代表台阶数,一次可以跨 2个或者 1个台阶,返回有多少种走法。

【举例】N=3,可以三次都跨1个台阶;也可以先跨2个台阶,再跨1个台阶;还可以先跨1个台阶,再跨2个台阶。所以有三种走法,返回3。

补充问题 2:假设农场中成熟的母牛每年只会生 1 头小母牛,并且永远不会死。第一年农场有1只成熟的母牛,从第二年开始,母牛开始生小母牛。每只小母牛3年之后成熟又可以生小母牛。给定整数N,求出N年后牛的数量。

【举例】N=6,第1年1头成熟母牛记为a;第2年a生了新的小母牛,记为b,总牛数为2;第3年a生了新的小母牛,记为c,总牛数为3;第4年a生了新的小母牛,记为d,总牛数为4。第5年b成熟了,a和b分别生了新的小母牛,总牛数为6;第6年c也成熟了,a、b和c分别生了新的小母牛,总牛数为9,返回9。

【要求】对以上所有的问题,请实现时间复杂度为O(logN)的解法。

斐波那契数列问题

奶牛问题

private int[][] multiMatrix(int[][] m1, int[][] m2) {//矩阵乘法
   // TODO Auto-generated method stub
   int[][] res=new int[m1.length][m2[0].length];
   for (int i = 0; i < m1.length; i++) {
   for (int j = 0; j < m2[0].length; j++) {
      for (int k = 0; k < m2.length; k++) {
         res[i][j]+=m1[i][k]*m2[k][j];
      }
   }
      
   }
   return res;
}

public int f3(int n)
{
   if (n<1) {
      return 0;
   }
   if (n==1||n==2) {
      return 1;
   }
   int [][] base= {{1,1},{1,0}};
   int[][] res=matrixPower(base, n-2);
   return res[0][0]+res[1][0];
}

public int c3(int n)
{
   if (n<1) {
      return 0;
   }
   if (n==1||n==2||n==3) {
      return 3;
   }
   int [][] base= {{1,0,1},{0,0,1},{1,0,0}};
   int[][] res=matrixPower(base, n-3);
   return 3*res[0][0]+2*res[1][0]+res[2][0];
}

二 矩阵的最小路径和

给定一个矩阵 m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。

经典动态规划方法。假设矩阵 m的大小为 M×N,行数为 M,列数为 N。先生成大小和 m一样的矩阵dp,dp[i][j]的值表示从左上角(即(0,0))位置走到(i,j)位置的最小路径和。对m的第一行的所有位置来说,即(0,j)(0≤j<N),从(0,0)位置走到(0,j)位置只能向右走,所以(0,0)位置到(0,j)位置的路径和就是 m[0][0..j]这些值的累加结果。同理,对 m 的第一列的所有位置来说,即(i,0) (0≤i<M),从(0,0)位置走到(i,0)位置只能向下走,所以(0,0)位置到(i,0)位置的路径和就是m[0..i][0]这些值的累加结果。

除第一行和第一列的其他位置(i,j)外,都有左边位置(i-1,j)和上边位置(i,j-1)。从(0,0)到(i,j)的路径必然经过位置(i-1,j)或位置(i,j-1),所以,dp[i][j]=min{dp[i-1][j],dp[i][j-1]}+m[i][j],含义是比较从(0,0)位置开始,经过(i-1,j)位置最终到达(i,j)的最小路径和经过(i,j-1)位置最终到达(i,j)的最小路径之间,哪条路径的路径和更小。那么更小的路径和就是 dp[i][j]的值。

public int minPathSum1(int[][] m) {
   if (m==null||m.length==0||m[0]==null||m[0].length==0) {
      return 0;
   }
   int row=m.length;
   int col=m[0].length;
   int[][] dp=new int[row][col];
   dp[0][0]=m[0][0];
   for (int i = 1; i < row; i++) {
      dp[i][0]=dp[i-1][0]+m[i][0];
   }
   for (int j = 0; j < col; j++) {
      dp[0][j]=dp[0][j-1]+m[0][j];
   }
   for (int i = 1; i < row; i++) {
      for (int j = 0; j < col; j++) {
         dp[i][j]=Math.min(dp[i-1][j], dp[i][j-1])+m[i][j];
      }
   }
   return dp[row-1][col-1];

}

矩阵中一共有 M×N 个位置,每个位置都计算一次从(0,0)位置达到自己的最小路径和,计算的时候只是比较上边位置的最小路径和与左边位置的最小路径和哪个更小,所以时间复杂度为O(M×N),dp矩阵的大小为M×N,所以额外空间复杂度为O(M×N)。动态规划经过空间压缩后的方法。这道题的经典动态规划方法在经过空间压缩之后,时间复杂度依然是O(M×N),但是额外空间复杂度可以从O(M×N)减小至O(min{M,N}),也就是不使用大小为M×N的dp矩阵,而仅仅使用大小为min{M,N}的arr数组。具体过程如下

public int minPathSum2(int[][] m)
{
   if (m==null||m.length==0||m[0]==null||m[0].length==0) {
      return 0;
   }
   int more=Math.max(m.length, m[0].length);
   int less=Math.min(m.length, m[0].length);
   boolean rowmore= more==m.length;
   int[] arr=new int[less];
   arr[0]=m[0][0];
   for (int i = 1; i < less; i++) {
      arr[i]=arr[i-1]+(rowmore? m[0][i]:m[i][0]);//先求出到对角线的值
      
   }
   //数组  arr 中保存的是dp[i][i]中的值,但如果给定的矩阵行数小于列数(M<N),那么就生成长度为M的arr,然后令arr更新成dp矩阵每一列的值,及将arr 中的值保存为 dp[i][N]
   // 从左向右滚动过去
   for (int i = 1; i < more; i++) {
      arr[0]=arr[0]+(rowmore?m[i][0]:m[0][i]);
      for (int j = 1; j < arr.length; j++) {
         arr[j]=Math.min(arr[j-1], arr[j])+(rowmore?m[i][j]:m[j][i]);
      }
   }
   
   return arr[less-1];
   
}

三 换钱的最少货币数

给定数组 arr,arr 中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求组成aim的最少货币数。

方法一:暴力递归

public int minCoins1(int[] arr,int aim)
{
   if (arr==null||arr.length==0||aim<0) {
      return -1;
   }
   return process(arr,0,aim);
}

private int process(int[] arr, int i, int rest) {
    if (i==arr.length) {
      return rest==0?0:-1;
   }
    int res=-1;
    for (int k = 0; k * arr[i]<=rest; k++) {
      int next=process(arr, i+1, rest-k*arr[i]);
      if (next!=-1) {
         res= res==-1?next+k:Math.min(res, next+k);
      }
   }
   return res;
}

//方法二:动态规划


public int minCoins(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return -1; // 对于非法输入,返回-1
    }
    int N = arr.length;
    // 创建一个二维数组 dp,dp[i][j] 表示使用前 i 个货币组成金额 j 的最少货币数
    int[][] dp = new int[N + 1][aim + 1];
    
    // 初始化最后一行(i=N)为-1,表示使用最后一个货币无法组成任何金额
    for (int col = 1; col <= aim; col++) {
        dp[N][col] = -1;
    }
    
    // 从倒数第二行开始向第一行递推,计算每个单元格的值
    for (int i = N - 1; i >= 0; i--) {
        for (int rest = 0; rest <= aim; rest++) {
            dp[i][rest] = -1;   // 先将当前位置的值设为-1,表示无法组成目标金额
            
            // 如果使用下一行的值能够组成当前金额,直接继承下一行的值
            if (dp[i + 1][rest] != -1) {
                dp[i][rest] = dp[i + 1][rest];
            }
            
            // 如果当前面值可以被使用,并且使用当前面值可以组成剩余金额,
            // 则更新当前位置的值为使用当前面值和不适用当前面值两种情况中的最小值
            if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) {
                if (dp[i][rest] == -1) {
                    dp[i][rest] = dp[i][rest - arr[i]] + 1;
                } else {
                    dp[i][rest] = Math.min(dp[i][rest], dp[i][rest - arr[i]] + 1);
                }
            }
        }
    }
    
    // 返回组成目标金额的最少货币数
    return dp[0][aim];
}
 

这段代码使用动态规划思想,通过填表的方式计算出组成目标金额所需的最少货币数。

minCoins2 方法就是填一张 N×aim 的表,而且因为省掉了枚举过程,所以每个位置的值都在O(1)的时间内得到,该方法时间复杂度为O(N×aim)。

四  机器人达到指定位置方法数

假设有排成一行的N个位置,记为1~N,N一定大于或等于2。开始时机器人在其中的M位置上(M一定是1~N中的一个),机器人可以往左走或者往右走,如果机器人来到1位置,那么下一步只能往右来到2位置;如果机器人来到N位置,那么下一步只能往左来到N-1位置。规定机器人必须走K步,最终能来到P位置(P也一定是1~N中的一个)的方法有多少种。给定四个参数N、M、K、P,返回方法数。

(1)
public int ways1(int N, int M, int K, int P) {
    // 检查输入参数的合法性
    if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
        return 0; // 如果参数不合法,直接返回0
    }
    // 调用 walk 方法计算机器人在K步内走到位置P的方法数
    return walk(N, M, K, P);
}

private int walk(int N, int cur, int rest, int P) {
    if (rest == 0) {
        return cur == P ? 1 : 0; // 如果已经走完K步,检查是否到达位置P,是则返回1,否则返回0
    }
    if (cur == 1) {
        return walk(N, 2, rest - 1, P); // 如果当前位置是1,则只能往右走到位置2
    }
    if (cur == N) {
        return walk(N, N - 1, rest - 1, P); // 如果当前位置是N,则只能往左走到位置N-1
    }
    // 否则,计算能够往左或往右走到下一步位置的方法总数
    return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
}
 

这段代码使用递归的方式计算机器人在K步内走到位置P的方法数。walk 方法负责计算具体的步数和位置情况,其中使用了递归的方式进行计算。

(2)动态规划

public int ways2(int N, int M, int K, int P) {
   if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
      return 0;
   }
   int[][] dp = new int[K + 1][N + 1];
   dp[0][P] = 1;
   for (int i = 1; i <= K; i++) {
      for (int j = 1; j <= N; j++) {
         if (j == 1) {
            dp[i][j] = dp[i - 1][2];
         } else if (j == N) {
            dp[i][j] = dp[i - 1][j - 1];
         } else {
            dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
         }
      }
   }
   return dp[K][M];
}

(3)动态规划+空间压缩


public int ways3(int N, int M, int K, int P) {
    if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
        return 0; // 如果参数不合法,直接返回0
    }
    int[] dp = new int[N + 1]; // 创建一个长度为N+1的数组,用于保存每个位置的方法数
    dp[P] = 1; // 初始化目标位置P的方法数为1
    for (int i = 1; i <= K; i++) {
        int leftUp = dp[1]; // 保存上一行的左上角的值,即位置1的值
        for (int j = 1; j <= N; j++) {
            int tmp = dp[j]; // 保存当前位置的值
            if (j == 1) {
                dp[j] = dp[j + 1]; // 如果当前位置是1,只能往右走到位置2
            } else if (j == N) {
                dp[j] = leftUp; // 如果当前位置是N,只能往左走到位置N-1
            } else {
                dp[j] = leftUp + dp[j + 1]; // 否则,计算能够往左或往右走到下一步位置的方法总数
            }
            leftUp = tmp; // 更新左上角的值为上一行的当前位置的值
        }
    }
    return dp[M]; // 返回机器人在K步内走到位置M的方法数
}


这种解法利用动态规划,使用一维数组dp来记录每个位置的方法数。外层循环遍历K步,内层循环遍历每个位置,通过更新数组元素的方式计算每个位置的方法数。最后返回机器人在K步内走到位置M的方法数。

五  换钱的方法数

给定数组 arr,arr 中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求换钱有多少种方法

(1)暴力解法

public int coins1(int[] arr, int aim) {
   if (arr == null || arr.length == 0 || aim < 0) {
      return 0;
   }
   return process1(arr, 0, aim);
}

private int process1(int[] arr, int index, int aim) {
   // TODO Auto-generated method stub
   int res = 0;
   if (index == arr.length) {
      res = aim == 0 ? 1 : 0;
   } else {
      for (int i = 0; arr[index] * i <= aim; i++) {
         res += process1(arr, index + 1, aim - arr[index] * i);
      }
   }
   return res;

}
(2) 记忆搜索的方法
public int coins2(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0; // 如果数组为空或目标钱数小于0,直接返回0
    }
    int[][] map = new int[arr.length + 1][aim + 1]; // 创建一个二维数组,用于保存每个索引和钱数对应的换钱方法数
    return process2(arr, 0, aim, map); // 调用递归函数,并返回结果
}

private int process2(int[] arr, int index, int aim, int[][] map) {
    int res = 0; // 定义结果变量res
    if (index == arr.length) { // 如果递归到达数组末尾
        res = aim == 0 ? 1 : 0; // 如果目标钱数为0,说明找到一种换钱方法,将res设为1,否则设为0
    } else {
        int mapValue = 0; // 用于存储之前计算过的换钱方法数
        for (int i = 0; arr[index] * i <= aim; i++) { // 遍历使用当前面值货币的数量
            mapValue = map[index + 1][aim - arr[index] * i]; // 查看之前计算过的换钱方法数
            if (mapValue != 0) { // 如果之前计算过
                res += mapValue == -1 ? 0 : mapValue; // 如果之前计算得到的是-1,说明之前的结果无效,不加入结果res中,否则加入结果res中
            } else { // 如果之前没有计算过
                res += process2(arr, index + 1, aim - arr[index] * i, map); // 继续递归计算剩余钱数的换钱方法数
            }
        }
    }
    map[index][aim] = res == 0 ? -1 : res; // 将计算得到的结果存入map数组中,若结果为0,则设为-1,表示无效结果
    return res; // 返回结果
}

段代码使用递归的方式解决换钱问题,并利用二维数组map来记录每个索引和钱数对应的换钱方法数。在递归函数process2中,首先判断递归是否到达了数组末尾,如果是,判断目标钱数是否为0,然后返回相应的结果。如果未到达数组末尾,则遍历使用当前面值货币的数量,并查看之前是否计算过对应的换钱方法数。如果计算过,将结果加入结果变量res中(若之前的结果为-1,说明无效,则不加入结果中),如果未计算过,则继续递归计算剩余钱数的换钱方法数。最后将计算得到的结果存入map数组中,并返回结果res。

public int coins3(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0; // 如果数组为空或目标钱数小于0,直接返回0
    }
    int[][] dp = new int[arr.length][aim + 1]; // 创建一个二维数组dp,用于保存每个索引和钱数对应的换钱方法数
    for (int i = 0; i < arr.length; i++) {
        dp[i][0] = 1; // 当钱数为0时,方法数为1
    }
    for (int j = 1; arr[0] * j <= aim; j++) {
        dp[0][arr[0] * j] = 1; // 第一个面值的货币只能用来凑整数倍的自己,所以设置对应的方法数为1
    }
    int num = 0; // 用于存储临时的换钱方法数
    for (int i = 0; i < arr.length; i++) {
        for (int j = 1; j <= aim; j++) {
            num = 0; // 初始化临时变量为0
            for (int k = 0; j - arr[i] * k >= 0; k++) {
                num += dp[i - 1][j - arr[i] * k]; // 计算当前面值货币使用k张时的换钱方法数,并累加到临时变量中
            }
            dp[i][j] = num; // 将计算得到的结果存入dp数组中
        }
    }
    return dp[arr.length - 1][aim]; // 返回结果
}

这段代码使用动态规划解决换钱问题,并利用二维数组dp来记录每个索引和钱数对应的换钱方法数。首先初始化第一列,即钱数为0时的方法数都为1,因为总钱数为0时只有一种换钱方法,即什么都不换。然后初始化第一行,即第一个面值的货币只能用来凑整数倍的自己,所以设置对应的方法数为1。接下来,遍历数组中的每个面值货币和钱数,并根据状态转移方程计算当前面值货币和钱数对应的换钱方法数。最后返回dp数组的最后一个元素,即最终的结果。

可以优化为

public int coins4(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0; // 如果数组为空或目标钱数小于0,直接返回0
    }
    int[][] dp = new int[arr.length][aim + 1]; // 创建一个二维数组dp,用于保存每个索引和钱数对应的换钱方法数
    for (int i = 0; i < arr.length; i++) {
        dp[i][0] = 1; // 当钱数为0时,方法数为1
    }
    for (int j = 1; arr[0] * j <= aim; j++) {
        dp[0][arr[0] * j] = 1; // 第一个面值的货币只能用来凑整数倍的自己,所以设置对应的方法数为1
    }
    for (int i = 0; i < arr.length; i++) {
        for (int j = 1; j <= aim; j++) {
            dp[i][j] = dp[i - 1][j]; // 初始化为上一个硬币的情况
            if (j - arr[i] >= 0) { // 判断当前面值的硬币是否可以组成j
                dp[i][j] += dp[i][j - arr[i]]; // 若可以组成,则加上组成j-arr[i]的情况数
            }
        }
    }
    return dp[arr.length - 1][aim]; // 返回结果
}

(4)空间压缩

public int coins5(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0; // 如果数组为空或目标钱数小于0,直接返回0
    }
    int[] dp = new int[aim + 1]; // 创建一个一维数组dp,用于保存每个钱数对应的换钱方法数
    for (int j = 1; j <= aim; j++) {
        dp[arr[0] * j] = 1; // 第一个面值的货币只能用来凑整数倍的自己,所以设置对应的方法数为1
    }
    for (int i = 0; i < arr.length; i++) {
        for (int j = 1; j <= aim; j++) {
            dp[j] += j - arr[i] >= 0 ? dp[j - arr[i]] : 0; // 根据状态转移方程计算当前钱数对应的换钱方法数
        }
    }
    return dp[aim]; // 返回结果
}

这段代码使用动态规划解决换钱问题,并利用一维数组dp来记录每个钱数对应的换钱方法数。首先初始化第一个面值的货币的倍数对应的方法数为1。然后遍历数组中的每个面值货币和钱数,并根据状态转移方程计算当前钱数对应的换钱方法数。最后返回dp数组的最后一个元素,即最终的结果。这种方法优化了空间复杂度,只使用一维数组来保存动态规划的结果。

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

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

相关文章

一文搞懂 Elasticsearch 之 Mapping

这篇文章主要介绍 Mapping、Dynamic Mapping 以及 ElasticSearch 是如何自动判断字段的类型&#xff0c;同时介绍 Mapping 的相关参数设置。 首先来看下什么是 Mapping&#xff1a; 1 什么是 Mapping&#xff1f; 在一篇文章带你搞定 ElasticSearch 术语中&#xff0c;我们讲…

DevicData-D-XXXXXXXX勒索病毒来袭:如何面对DevicData-D-XXXXXXXX勒索病毒的威胁

尊敬的读者&#xff1a; .DevicData-D-XXXXXXXX勒索病毒&#xff0c;犹如数字世界的黑暗幽灵&#xff0c;通过其复杂的加密算法&#xff0c;将用户数据变为数字谜团&#xff0c;要求赎金以唤回失去的信息。在这个数字时代&#xff0c;了解其特质和对抗方法至关重要。面对复杂的…

Linux进程之通过系统调用创建进程[fork()函数]

文章目录 0.PID是什么?1.通过代码创建子进程--fork1.1fork()初识1.2通过系统调用创建进程1.3perror()函数的了解 2.fork()的进一步了解2.1通过代码了解2.2查看进程的指令 0.PID是什么? 进程PID&#xff08;Process ID&#xff09;是操作系统为每个正在运行的进程分配的唯一标…

0基础学习VR全景平台篇第120篇:极坐标处理接缝 - PS教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 紧跟上节课&#xff0c;我们已经学会了怎么利用PS蒙版工具来对航拍全景图补天。但是在后续工作学习中&#xff0c;我们会遇到天空这部分存在部分接缝的问题&#xff0c;如图&…

ZYNQ调试w25q128bv做flash启动系统

配置petalinux系统从flahs启动&#xff0c;发现BOO.BIN能启动&#xff0c;BOOT.BINimage.ub启动不了。其中烧写和配置的时候&#xff0c;image.ub.bin偏移地址都是0x520000 烧写&#xff0c;然后启动 U-Boot 2018.01-00083-gd8fc4b3b70 (Nov 13 2023 - 03:29:36 0000) Xilinx…

【unity】常用属性特征

编辑器功能 AddComponentMenu-添加组件菜单 将脚本添加到Unity编辑器的菜单中&#xff0c;方便开发者在编辑器中快速添加组件。 示例 using UnityEngine; [AddComponentMenu("添加组件/FollowTransform")] public class FollowTransform : MonoBehaviour { }效果 …

Seaborn数据可视化综合应用Basemap和Seaborn在线闯关_头歌实践教学平台

Seaborn数据可视化综合应用Basemap和Seaborn 第1关 Seaborn第2关 Seaborn图形介绍第3关 Basemap 第1关 Seaborn 任务描述 本关任务&#xff1a;编写一个绘制每个月销售总额的折线图。 编程要求 本关的编程任务是补全右侧上部代码编辑区内的相应代码&#xff0c;根据输入文件路…

java语言开发B/S架构医院云HIS系统源码【springboot】

医院云HIS全称为基于云计算的医疗卫生信息系统( Cloud- Based Healthcare Information System)&#xff0c;是运用云计算、大数据、物联网等新兴信息技术&#xff0c;按照现代医疗卫生管理要求&#xff0c;在一定区域范围内以数字化形式提供医疗卫生行业数据收集、存储、传递、…

基于若依的ruoyi-nbcio流程管理系统增加流程设计器支持自定义表单的选择与处理

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 因为之前不支持在流程设计器进行自定义业务表单的关联选择&#xff0c;所以这部分实现这个。 1、前端 对…

ZooKeeper+Kafka+ELK+Filebeat集群搭建实现大批量日志收集和展示

大致流程&#xff1a;将nginx 服务器&#xff08;web-filebeat&#xff09;的日志通过filebeat收集之后&#xff0c;存储到缓存服务器kafka&#xff0c;之后logstash到kafka服务器上取出相应日志&#xff0c;经过处理后写入到elasticsearch服务器并在kibana上展示。 一、集群环…

lora微调模版

lora微调模版 1、版一&#xff1a;使用peft包的lora微调&#xff08;1&#xff09;设置超参方式一&#xff1a;代码中设置&#xff08;便于debug&#xff09;方式二&#xff1a; .sh文件指定 &#xff08;2&#xff09;加载数据集I、对应的.jsonl或json文件, 原始格式为&#x…

Kafka简单汇总

Kafka的结构图 多个Parttion共同组成这个topic的所有消息。每个consumer都属于一个consumer group&#xff0c;每条消息只能被consumer group中的一个Consumer消费&#xff0c; 但可以被多个consumer group消费。即组间数据是共享的&#xff0c;组内数据是竞争的。二、消费模型…

PO设计模式详解(Python+selenium+unittest)

一、什么是PO设计模式&#xff08;Page Object Model&#xff09; 1、Page Object是一种设计模式&#xff0c;它主要体现在对界面交互细节的封装上&#xff0c;使测试用例更专注于业务的操作&#xff0c;从而提高测试用例的可维护性。 2、一般PO设计模式有三层 第一层&#…

【紫光同创国产FPGA教程】【PGC1/2KG第六章】密码锁实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGC1/2KG开发平台&#xff08;盘古1K/2K&#xff09; 一&#xff1a;盘古1K/2K开发板&#xff08;紫光同创PGC…

Git图形化界面GUI的使用SSH协议及idea集成Git

前言 图形化界面&#xff08;GUI&#xff0c;Graphical User Interface&#xff09;是一种用户与计算机程序或操作系统交互的方式&#xff0c;通过图形元素&#xff0c;如图标、按钮、窗口等&#xff0c;而不是通过命令行来完成操作。GUI的设计旨在让用户通过直观的图形界面进行…

MAC地址注册的原理和应用

MAC地址注册是指在网络设备中&#xff0c;将设备的物理地址&#xff08;即MAC地址&#xff09;与设备的IP地址进行关联和注册的过程。MAC地址是以太网卡硬件上的独特标识符&#xff0c;用于在局域网中标识网络设备。 MAC地址注册在网络管理中起到重要作用&#xff0c;它可以帮助…

vue3配置环境变量,小白简单易学

环境变量的意义就是防止我们更新打包的时候写错变量&#xff0c;合并代码这些一系列问题 首先看看效果 左边是本地测试环境&#xff0c;右边是打包后的生产环境&#xff0c;写这个环境变量的好处就是&#xff0c;你在本地开发的时候变量随便改&#xff0c;不会影响生产环境&am…

ChIP在植物领域中的应用

01 什么是ChIP&#xff1f; 染色质免疫共沉淀技术&#xff08;Chromatin Immunoprecipitation assay&#xff0c;ChIP&#xff09;是研究体内DNA与蛋白质相互作用的方法。其基本原理是在细胞生理状态下固定蛋白质-DNA复合物&#xff0c;将其随机打断为一定长度范围内的染色质小…

阿里云容器镜像服务的运维总结

一、背景 容器镜像服务&#xff0c;作为一个可选付费产品&#xff0c;主要作用是存储docker的镜像仓库&#xff0c;供k8s拉取到Pod节点里。 你可以自己搭建一个harbor镜像仓库&#xff0c;在公司的开发环境下&#xff0c;将image推送到仓库&#xff1b;然后在生产k8s从仓库拉取…

基于JAX-WS实现RESTful形式的web服务端点(endpoint)

RESTful形式的web服务使用XML/HTTP绑定&#xff0c;实现jakarta.xml.ws.Provider。 服务端的实现类用jakarta.xml.ws.WebServiceProvider和BindingType(valueHTTPBinding.HTTP_BINDING)注释。其中HTTPBinding.HTTP_BINDING表示使用XML/HTTP绑定。 例如&#xff1a; WebServic…