【算法思维】-- 动态规划(C++)

news2025/1/21 0:55:55

OJ须知:

  • 一般而言,OJ在1s内能接受的算法时间复杂度:10e8 ~ 10e9之间(中值5*10e8)。在竞赛中,一般认为计算机1秒能执行 5*10e8 次计算
时间复杂度取值范围
o(log2n)大的离谱
O(n)10e8
O(nlog(n))10e6
O(nsqrt(n)))10e5
O(n^2)5000
O(n^3)300
O(2^n)25
O(3^n)15
O(n!)

11

时间复杂度排序: o(1) < o(log2n) < o(n) < o(nlog2n) < o(n^2) < o(n^3) < o(2^n) < o(2^n) < o(3^n) < o(n!)

目录

Dynamic Programming

DP定义

斐波那契数⭐

方法一:递归

复杂度分析

方法二:DP

复杂度分析

单词拆分⭐⭐

方法一:DP 

复杂度分析

三角形最小路径和⭐⭐

方法一:DP 

复杂度分析

方法二:DP(反向思维)

复杂度分析

不同路径⭐⭐

方法一:DP 

复杂度分析

最小路径和⭐⭐

方法一:DP 

复杂度分析

背包问题⭐⭐⭐

方法一:DP  

复杂度分析


Dynamic Programming

DP定义

        动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化了的艺术。

融汇贯通的理解:

        分治(大事化小,小事化了)将问题进行分解,通过求解子问题,再用子问题推导大问题得解。

        在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。

DP具备了以下三个特点

  1. 分解子问题:把原来的问题分解成了几个相似的子问题(先求解最小的子问题)
  2. 求解子问题:所有的子问题都只需要解决一次
  3. 保存子问题的解:按照需要储存子问题的解。(后序再以此推动,从而以子问题的解求取更大的子问题的解)

融汇贯通的理解:

        DP VS 递归:递归中子问题的解一般是不保存的,而DP是需要保留结果的,有时候根据需要,有时候是部分的解,有时候甚至是保留全部的解。

动态规划的本质,是对问题状态的定义状态转移方程的定义(状态以及状态之间的递推关系)

融汇贯通的理解:

        状态的定义:子问题。

        状态转移方程的定义:用子问题推大问题。

动态规划问题一般从以下四个角度考虑:

  1. 状态定义(根据题目的问题抽象而出)
  2. 状态间的转移方程定义(根据题目的问题的线索 + 状态定义 = 得出(≈递归方程))
  3. 状态的初始化(一般就是最简单的子问题,不需要任何的推动,也不需要任何转移方程就可以将解确定出来)
  4. 返回结果(某一个状态的解 / 某几个状态处理的解)

状态定义的要求:定义的状态一定要形成递推关系

一句话概括:三特点四要素两本质。

难点:

  1. 状态比较抽象,不好寻找
  2. 转移方程根据线索不好找

适用场景:

  1. 最大值 / 最小值
  2. 可不可行 / 是不是
  3. 方案个数

斐波那契数⭐

509. 斐波那契数 - 力扣(LeetCode)

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

        F(0) = 0,F(1) = 1
        F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。

提示:

  • 0 <= n <= 30。

方法一:递归

class Solution {
public:
	int Fib(int n) {
		// 初始值
		if (n == 0) return 0;
		if (n == 1 || n == 2) return 1;

		// F(n) = F(n-1) + F(n-2)
		return Fib(n - 1) + Fib(n - 2);
	}
};

复杂度分析

  • 时间复杂度:O(2^n),随着n的增大呈现指数增长,效率低下当输入比较大时,可能导致栈溢出在递归过程中有大量的重复计算。(此处:0 <= n <= 30侥幸跑过)
  • 空间复杂度:O(n),为树的高度。

下图以求n = 4为例:

        此处:1、根据C语言的函数栈帧开辟销毁特性2、根据C语言语句执行先左后右的顺序。在语句:

return Fib(n - 1) + Fib(n - 2);

        是先一路递归Fib(n - 1)后产生返回值并释放,才会再执行Fib(n - 2)。所以空间复杂度为树高:O(n)。

方法二:DP

引入DP四点:

  1. 状态定义(根据题目的问题抽象而出)
  2. 状态间的转移方程定义(根据题目的问题的线索 + 状态定义 = 得出(≈递归方程))
  3. 状态的初始化(一般就是最简单的子问题,不需要任何的推动,也不需要任何转移方程就可以将解确定出来)
  4. 返回结果(某一个状态的解 / 某几个状态处理的解)

抽象题中线索:

  • 状态定义:F(i) = ?
  • 状态间的转移方程定义:F(i) = F(i - 1) + F(i - 2)
  • 状态的初始化:F(0) = 0,F(1) = 1
  • 返回结果:F(n) = ?
class Solution {
public:
    int fib(int n) {
        vector<int> v(n + 1, 0);
        v[0] = 0;
        if(n >= 1) v[1] = 1; // 题目中允许n = 0
        for(int i = 2; i <= n; i++)
            v[i] = v[i - 1] + v[i - 2];
        return v[n];
    }
}

        经典的DP基础题,将每一个状态都进行保存,后面的状态都是通过前面已知状态结果,递推过来的。

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

        此处还可以对空间复杂度进行优化,因为求当前状态只需要前两个状态的结果,其余结果毫无用处。

class Solution {
public:
    int fib(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        int F_one = 0, F_tow = 1;
        int ret = 0;
        for(int i = 2; i <= n; i++)
        {
            ret = F_one + F_tow;
        // 更新中间状态
            F_one = F_tow;
            F_tow = ret;
        }
        return ret;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

单词拆分⭐⭐

139. 单词拆分 - 力扣(LeetCode)


给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

        s = "leetcode", wordDict = ["leet", "code"]

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 和 wordDict[i] 仅有小写英文字母组成
  • wordDict中的所有字符串 互不相同

方法一:DP 

抽象题中线索:

  • 状态定义:s 的前 i 个字符是否可以被分割
  • 状态间的转移方程定义:s[0,n]能被分割,则s[n + 1,m]能否被分割,代表s[0,m]能否被分割(n < m)
  • 状态的初始化:空字符串true(没有任何实际意义,就是辅助)
  • 返回结果:s 是否可以被分割
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        // 状态定义:s 的前 i 个字符是否可以被分割
        vector<bool> ret(s.size() + 1, false);
        // 状态的初始化:空字符串true
        ret[0] = true;
        unordered_set<string> dict;
        for(int i = 0; i < wordDict.size(); i++)
            dict.insert(wordDict[i]);

        // 状态间的转移方程定义: s[0,n]能被分割,则s[n + 1,m]能否被分割,代表s[0,m]能否被分割(n < m)
        for(int m = 1; m <= s.size(); m++)
        {
            for(int n = 0; n < m; n++)
            {
                if(ret[n] && dict.end() != dict.find(s.substr(n, m - n)))
                {
                    ret[m] = true;
                    break;
                }
            }
        }
        // 返回结果:s 是否可以被分割
        return ret[s.size()];
    }
};

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

三角形最小路径和⭐⭐

​​​​​​120. 三角形最小路径和 - 力扣(LeetCode)


给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点在这里指的是 下标上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标  i  或  i + 1

提示:

  • 1 <= triangle.length <= 200
  • triangle[0].length == 1
  • triangle[i].length == triangle[i - 1].length + 1
  • -104 <= triangle[i][j] <= 104

方法一:DP 

抽象题中线索:

  • 状态定义:从 [ 0, 0 ] 到 [ i, j ] 的min = ?
  • 状态间的转移方程定义:[ i, j ] += min([ i - 1, j - 1], [ i - 1, j ])(有些路径只有一条,代码里体现)

  • 状态的初始化:[ 0, 0 ]的min = [ 0, 0 ]
  • 返回结果:min ( [ 底行 ][ 0 ] ~ [ 底行 ][ 行底 ] )
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty())
            return 0;
        int row = triangle.size();
        int col = triangle[row - 1].size();

        // 状态定义:从[ 0, 0 ]到[ i, j ]的min = ?
        vector<vector<int>> ret(row, vector<int>(col, 0));

        // 状态的初始化: [ 0, 0 ]的min = [ 0, 0 ]
        ret[0][0] = triangle[0][0];
        for(int i = 1; i < row; i++)
        {
            for(int j = 0; j <= i; j++)
            {
                // 状态间的转移方程定义: [i, j] += min([i - 1, j - 1], [i - 1, j])
                if(j == 0) ret[i][j] = ret[i - 1][j] + triangle[i][j];
                else if(j == i) ret[i][j] = ret[i - 1][j - 1] + triangle[i][j];
                else ret[i][j] = min(ret[i - 1][j], ret[i - 1][j - 1]) + triangle[i][j];
            }
        }
        // 返回结果: min([底行][0] ~ [底行][行底])
        return *min_element(ret[row - 1].begin(), ret[row - 1].end());
    }
};

复杂度分析

  • 时间复杂度:O(n^2)是一个2、3、4、5、6、7、8 …… row + 1的等差数列。
  • 空间复杂度:O(n^2)

进阶:

        你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗?

        看上述代码,会发现我们根本就只是需要最后的一行数据,所以其实我们对于前述的二维数组,完全可以以一个以为数组代替。

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty()) return 0;
        int row = triangle.size();
        int col = triangle[row - 1].size();

        // 状态定义:从[ 0, 0 ]到[ i, j ]的min = ?
        vector<int> ret(col, 0);

        // 状态的初始化: [ 0, 0 ]的min = [ 0, 0 ]
        ret[0] = triangle[0][0];
        for(int i = 1; i < row; i++)
        {
            for(int j = i; j >= 0; j--)
            {
                // 状态间的转移方程定义: [i, j] += min([i - 1, j - 1], [i - 1, j])
                if(j == 0) ret[j] = ret[j] + triangle[i][j];
                else if(j == i) ret[j] = ret[j - 1] + triangle[i][j];
                else ret[j] = min(ret[j], ret[j - 1]) + triangle[i][j];
            }
        }
        // 返回结果: min([底行][0] ~ [底行][行底])
        return *min_element(ret.begin(), ret.end());
    }
};
  • 时间复杂度:O(n^2),是一个2、3、4、5、6、7、8 …… row + 1的等差数列。
  • 空间复杂度:O(n)

方法二:DP(反向思维)

        前面我们利用的解法,是这一行的解来自上一行的解,而这次我们反着,这一行的解来自下一行的解而不是上一行的解。

抽象题中线索:

  • 状态定义:从 [ i, j ] 到 [ 底行, 行底 ] 的min = ?
  • 状态间的转移方程定义:[ i, j ] += min([ i + 1, j ], [ i + 1, j + 1 ])(有些路径只有一条,代码里体现)

  • 状态的初始化:[ 底行, 行底 ] 的 min = [ 底行, 行底 ]
  • 返回结果:[0, 0]
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty()) return 0;
        int row = triangle.size();
        int col = triangle[row - 1].size();
        vector<vector<int>> ret(row, vector<int>(col, 0));
        for(int i = 0; i<col; i++)
            ret[row - 1][i] = triangle[row - 1][i];

        for(int i = row - 2; i >= 0; i--)
        {
            for(int j = 0; j <= i; j++)
            {
                ret[i][j] = min(ret[i + 1][j], ret[i + 1][j + 1]) + triangle[i][j];
            }
        }
        return ret[0][0];
    }
};

复杂度分析

  • 时间复杂度:O(n^2)是一个2、3、4、5、6、7、8 …… row + 1的等差数列。
  • 空间复杂度:O(n^2)

不同路径⭐⭐

62. 不同路径 - 力扣(LeetCode)


一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

提示:

  • 1 <= m, n <= 100
  • 题目数据保证答案小于等于 2*10^9

方法一:DP 

抽象题中线索:

  • 状态定义:从 [0,0] 到 [i,j] 有几种路径?
  • 状态间的转移方程定义:[i,j] 路径 = [i,j - 1] 路径 +  [i - 1,j] 路径
  • 状态的初始化:第一行,第一列路径为1
  • 返回结果:[底行, 行底]
class Solution {
public:
    int uniquePaths(int m, int n) {
        // 状态定义: 从[0,0]到[i,j]有几种路径?
        vector<vector<int>> ret(m, vector<int>(n, 0));

        // 状态的初始化: 第一行,第一列路径为1
        for(int i = 0; i < m ; i++) ret[i][0] = 1;
        for(int i = 0; i < n ; i++) ret[0][i] = 1;

        for(int i = 1; i < m ; i++)
        {
            for(int j = 1; j < n; j++)
            {
                // 状态间的转移方程定义: [i,j]路径 = [i,j - 1]路径 + [i - 1,j]路径
                ret[i][j] = ret[i][j - 1] + ret[i - 1][j];
            }
        }

        // 返回结果: [底行, 行底]
        return ret[m - 1][n - 1];
    }
};

复杂度分析

  • 时间复杂度:O(m*n)。
  • 空间复杂度:O(m*n)。

进阶:

        你可以只使用 O(n) / O(m) 的额外空间来解决这个问题吗?

        我们通过上述代码可以发现,[i,j] 的路径数只和 [i - 1,j] 与  [i,j - 1] 有关。也就是说其实我们可以利用一个一维数组就解决问题。比如以列为一维数组:vector<int> array(m, 1),当列由第 i 列移动到 i + 1 列。

  • 状态定义:从 [0,0] 到 [i,j] 有几种路径?
  • 状态间的转移方程定义:[i] 路径(后) = [i - 1] 路径 +  [i] 路径(前)
  • 状态的初始化:第一列路径为1
  • 返回结果:[列底]
class Solution {
public:
    int uniquePaths(int m, int n) {
        // 状态定义: 从[0,0]到[i,j]有几种路径?
        // 状态的初始化: 第一列路径为1
        vector<int> ret(m, 1);

        for(int i = 1; i < n; i++)
        {
            for(int j = 1; j < m; j++)
            {
                // 状态间的转移方程定义: [i]路径(后) = [i - 1]路径 + [i]路径(前)
                ret[j] = ret[j - 1] + ret[j];
            }
        }

        // 返回结果: [列底]
        return ret[m - 1];
    }
};
  • 时间复杂度:O(m*n)。
  • 空间复杂度:O(m)。

最小路径和⭐⭐

64. 最小路径和 - 力扣(LeetCode)


给定一个包含非负整数的 m * n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

提示:

  • m == grid.length
  • n == grid[ i ].length
  • 1 <= m, n <= 200
  • 0 <= grid[ i ][ j ] <= 100

方法一:DP 

抽象题中线索:

  • 状态定义:从 [0,0] 到 [i,j] min = ?
  • 状态间的转移方程定义:[i,j] += min ([i,j - 1],[i - 1,j])(有些路径只有一条,代码里体现)

  • 状态的初始化:[0, 0] min = [0, 0]
  • 返回结果:[底行, 行底]
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int row = grid.size();
        int col = grid[0].size();
        // 状态定义: 从 [0,0] 到 [i,j] min = ?
        vector<vector<int>> ret(row, vector<int>(col, 0));
        
        // 状态的初始化: [0, 0] min = [0, 0]
        ret[0][0] = grid[0][0];

        for(int i = 0; i < row; i++)
        {
            for(int j = 0; j < col; j++)
            {
                // 状态间的转移方程定义: [i,j] += min ([i,j - 1],[i - 1,j])
                if(i == 0 && j > 0) ret[0][j] = grid[0][j] + ret[0][j - 1];
                else if(j == 0 && i > 0) ret[i][0] = grid[i][0] + ret[i - 1][0];
                else if(j > 0 && i > 0) ret[i][j] = min(ret[i][j - 1], ret[i - 1][j]) + grid[i][j];
            }
        }
        
        // 返回结果: [底行, 行底]
        return ret[row - 1][col - 1];
    }
};

        方便看原理,下述代码将状态间的转移方程特殊情况,单独提出进行书写。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int row = grid.size();
        int col = grid[0].size();
        // 状态定义: 从 [0,0] 到 [i,j] min = ?
        vector<vector<int>> ret(row, vector<int>(col, 0));

        // 状态的初始化: [0, 0] min = [0, 0]
        ret[0][0] = grid[0][0];

        // 特殊转换状态:第一行、第一列
        for(int i = 1; i < row; i++) ret[i][0] = grid[i][0] + ret[i - 1][0];
        for(int i = 1; i < col; i++) ret[0][i] = grid[0][i] + ret[0][i - 1];

        for(int i = 1; i < row; i++)
        {
            for(int j = 1; j < col; j++)
            {
                // 状态间的转移方程定义: [i,j] += min ([i,j - 1],[i - 1,j])
                ret[i][j] = min(ret[i][j - 1], ret[i - 1][j]) + grid[i][j];
            }
        }
        // 返回结果: [底行, 行底]
        return ret[row - 1][col - 1];
    }
};

复杂度分析

  • 时间复杂度:O(m*n)。
  • 空间复杂度:O(m*n)。

进阶:

        你可以只使用 O(n) / O(m) 的额外空间来解决这个问题吗?

        此题与上一题极为的相似,所上一题采取的列解决,这一题就采取行解决。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int row = grid.size();
        int col = grid[0].size();
        // 状态定义: 从 [0,0] 到 [i,j] min = ?
        vector<int> ret(col, 0);
        
        // 状态的初始化: [0, 0]min = [0, 0]
        ret[0] = grid[0][0];

        for(int i = 0; i < row; i++)
        {
            for(int j = 0; j < col; j++)
            {
                // 状态间的转移方程定义: [j] = min ([j],[j-1]) + grid[i][j]
                if(i == 0 && j > 0) ret[j] = ret[j - 1] + grid[i][j];
                else if(j == 0 && i > 0) ret[j] = ret[j] + grid[i][j];
                else if(i > 0 && j > 0)ret[j] = min(ret[j - 1], ret[j]) + grid[i][j];
            }
        }

        // 返回结果: [底行, 行底]
        return ret[col - 1];
    }
};
  • 时间复杂度:O(m*n)。
  • 空间复杂度:O(n)。

背包问题⭐⭐⭐

125 · 背包问题(二) - LintCode


有 n 个物品和一个大小为 m 的背包,给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值。

问最多能装入背包的总价值是多大?

提示:

  1. A[i], V[i], n, m 均为整数
  2. 你不能将物品进行切分
  3. 你所挑选的要装入背包的物品的总大小不能超过 m
  4. 每个物品只能取一次
  5. m <= 1000len(A),len(V)<=100

方法一:DP  

抽象题中线索:

  • 状态定义:到第 i 商品的第 j 个空间 ret[i][j] 时的max价值 = ?
  • 状态间的转移方程定义:
  1. 当前空间 j 不够放该商品,原封不动(上次商品的样子)ret[i][j] = ret[i - 1][j] 
  2. 当前空间 j 够放该商品max( (放入i + 剩余空间能放的max) , (前面 i-1) )ret[i][j] = max(ret[i - 1][j],v[i - 1] + ret[i - 1][j - a[i - 1]]) 

  • ​​​​​​​状态的初始化:没有商品 ret[0][j]max价值 = 0,没有空间 ret[i][0]max价值 = 0
  • 返回结果:到第 最后 商品的第 最后 空间 ret[最后][最后] 时的max价值 = ?

class Solution {
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param a: Given n items with size A[i]
     * @param v: Given n items with value V[i]
     * @return: The maximum value
     */
    int backPackII(int m, vector<int> &a, vector<int> &v) {
        // write your code here
        // 状态定义: 到第 i 商品的第 j 个空间 ret[i][j] 时的max价值 = ?
        // ​​​​​​​状态的初始化: 没有商品 ret[0][j]max价值 = 0,没有空间 ret[i][0]max价值 = 0
        vector<vector<int>> ret(a.size() + 1, vector<int>(m + 1, 0));

        // 状态间的转移方程定义: 
        for(int i = 1; i <= a.size(); i++)
        {
            for(int j = 1; j <= m; j++)
            {
                // 当前空间 j 不够放该商品,原封不动(上次商品的样子): ret[i][j] = ret[i - 1][j] 
                if(a[i - 1] > j)
                    ret[i][j] = ret[i - 1][j];
                // 当前空间 j 够放该商品( max( (放入i + 剩余空间能放的max) , (前面 i-1) ) ): ret[i][j] = max(ret[i - 1][j],v[i - 1] + ret[i - 1][j - a[i - 1]]) 
                else
                    ret[i][j] = max(ret[i - 1][j], v[i - 1] + ret[i - 1][j - a[i - 1]]);
            }
        }
        // 返回结果: 到第 最后 商品的第 最后 空间 ret[最后][最后] 时的max价值 = ?
        return ret[a.size()][m];
    }
};

复杂度分析

(m:背包大小;n:商品个数)

  • 时间复杂度:O(m*n)。
  • 空间复杂度:O(m*n)。

进阶:

        你可以只使用 O(m) 的额外空间来解决这个问题吗?

        我们可以发现,我们使用的仅仅就是 i -1 个商品时和 i 个商品时背包的状态。所我们可以将 m*n 变为 m*2,即:O(m)。

class Solution {
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param a: Given n items with size A[i]
     * @param v: Given n items with value V[i]
     * @return: The maximum value
     */
    int backPackII(int m, vector<int> &a, vector<int> &v) {
        // write your code here
        // 状态定义: 
        // ​​​​​​​状态的初始化: 
        vector<vector<int>> ret(2, vector<int>(m + 1, 0));

        // 最后一组数据
        int row = 0;
        // 状态间的转移方程定义: 
        for(int i = 1; i <= a.size(); i++)
        {
            for(int j = 1; j <= m; j++)
            {
                row = i % 2;
                if(a[i - 1] > j)
                    ret[row][j] = ret[(row + 1)%2][j];
                else
                    ret[row][j] = max(ret[(row + 1)%2][j], v[i - 1] + ret[(row + 1)%2][j - a[i - 1]]);
            }
        }
        // 返回结果: 
        return ret[row][m];
    }
};

核心:

  • row = i % 2 是当前行
  • (row + 1) % 2 是上一行

待更……

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

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

相关文章

从CI/CD持续集成部署到DevOps研发运维一体化

今天整理下从传统的CI/CD到DevOps研发运维一体化的整个演进过程。类似于每日构建和冒烟测试&#xff0c;实际上在10多年前就已经在实践&#xff0c;比如当前用的笔记多的AntCruiseControl方式来实现自动化的编译构建和持续集成能力。 包括当前DevOps过程实践中的持续集成&…

基于Springboot的班级综合测评管理系统的设计与实现

摘要 随着互联网技术的高速发展&#xff0c;人们生活的各方面都受到互联网技术的影响。现在人们可以通过互联网技术就能实现不出家门就可以通过网络进行系统管理&#xff0c;交易等&#xff0c;而且过程简单、快捷。同样的&#xff0c;在人们的工作生活中&#xff0c;也就需要…

Android内存泄漏问题排查分析及常见解决方案

什么是内存泄漏&#xff1a; 在Android开发过程中&#xff0c;当一个对象已经不需要再使用了&#xff0c;本该被回收时&#xff0c;而另个正在使用的对象持有它引用从而导致它不能被回收&#xff0c;这就导致本该被回收的对象不能被回收而停留在堆内存中&#xff0c;内存泄漏就…

你真的熟悉多线程的程序的编写?快来查漏补缺

目录 一、Thread 类的属性及常用的构造方法 1.1、 Thread 常见构造方法 1.2、Thread 类的常见属性 1.3、启动&#xff08;创建&#xff09;一个线程 1.4、中断一个线程 1.5、等待一个线程 1.6、休眠当前线程 1.7、当前线程让出的 CPU 资源 二、线程状态 一、Thread 类…

华为OD机试真题(Java),整数编码(100%通过+复盘思路)

一、题目描述 实现一个整数编码方法&#xff0c;使得待编码的数字越小&#xff0c;编码后所占用的字节数越小。 编码规则如下&#xff1a; 编码时7位一组&#xff0c;每个字节的低7位用于存储待编码数字的补码&#xff1b;字节的最高位表示后续是否还有字节&#xff0c;置1表…

2023联网公司时薪排行榜出炉,多多排榜首。微软、美团很强

今天分享一个对于选择公司非常有用的参考&#xff1a;“互联网时薪”。 我们在选择一个公司的时候&#xff0c;往往会比较关注总收入package (除了基本的月薪&#xff0c;加上其他的所有的收入&#xff0c;包括但不限于奖金、股票或股份的分红等等)。 然而&#xff0c;总收入…

算力网络安全

算力网络安全 1. 算力网络简介1.1 基本概念1.2 应用场景 2. 算力网络安全需求3. 算力网络安全架构3.1 算力网络参考架构3.2 资源层安全3.3 控制层和编排管理层安全3.4 服务层安全 4. 算力网络安全关键技术4.1 安全计算4.2 安全编排4.3 数据溯源4.4 可信内生4.5 操作审计4.6 安全…

【服务器】Linux搭建我的世界服务器 + 公网远程联机教程

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1. 安装JAVA 2. MCSManager安装 3.局域网访问MCSM 4.创建我的世界服务器 5.局域网联机测试 6.安装cpolar内网穿透 7. 配置公网访问地址 8.远程联机测试 9. 配置固定…

ICMP 协议详解

文章目录 1 概述2 ICMP 协议2.1 工作原理2.2 报文格式2.3 ICMP 类型 1 概述 #mermaid-svg-6yUB8ZNYSzjbbDDq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6yUB8ZNYSzjbbDDq .error-icon{fill:#552222;}#mermaid-s…

Qt 路径

Qt 路径 Qt中路径问题小结目录与路径的区别路径分隔符Qt 路径与 Windows 路径转换相对路径mkdir 和 mkpath判断目录是否存在修改路径setPath Qt中路径问题小结 原文链接&#xff1a;https://blog.csdn.net/Andy_93/article/details/52831175 在做Qt项目的时候&#xff0c;我们…

【重学c++primer】第二章 深入浅出:变量的类型

文章目录 【重学cprimer】第二章 变量以及变量的基本类型1、从初始化/赋值语句说起2、类型详解一些未定义部分字面值变量以及变量的类型隐式类型转换 3、复合类型&#xff1a;从指针到引用指针的操作void*指针的好处引用指针的引用 4、常量和常量表达式类型const和指针const的赋…

AndroidStudio导入Android AOSP源码

一、生成导入到AS所需的配置文件 1.1、切换到Android源码的目录&#xff0c;执行配置环境脚本 source build/envsetup.sh1.2、执行lunch,选择对应产品 lunch1.3、执行make idegen make idegen编译完成后&#xff0c;就可以在Android源码的根目录下看到android.iml和android…

元宇宙展厅--音乐科技展厅

作为音乐科技领域的先锋&#xff0c;这里是一个展示最新音乐科技的创新空间。我们的元宇宙展厅汇聚了来自世界各地最前沿的音乐创新&#xff0c;将展示最新、具有前瞻性的音乐科技应用。让您可以深入了解这个领域的最新发展。 一、音乐科技展厅概述 让我们来了解一下我们的元宇…

首期smardaten无代码训练营圆满收官,两周内容精彩回顾!

”smardaten无代码训练营&#xff0c;旨在通过线上碎片化时间的课程学习实操演练&#xff0c;帮助学员探索产品能力&#xff0c;验证项目需求&#xff0c;实现多个demo系统的复刻搭建。“ 首期smardaten无代码训练营于上周圆满收官&#xff01;本期共有64名学员报名参加&#…

KDZR-10A三相直流电阻测试仪

一、产品概述 直流电阻的测量仪是变压器、互感器、电抗器、电磁操作机构等感性线圈制造中半成品、成品出厂试验、安装、交接试验及电力部门预防性试验的项目&#xff0c;能有效发现感性线圈的选材、焊接、连接部位松动、缺股、断线等制造缺陷和运行后存在的隐患。 为了满足感…

Hive ---- DDL(Data Definition Language)数据定义

Hive ---- DDL&#xff08;Data Definition Language&#xff09;数据定义 1. 数据库&#xff08;database&#xff09;1. 创建数据库2. 查询数据库3. 修改数据库4. 删除数据库5. 切换当前数据库 2. 表&#xff08;table&#xff09;1. 创建表2. 查看表3. 修改表4. 删除表5. 清…

07_阻塞队列(BlockingQueue)

目录 1. 什么是BlockingQueue 2. 认识BlockingQueue 3. 代码演示 栈与队列概念 栈(Stack)&#xff1a;先进后出&#xff0c;后进先出 队列&#xff1a;先进先出 1. 什么是BlockingQueue 在多线程领域&#xff1a;所谓阻塞&#xff0c;在某些情况下会挂起线程&#xff08;即…

JVM 基本知识

目录 前言 一、JVM 内存区域划分 1.1 程序计数器 1.2 栈 1.3 堆 1.4 方法区 二、 JVM 类加载机制 2.1 类加载需要经过的几个步骤 2.1.1 Loading - 加载 2.1.2 Linking - 连接 2.1.3 initialization&#xff08;初始化&#xff09; 小结 经典面试题 三、JVM 垃圾…

天河新一代,安装OpenCV

1&#xff09;下载 Releases opencv/opencv GitHub 下载一个版本&#xff0c;传上去。 解压&#xff0c;因为只要最基本的功能&#xff0c;所以不需要ctri等包。 2&#xff09; 一些选项 cmake .. -D<选项名1><设定值1> -D<选项名2><设定值2> …

Metasploit Framework-安全漏洞检测工具使用

一款开源的安全漏洞检测工具&#xff0c;简称MSF。可以收集信息、探测系统漏洞、执行漏洞利用测试等&#xff0c;为渗透测试、攻击编码和漏洞研究提供了一个可靠平台。 集成数千个漏洞利用、辅助测试模块&#xff0c;并保持持续更新。 由著名黑客、安全专家H.D. Moore主导开发…