算法篇——动态规划 完全和多重背包问题 (js版)

news2024/11/16 12:00:41

一些分析总结

01 背包 问题和 完全背包 问题的不同点在于,所有的物品只能使用一次,判断 哪些物品 装进背包里 物品价值和 最大;而 完全背包 问题中,所有物品都能使用n次,判断 哪个物品 装 n 个进去 物品价值和 最大。

01 背包的递推公式是:【当然先遍历物品还是背包的容量都可以】

for(var i = 0; i < nums.length; i++) {
   for(var j = target; j <= nums[i]; j++) {
      dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
   }
}

还有一种就是 一维滚动数组,原本的 二维数组 dp[i][j] 表示第 i 层、背包容量为 j 时的状态值,而递推中 只需要用到上一层的状态 dp[i-1][j] 和 dp[i-1][j-weight[i]]。因此,可以通过在一维数组中使用滚动的方式,将上一层的状态直接拷贝到当前层,从而节省空间。这种优化仅适用于无需回溯到更早层次,只需要关注前一层状态的情况。

for(int i = 0; i < nums.length; i++) {
    for(int j = target; j >= nums[i]; j--) {
        dp[j] = max(dp[j], dp[j - nums[i]] + value[i]);
    }
}

这里的内层循环采用了倒序遍历,dp[j] 表示遍历到第 i 个物品、背包容量为 j 时的最优解。倒序遍历可以确保在计算 dp[j] 时,dp[j - weight[i]] 仍然表示第 i-1 个物品、背包容量为 j-weight[i] 时的最优解,不会受到当前层的状态更新影响。


接下来就是这篇文章的重头戏,完全背包 问题:每种物品都有无限件可供选择,求在有限的背包容量下,如何选择物品价值和最大化。

1.首先就是确立状态dp[i][j] 表示前 i 个物品,背包容量为 j 时的最大价值。

对于第 i 个物品,可选放入多个此它,直到超过背包容量。 对于每个物品,要么放入,要么不放。

如果选择放入,剩余的背包容量就变成 j - weight[i]。 在放入一个物品后,变为求前 i 个物品,背包容量为 j - weight[i] 的最大价值,即 dp[i][j - weight[i]]

如果选择不放入,变为求前 i-1 个物品,背包容量为 j 的最大价值,即 dp[i-1][j]

因此递推公式为:

for(let j = 0; j <= bagWeight; j++) {
    for(let i = 0; i < nums.length; i++) {
        dp[j] = Math.max(dp[j], dp[j - nums[i]] + value[i])
    }
}

OR 

for(let i = 0; i < nums.length; i++) {
    for(let j = nums[i]; i <= bagWeight; j++) {
        dp[j] = Math.max(dp[j], dp[j - nums[i]] + value[i])
    }
}

此处的两层循环可以颠倒:

按照 外层循环是物品,内层循环是背包容量 的遍历方式时,如果当前物品 i 的重量 weight[i] 较小,在更新 dp[j] 的过程中,将会使用到同一层之前已经更新过的状态值 dp[j - weight[i]]。 由于 完全背包问题 每个物品可以选择多次,在计算 dp[j - weight[i]] 时,确保计算了当前物品 i ,得到 dp[j - weight[i] + value[i]]。在计算 dp[j] 时,如果再使用 dp[j - weight[i]] 进行更新,相当于将物品 i 又选一次,这样就允许了重复选择相同物品的情况。

进入正题 

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

链接:力扣

解题思路:

这道题是完全背包问题,但也涉及到排列组合的问题,因为 5=2+2+1 和 5=2+1+2 是两种组合方式,但我们这里只需要 组合方式 即可,上面说到完全背包问题的两层循环是可以颠倒的,但本题很特殊,如果进行了颠倒会怎么样,下面进行分析:

假如输入是 amount = 6, coins = [1, 5]

// 先遍历物品
for (let i = 0; i < coins.size(); i++) { 
    // 再遍历背包容量
    for (let j = coins[i]; j <= amount; j++) {
        dp[j] += dp[j - coins[i]]
    }
}

执行顺序:

  1. 外层循环遍历物品:

    • 从 i = 0 开始,遍历每个物品
    • coins.size() 表示物品的数量,coins[i] 表示第 i 个物品的价值
  2. 内层循环遍历背包容量:

    • 从 j = coins[i] 开始,直到超过目标容量 amount
    • dp[j] += dp[j - coins[i]] 表示更新当前背包容量 j 下的最优解,将之前计算过的状态值 dp[j - coins[i]] 加上当前物品的价值,即选择第 i 个物品的情况

通过依次遍历每个物品和背包容量,不断更新最优解,最终得到完全背包问题的最优解

先将 1 加入计算,再计算 5,得到的方法只有 {1, 5},没有 {5, 1},得到了组合数是 1

如果将两层循环进行颠倒:

// 先遍历背包容量
for (let j = 0; j <= amount; j++) {
    // 再遍历物品
    for (let i = 0; i < coins.length; i++) { 
        dp[j] += dp[j - coins[i]]
    }
}

执行顺序:

  1. 外层循环遍历背包容量,从0到金额 amount
  2. 内层循环遍历物品
  3. 在内循环中,对于每个背包容量 j,通过累加计算凑成金额 j 的组合数量
    • 逐个考虑当前物品的面额 coins[i]
    • 如果 j - coins[i] >= 0,表示可以选择当前物品,将其加入组合中
    • 更新状态转移方程 dp[j] += dp[j - coins[i]],将前面计算得到的组合数量加到当前容量为 j 的组合数中
  4. 完成内层循环后,外层循环继续下一个背包容量的遍历
  5. 最终得到的数组 dp 中的元素 dp[amount] 即为凑成目标金额 amount 的组合数量

背包容量的每个值,都经过 1 和 5 ,包含了 {1, 5} 和 {5, 1} 两种情况,得到了排列数是 2

因此只能先遍历物品再遍历背包容量,完整代码如下:

var change = function(amount, coins) {
    // 如果这一个硬币等于 总金额,直接返回1
    if(coins.length == 1 && coins[0] == amount) return 1
    let dp = Array(amount + 1).fill(0)
    dp[0] = 1
    for(let i = 0; i < coins.length; i++) {
        for(let j = coins[i]; j <= amount; j++) {
            dp[j] += dp[j - coins[i]] 
        }
    }
    return dp[amount]
}

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

链接:力扣

解题思路:

本题就是区别于上题所说的,用到了排列的方式,以下是完整代码: 

var combinationSum4 = function(nums, target) {
    // 如果这一个元素等于 target ,直接返回1
    if(nums.length == 1) {
        if(nums[0] == target) return 1
        else if(nums[0] > target) return 0
    }
    let dp = Array(target + 1).fill(0)
    dp[0] = 1
    for(let j = 0; j <= target; j++) {
        for(let i = 0; i < nums.length; i++) {
            // 如果背包容量是大于当前元素的才能进行计算,不然装不下
            if(j >= nums[i]) dp[j] += dp[j - nums[i]] 
        }
    }
    return dp[target]
}

70. 爬楼梯 

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

链接:力扣

 

var climbStairs = function(n) {
    const dp = [1, 2]
    for(let i = 2; i < n; ++i) {
        dp[i] = dp[i - 1] + dp[i - 2]
    }
    return dp[n-1]
}

322. 零钱兑换 

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的
链接:力扣

外层循环遍历每种硬币,内层循环从当前硬币的面值开始,到目标金额结束,逐个更新dp数组。对于每个金额j,将其更新为 dp[j-coins[i]] + 1 与原来的 dp[j] 之间的较小值,其中 coins[i] 表示当前硬币面值。

最后,函数返回 dp[amount] 的值,如果 dp[amount] 仍为无穷大,则说明无法凑成目标金额,返回-1;否则返回最少所需硬币数量。

var coinChange = function(coins, amount) {
    if(amount == 0) return 0
    if(coins.length == 1 && coins[0] > amount) return -1
    let dp = Array(amount + 1).fill(Infinity)
    dp[0] = 0
    for(let i =0; i < coins.length; i++) {
        for(let j = coins[i]; j <= amount; j++) {
            dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j])
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount]
}

279. 完全平方数 

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
链接:力扣

  • 使用两层循环来遍历完全平方数。外层循环从1开始逐个取平方根,直到平方值 <= n
  • 内层循环从当前完全平方数开始,到目标数n结束,逐个更新dp数组。对于每个数j,将其更新为 dp[j-val] + 1 与原来的 dp[j] 之间的较小值,其中val表示当前完全平方数的值
var numSquares = function(n) {
    let dp = Array(n + 1).fill(Infinity)
    dp[0] = 0
    for(let i =1; i ** 2 <= n; i++) {
        let val = i ** 2
        for(let j = val; j <= n; j++) {
            dp[j] = Math.min(dp[j - val] + 1, dp[j])
        }
    }
    return dp[n]
}

139. 单词拆分 

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

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

var wordBreak = function(s, wordDict) {
    let dp = Array(s.length + 1).fill(false)
    dp[0] = true
    // 排列问题,先遍历背包容量,再遍历物品
    for(let i = 0; i <= s.length; i++){
        for(let j = 0; j < wordDict.length; j++) {
            if(i >= wordDict[j].length) {
                if(s.slice(i - wordDict[j].length, i) === wordDict[j] && dp[i - wordDict[j].length]) dp[i] = true
            }
        }
    }
    return dp[s.length]
}

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

链接:力扣

解题思路:

“如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警”,则当我偷第 i  间房时,就不考虑 第 i-1 间,然后去偷 i+2 间房,反之就是偷 i-1 间房。 并且dp[0] 定是 nums[0],dp[1] 是max(nums[0], nums[1]),来决定我以何种间隔进行计算。

var rob = function(nums) {
    if(nums.length == 0) return 0
    const dp = [nums[0], Math.max(nums[0], nums[1])]
    for(let i = 2; i < nums.length; i++) {
        // 错位相加
        dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
    }
    return dp[nums.length - 1]
}

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
链接:力扣

解题思路:

此题和上一题的不同之处就是成了环,在原有的相邻的不能偷的基础上,需要考虑情况偷了 nums[1] 就不能偷 nums[nums.length - 1],这就是区别。

var rob = function(nums) {
    if(nums.length == 1) return nums[0]
    let res1 = toRob(nums, 0, nums.length - 2)
    let res2 = toRob(nums, 1, nums.length - 1)
    return Math.max(res1, res2)
}

var toRob = function(nums, start, end) {
    // 如果首尾相同,偷一个
    if(start == end) return nums[start]
    const dp = Array(nums.length).fill(0)
    // 从第一个开始
    dp[start] = nums[start]
    // 选择偷第一个房或第二个房
    dp[start+1] = Math.max(nums[start], nums[start+1])
    for(let i = start + 2; i <= end; i++) {
        dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1])
    }
    return dp[end]
}

337. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
链接:力扣

解题思路:

通过使用一个长度为2的数组的状态转移方程,来记录状态变化,记录当前节点选择偷还是不偷所得到的最大金额,并且将其结果存储在哈希表中,避免了重复遍历。

需要分别计算不偷当前节点和偷当前节点的最大金额:

  • 不偷当前节点时,可以选择偷或不偷左子节点,偷或不偷右子节点,取两者之间最大值,保存在变量 Not 中
  • 偷当前节点时,不能偷其左右子节点,将当前节点的值 与 左子节点不偷的最大金额 与 右子节点不偷的最大金额 相加,保存在变量 Do 中
const rob = function(root) {
    // 通过哈希表保存已计算过的节点的结果
    const map = new Map()
    // 后序遍历函数
    const postOrderTraversal = function(node) {
        // 递归出口
        if (!node) return [0, 0]
        // 避免重复遍历
        if(map.has(node)) return map.get(node)
        // 遍历子树
        const left = postOrderTraversal(node.left)
        const right = postOrderTraversal(node.right)
        // 不偷当前节点,其左右子节点可以偷或不偷,取最大值
        const Not = Math.max(left[0], left[1]) + Math.max(right[0], right[1])
        // 偷当前节点,左右子节点只能不偷
        const Do = node.val + left[0] + right[0]
        // 返回选择偷还是不偷
        const result =  [Not, Do]
        map.set(node, result)
        return result
    }
    const res = postOrderTraversal(root)
    // 返回最大值
    return Math.max(...res)
}

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
链接:力扣

解题思路:

  1. dp[i] 表示第 i 天结束后的状态,每个状态由两个值组成:dp[i][0] 表示持有股票时的最大利润,dp[i][1] 表示不持有股票时的最大利润
  2. 初始化第一天的状态为 [-prices[0], 0],即第一天选择买入股票,没有卖出
  3. 从第二天开始遍历价格数组,对于每一天 i
    • dp[i][0] 的取值为 前一天持有股票的最大利润 与 当天买入股票后的利润 中的较大值,即 Math.max(dp[i - 1][0], -prices[i])。表示选择 保持前一天的股票状态将前一天 不持有股票 的状态转变为 持有股票 的状态
    • dp[i][1] 的取值为 前一天不持有股票的最大利润当天卖出股票后的利润 中的较大值,即 Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0])。表示选择 保持前一天的股票状态将前一天持有股票 的状态转变为 不持有股票 的状态,并获得当天的卖出利润
  4. 最终结果为 dp[len - 1][1],表示最后一天不持有股票时的最大利润

贪心算法: 

var maxProfit = function(prices) {
    if(prices.length == 1) return 0
    let min = prices[0], max = 0
    for(let i = 0; i < prices.length; i++) {
        // 前一天的差值和今天卖出的差值
        max = Math.max(max, prices[i] - min)
        // 之前买入的价格和今天买入的价格
        min = Math.min(min, prices[i])
    }
    return max
}

动态规划:

var maxProfit = function(prices) {
    const len = prices.length
    // 创建dp数组
    const dp = new Array(len).fill([0, 0])
    // dp 数组初始化
    dp[0] = [-prices[0], 0]
    for (let i = 1; i < len; i++) {
        // 更新dp[i]
        dp[i] = [
            // 前一天持有股票的最大利润 与 当天买入股票后的利润 的最大值
            Math.max(dp[i - 1][0], -prices[i]),
            // 前一天不持有股票的最大利润 与 当天卖出股票后的利润 的最大值
            Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]),
        ]
    }
    return dp[len - 1][1]
}

122. 买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。
链接:力扣

本题和上一题的不同之处在于:

  • 上一题股票只能买卖一次,如果买入股票,那么第 i 天持有股票,即 dp[i][0] 是 -prices[i]
  • 本题一只股票可以买卖多次,当第 i 天买入股票时,持有的现金可能有之前产生的利润
  • 第 i 天持有股票即 dp[i][0],如果第 i 天买入股票,所得现金是 昨天不持有股票的所得现金 减去 今天的股票价格,即 dp[i - 1][1] - prices[i]

贪心算法: 

var maxProfit = function(prices) {
    var res = 0
    // 计算正利润
    var odd = 0
    for(var i = prices.length-1; i >= 0; i--) {
        // 进行差值计算
        odd = prices[i] - prices[i-1]
        // 如果是正利润就放入结果中
        if(odd > 0) res += odd
        else odd = 0
    }
    return res
}

动态规划:

var maxProfit = function(prices) {
    const len = prices.length
    let dp = Array.from(Array(len).fill(0))
    dp[0] = [-prices[0], 0]
    for(let i = 1; i < len; i++) {
        dp[i] = [
            // dp[i-1][1] - prices[i] = 不持有的利润 减去 买入的价格
            Math.max(dp[i-1][0], dp[i-1][1] - prices[i]),
            Math.max(dp[i-1][1], dp[i-1][0] + prices[i])
        ]
    }
    return dp[len -1][1]
}

123. 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
链接:力扣

解题思路:

这一题相较于上面两题,不同之处在于,只能进行两次交易,因此我们需要考虑:

何时第一次买入,何时第一次卖出

何时第二次买入,何时第二次卖出

代码不同的地方就是递推公式需要判断 买入时的利润卖出的利润 是基于第几次 

var maxProfit = function(prices) {
    const len = prices.length
    let dp = Array.from(Array(len).fill(0))
    dp[0] = [-prices[0], 0, -prices[0], 0]
    for(let i = 1; i < len; i++) {
        dp[i] = [
            Math.max(dp[i-1][0], -prices[i]),
            Math.max(dp[i-1][1], dp[i-1][0] + prices[i]),
            Math.max(dp[i-1][2], dp[i-1][1] - prices[i]),
            Math.max(dp[i-1][3], dp[i-1][2] + prices[i])
        ]
    }
    return dp[len -1][3]
}

188. 买卖股票的最佳时机 IV

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格,和一个整型 k 。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
链接:力扣

解题思路:

基于上面一题的思路,我们不可能将k次全部列出,但卖出之前必须买入,可得,奇数时买入,偶数时卖出

  • 初始化第一天的状态,对于奇数索引 j,即买入状态,将其设置为 0 - prices[0],表示第一天进行买入操作得到的利润
  • 从第二天开始遍历价格数组,对于每一天 i 和交易次数索引 j(从0开始,以2递增)
  • 更新 dp[i][j+1],表示第 i 天结束后进行 j+1 次交易时 持有股票的最大利润。取 前一天持有股票的最大利润 dp[i-1][j+1] 和 前一天不持有股票的最大利润当天买入股票后的利润 的较大值
var maxProfit = function(k, prices) {
    const len = prices.length
    // 行数 prices.length,列数 2 * k + 1
    let dp = Array.from(Array(prices.length), () => Array(2*k+1).fill(0))
    // 第一天买入时得到的利润
    for(let i = 1; i < 2 * k; i += 2) {
        dp[0][i] = -prices[0]
    }
    for(let i = 1; i < len; i++) {
        for (let j = 0; j < 2 * k; j += 2) {
            dp[i][j+1] = Math.max(dp[i-1][j+1], dp[i-1][j] - prices[i]);
            dp[i][j+2] = Math.max(dp[i-1][j+2], dp[i-1][j+1] + prices[i]);
        }
    }
    return dp[len - 1][2 * k]
}

309. 最佳买卖股票时机含冷冻期

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
链接:力扣

 

dp[i][0]:表示第 i 天结束时持有股票的最大利润

dp[i][1]:表示第 i 天结束时不持有股票且处于冷冻期的最大利润

dp[i][2]:表示第 i 天结束时不持有股票且不处于冷冻期的最大利润

var maxProfit = function(prices) {
    const len = prices.length
    let dp = Array.from(Array(len), () => Array(3).fill(0))
    dp[0][0] = -prices[0]
    for (let i = 1; i < len; i++) {
        dp[i][0] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][0])
        dp[i][1] = dp[i - 1][0] + prices[i]
        dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2])
    }
    return Math.max(dp[len - 1][1], dp[len - 1][2])
}

714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
链接:力扣

var maxProfit = function(prices, fee) {
    const len = prices.length
    let dp = Array.from(Array(len).fill(0))
    dp[0] = [-prices[0], 0]
    for(let i = 1; i < len; i++) {
        dp[i] = [
            Math.max(dp[i-1][0], dp[i-1][1] - prices[i]),
            Math.max(dp[i-1][1], dp[i-1][0] + prices[i] - fee)
        ]
    }
    return dp[len -1][1]
}

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

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

相关文章

Mybatis源码分析_日志模块 (1)

不得不承认&#xff0c;学习MyBatis的时间成本要比学习Spring低很多&#xff0c;Mybatis是我所了解过的代码量最小、整体架构最简单&#xff0c;但是又是最具有学习价值的一个框架。如果&#xff0c;你想快速的阅读一个框架的源码&#xff0c;并且掌握这个框架的精髓&#xff0…

浅析GeoServer CVE-2023-25157 SQL注入

简介 GeoServer是一个开源的地图服务器&#xff0c;它是遵循OpenGIS Web服务器规范的J2EE实现&#xff0c;通过它可以方便的将地图数据发布为地图服务&#xff0c;实现地理空间数据在用户之间的共享。 影响版本 geoserver<2.18.7 2.19.0<geoserver<2.19.7 2.20.0…

国内外八大敏捷开发工具盘点

1、Leangoo领歌&#xff1b;官网&#xff1a;Leangoo领歌 - 高效企业必备的敏捷工具,Scrum工具,SAFe敏捷工具,敏捷项目管理,敏捷研发工具 2、VersionOne&#xff1b;官网&#xff1a;https://www.collab.net/products/versiononehttps://www.collab.net/products/versionone …

Python高级系列教程:Python高级语法与正则表达式

学习目标 1、能够掌握with语句的使用 2、能够知道生成器的两种创建方式 3、能够知道深拷贝和浅拷贝的区别 4、能够掌握Python中的正则表达式编写 一、Python高级语法 1、with语句和上下文管理器 ☆ with语句 Python提供了 with 语句的写法&#xff0c;既简单又安全。文件操…

Vue-scoped(局部)样式

scoped(局部)样式 scoped是在脚手架有一个编写样式的小技巧 作用&#xff1a;让样式在局部生效&#xff0c;防止冲突 1 编写案例 现在有两个组件&#xff0c;一个student,一个school&#xff0c;现在想给组件写点样式 这里只给个背景色 没问题&#xff0c;样式生效 2 样式冲…

docker canal 安装

(373条消息) 使用 Docker 部署 canal 服务_canal docker部署_qq2276031的博客-CSDN博客https://blog.csdn.net/qq2276031/article/details/120234122 docker canal github 网址 Canal Admin Docker alibaba/canal Wiki (github.com)https://github.com/alibaba/canal/wiki/Ca…

璞华产业园区租赁运营平台,助力空间资产管理数字化转型!

{ 产业园区租赁运营平台 } 直面行业痛点 专注技术创新 点击输入图片描述&#xff08;最多30字&#xff09; 产业园区作为产业转型升级的重要载体&#xff0c;产业园区租赁运营也正迎来新的发展机遇。璞华一直关注为客户智能化转型过程中提供的服务&#xff0c;能否将技术方案…

【自监督论文阅读 3】DINOv1

文章目录 一、摘要二、引言三、相关工作3.1 自监督学习3.2 自训练与知识蒸馏 四、方法4.1 SSL with Knowledge Distillation4.2 教师网络4.3 网络架构4.4 避免坍塌 五、实验与评估六、消融实验6.1 不同组合的重要性6.2 教师网络选择的影响6.3 避免坍塌6.4 在小batch上训练 七、…

elk搭建

1、前言 ELK 是一个开源的日志管理解决方案&#xff0c;主要是为了统一收集生产的日志&#xff0c;方便日志的查询&#xff0c;传统的日志都是保存在每个机器上&#xff0c;当要查询的时候&#xff0c;需要到每一台机器上去查询日志&#xff0c;非常麻烦&#xff0c;而elk则使…

阿里巴巴 Java 开发手册部分整理

阿里巴巴 Java 开发手册 一、编程规约(一) 命名风格(二) 常量定义(三) 代码格式(四) OOP 规约(五) 集合处理(六) 并发处理(七) 控制语句(八) 注释规约(九) 其它 二、异常日志(一) 异常处理(二) 日志规约 三、单元测试四、安全规约五、MySQL 数据库(一) 建表规约(二) 索引规约(三…

Java 操作pdf工具类

1、获取pdf页数 添加maven依赖&#xff1a; <!-- java获取Pdf文件页码 --><dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>1.8.11</version></dependency>代码实现&#x…

Goby 漏洞发布|电信网关配置管理系统后台 /manager/teletext/material/upload.php 文件上传漏洞

漏洞名称&#xff1a;电信网关配置管理系统后台 /manager/teletext/material/upload.php 文件上传漏洞 English Name&#xff1a;Telecom system /manager/teletext/material/upload.php fileupload vulnerability CVSS core: 9.8 影响资产数&#xff1a;856 漏洞描述&…

不过时的经典层架构

在《设计服务要考虑的7个维度》中讲到设计一定要把不稳定的部分做封装。今天咱们就从这个角度重新审视一下经典的四层架构。 上面是一个经典层架构的示意图。这个架构大多数的公司和项目都在直接用&#xff0c;或者用其变体。 比如&#xff0c;某大厂上层Client客户端可能是电商…

安卓蓝牙GATT协议介绍

前言 现在低功耗蓝牙&#xff08;BLE&#xff09;连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范&#xff0c;这些很短的数据段被称为属性&#xff08;Attribute&#xff09;。 GAP 详细介绍 GAT…

GBASE金融信创优秀解决方案鉴赏 · 核心业务系统数据库解决方案

为此&#xff0c;实验室特别开设金融信创优秀解决方案专栏&#xff0c;集中展示优秀成果。现在&#xff0c;让我们一起来领略下GBASE的优秀解决方案吧~可点击阅读原文 →《金融信创优秀解决方案--核心业务系统数据库解决方案》。 核心业务系统数据库解决方案 方案简介 随着技…

Android Qcom USB Driver学习(十三)

该系列文章总目录链接与各部分简介&#xff1a; Android Qcom USB Driver学习(零) Android Qcom USB Driver学习(八) Android Qcom USB Driver学习(九) UCSI USB Type-C Connector System Software Interface Specification DPM Device Policy Manager deals with the USB P…

智安网络|驾驭数字化转型时代:加速业务转型的战略

随着科技的飞速发展和数字化时代的到来&#xff0c;企业面临着前所未有的机遇和挑战。数字化正在改变着商业的方方面面&#xff0c;而那些能够及时适应和把握这些变化的企业将脱颖而出。因此&#xff0c;加速企业转型成为了当务之急。 一、为什么需要加速企业转型 1.全球市场竞…

D. Pairs of Segments(最大不相交区间数量)

Problem - D - Codeforces 给定一个由n个线段组成的数组[[l1,r1],[l2,r2],…,[ln,rn]]&#xff0c;其中每个线段用左右端点表示。如果存在至少一个x满足l1≤x≤r1且l2≤x≤r2&#xff0c;则称线段[l1,r1]和[l2,r2]相交。 如果k为偶数&#xff0c;并且可以将该数组的元素分成k/…

VTK裁剪【3】-vtkClipPolyDatavtkPolyPlane问题

前言&#xff1a;本博文主要记录vtkClipPolyData中采用vtkPolyPlane作为裁剪工具时的出现的问题&#xff0c;供各位小伙伴进行参考&#xff0c;避免踩坑&#xff01; 目录 vtkPolyPlane介绍及作用 vtkClipPolyData原理 实现流程&#xff1a; 问题所在&#xff1a; 需求&…

css绘制网格背景

文章目录 前言效果图说明 前言 本篇文章主要简单扼要的去实现css网格背景&#xff0c;并进一步探求其应用原理 效果图 css代码 body::before, body::after {position: fixed;top: 0;left: 0;right: 0;bottom: 0;content: ;background-repeat: repeat;pointer-events: none;o…