算法 动态规划

news2024/11/26 12:45:00

更多文章:https://www.pandaer.space

动态规划

算法很简单!今天我们来聊聊动态规划,我们先从动态规划怎么来的讲起,然后聊聊动态规划应该如何学?最后正式开始动态规划的学习之旅。

动态规划怎么就出现了呢?

存在即合理,动态规划的出现一定是为了解决一些问题的?那是解决什么样的问题呢?在我看来动态规划是一种优化手段,如果你了解过一点动态规划,就一定听说过这样一句话,“能用动态规划解决的问题,一定可以用回溯解决”。没错,在我的认知中,动态规划是回溯的某些情况下的优化算法。那是哪些情况呢?学过回溯之后,我们知道回溯的核心是构建一个多阶段决策树,树的每一层代表一个阶段,这一层的节点代表了这一层的决策选择,但是这些决策选择有可能是重复的,也有可能是冗余的,但是回溯可不管这些即便是重复的也会继续往下决策,这样就有一些重复或者冗余决策,这就导致了回溯的时间复杂度比较高。而动态规划优化的就是这类情况。即 “回溯 + 重复/冗余子状态 = 动态规划”。

到此,我们知道了回溯出现的原因,接下来就来聊聊学习动态规划的正确姿势,既然动态规划是针对某些特定情况的优化,那么只要我们能够记住这些特定的情况,那么在遇到这些特定情况的时候,就知道需要使用动态规划了。没错,就是记特殊情况,说的高大上一点就是记模型,从整体上看,这些在这些特定情况下的问题,一般都是可行问题,计数问题,最值问题可行问题,需要我们判断某种状态是否可达。计数问题,需要我们给出到达某种状态有多少种可能。最值问题,需要我们给出到达某种状态,最大代价或者最小代价。但是在具体的模型下,这些问题的解法也有不同。我认为的模型有

  1. 背包模型
  2. 路径模型
  3. 打家劫舍&股票买卖模型
  4. 爬楼梯模型
  5. 匹配模型
  6. 其他模型

像我们这样的初学者,我们最应该掌握的是前5种模型,尤其是1,3,5模型,最重要的当属第一种模型,在学习具体的模型之前,我们先来看看动态规划的基本解题步骤

动态规划也很死板?

大致的解题步骤是这样的,如果这个问题可以用回溯解决,而且在回溯的过程中存在重复/冗余的状态就可以用动态规划。具体而言

  1. 判断这道题是否可以分阶段解决
  2. 画出这道题的决策树
  3. 从决策树中分析出状态
  4. 定义状态
  5. 给出状态转移方程
  6. 给出代码

在这6个步骤中,相对而言第4,5步是我们不熟悉,也是比较困难的,尤其对于新手而言。所谓的定义状态,就是明确在整个决策过程中,存在哪些不同的选择。而状态转移方程,表达的是当前阶段的状态如何通过之前的状态通过一定的加减乘除运算到达。虽然这两步比较困难,但是他们很固定,基本上就是上面那5个模型。所以掌握模型,吃定动态规划

掌握好模型,动态规划不用愁?

我们应该怎样学习这些模型呢?建立映射关系并能举一反三。举个例子,比如你看到一个问题,你能够将这个问题抽象为一个模型,并记住以及理解了在这个模型下的状态的定义,以及状态转移方程的推导,然后解决这个问题。就算掌握了这个模型。所以掌握模型的秘诀就是:不断总结,多多练习。所谓总结就是做完一道题之后,思考一下这道题的特点,以及这道题的特点和哪个模型比较像。接下来我们来仔细看看具体的模型吧

掌握好背包模型,动态规划就成功了一半

需要总结出背包模型,我们需要从一些经典的题目开始。

假设有一个背包,可承载的最大重量为w 千克。现在有 n 个物品,每个物品的重量weights[i](0<=i<n)不等并且不可分割。任选几件物品装入背包,是否能装满背包?

输入:
10
6
3 1 4 6 9 12

输出:
true

解释:
输入的第一行为w,第二行为n,第三行为n个物体的重量
4+6=10 因此可以装满背包。

阅读这道题目,我们可以分析出这是一道可行问题,满足利用动态规划解题的题目第一个特点。然后我们先忘记动态规划,看看如何用回溯来解决这个问题,并简单的画出决策树。你可以动手试试,这里我先给出我的思路,首先我们需要知道如何分阶段,这里我选择根据物品是否被选择来分阶段,即每个阶段决策这个物品是否在背包中,然后判断一下,在放入物品之后,是否装满背包,如果知道物品决策完毕之后,也没有满足装满背包,那么就可以确定这些物品不能装满背包。我们可以根据这个思路画出一个决策树。如下图:

在这里插入图片描述

这里我只画了前三个阶段,可以看到当决策4这个物品放不放入背包之后,存在两个相同重量的节点,如果再往下面进行决策的话,这两个节点对应的子树一定是一样的。这个就是重复的状态。

根据前面所讲的动态规划的内容,我们知道这道题可以用回溯来解决了,那么我们就可以开始来定义状态了,定义状态,一般情况下是执行某个操作之前或者之后,某个东西的情况。就比如上面这个思路,就是在决策一个物品是否放入背包后,背包的重量。这里背包的重量就是我们寻找的状态。我们定义状态的时候,必须定义完全并需要标识这种状态是否可达。比如这里,我们的状态时背包的重量,那么粗暴一点,背包重量的取值就为:[0,n] ,即每次决策完之后,背包的重量都有[0,n]这n+1种可能,所以我们可以利用一个二维数组来表示状态,i表示第i个物品决策之后,j表示当前背包的重量,arr[i][j] 的值表示在第i个物品决策完之后,背包重量为j的这种状态是否可达。到此状态我们就定义完成了,接下来看看状态转移方程,即如何基于前面的状态推导出现在的状态。这里就需要根据决策来推导,通常是反推,比如这里,当我们要判断arr[i][j] 这个状态是否可达时,我们需要这样想,要想到达这个状态,我们能够执行的操作之后两个,一个是选择了第i个物品的,一个是没有选择第i个物品,我们可以一个一个看,我们先看选择了第i个物品后,背包的重量到达了j,那么基于的上个阶段一定是arr[i-1][j-weight[i]] 即上一个阶段,背包重量为j-weight[i] ,同理,如果没有选择第i个物品,那么一定是基于arr[i-1][j] 这个状态推导过来。我们通过具体的决策操作知道了arr[i][j]依赖于哪些个之前的状态。然后我们就可以看看如何根据之前的这些状态推导出现在的状态,这个要具体问题具体分析,对于这个可行的问题,推导方程当然是 arr[i][j] = arr[i-1][j] || arr[i-1][j-weight[i]] 即只要上一个阶段中,背包重量为j可达,或者背包重量为 j-weight[i]可达,就可以推导出当前阶段,背包重量为j可达。

总结一下上面的过程,首先我们先通过决策树知道了可以利用动态规划来解决问题,然后我们根据决策之后,某些个属性的发生了变化,而且每个阶段这些属性都会变化,比如背包重量。我们就知道了我们选择哪个属性作为状态,确定好状态之后,我们需要列举出状态的所有的可能。状态定义完成之后,我们可以根据具体的操作逆推当前的状态和之前哪些个状态相关,并根据题意构造出推导方程,这个推导方程就是状态转移方程。状态转移方程有一个很明显的特点,那就是一定是基于之前的状态(准确的说应该是已有的状态)推导过来的。

到这里呢,思路我们就梳理完毕了,那么就可以进入最后一步,写代码。

public class Code01_Solve_Knapsack {

    /**
     * 定义状态 dp[i][j] :表示在第i个物品决策之后,当前背包的重量为j这个状态是否可达
     * 状态转移方程 dp[i][j] = dp[i-1][j] || dp[i-1][j-weights[i]]
     *
     * @param w
     * @param n
     * @param weights
     * @return
     */
    public boolean solveKnapsack(int w, int n, int[] weights) {
        // 定义状态
        boolean[][] dp = new boolean[n][w + 1];

        // 初始化 DONE 因为boolean值默认就是false,自动完成

        // 设置初项
        dp[0][0] = true;
        if (weights[0] <= w) {
            dp[0][weights[0]] = true;
        }

        // 填表 根据状态转移方程
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= w; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - weights[i] >= 0) {
                    dp[i][j] = dp[i][j] || dp[i - 1][j - weights[i]];
                }
            }
        }

        // 返回结果
        return dp[n - 1][w];
    }
}

可以看到从思路到代码还是有点不一样的,有一定的改变,不过整体的思路总是一样的。当然目前这段代码的空间复杂度还是比较高的,后面会讲解动态规划的空间优化来优化这段代码的空间复杂度。

从这段代码中我们可以总结出一个代码模板

public int dpSolve(args) {
    // 定义状态
    // 初始化
    // 设置初项
    // 填表 根据状态转移方程
    // 返回结果
}

到此呢,背包模型就讲完了,但是背包模型还有一些变种,比如完全背包问题,有限背包问题。我讲的这个是0-1背包问题,分析思路和具体的解题套路都没有变,唯一的区别就是使用的物品个数有一定的改变,比如完全背包问题,就是使用的物品个数不限,有限背包问题就是使用的物品个数有限,0-1背包问题就是使用的物品个数只有一个。

当你理解完这道题之后,就代表你成功入门了动态规划,而且也知道了背包模型,接下来就是练习有关背包模型的相关题目了。具体如下:

  1. 分割等和子集

  2. 目标和

  3. 零钱兑换

  4. 零钱兑换 II

路径模型真的很简单?

接下来我们来介绍第二个DP模型:路径模型,同样的我们先看一道题,从题目中总结模型的特点。

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

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

示例 1:

在这里插入图片描述

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

按照之前的套路,我们得先确认这道题可以利用回溯来解决,然后我们再来思考选择那个属性作为状态,然后再确定状态转移方程。同样的,我们一步一步来

第一步:这道题可以利用回溯来解决吗?答案肯定显而易见,毕竟这是一篇讲解动态规划的文章😂,但是应该如何分析呢?回溯的关键是构建多阶段决策树,在这道题就是向下走还是向右走,走完了之后,下一个阶段的决策需要依赖当前阶段决策的状态,最后到达终点后,统计结果。根据题目的意思,你会发现一个现象,无论怎么走,都会距离终点近一步。而总共只需要 m+n-1 步就可以到达终点。所以我们可以分为m+n-1个阶段,然后利用回溯求解。

第二步:我们知道这道题可以利用回溯来解决问题,那么接下来我们需要选择一个属性作为状态,我说过一般而言是选择进行决策之后的属性作为状态,当我们做完向左走还是向右走后,我们当前的状态为在grid中的某个位置(x,y) 然后思考我们能不能知道这个属性在全部阶段的所有的可能。很明显我们知道,不就是grid中的每个格子吗?于是我们的状态就出来了,dp[i][j] 表示当处于grid(i,j)位置时的最小代价值

第三步:推导状态转移方程,这个状态转移方程很容易推导,要么是从上面来(向下走),要么是从左边来(向右走)所以状态转移方程为:dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j]

当你梳理完成这三步之后,那么就可以写代码了。参考代码如下

class Solution {
    public int minPathSum(int[][] grid) {
        // 定义状态
        int row = grid.length;
        int col = grid[0].length;
        int[][] dp = new int[row][col];

        // 初始化 DONE 

        // 设置初项
        dp[0][0] = grid[0][0];
        for (int j = 1; j< col;j++) {
            dp[0][j] = dp[0][j-1] + grid[0][j];  
        }

        for (int i = 1; i< row;i++) {
            dp[i][0] = dp[i-1][0] + grid[i][0];
        }

        // 填表
        for (int i = 1; i < row;i++) {
            for (int j = 1; j < col;j++) {
                dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
            }
        }

        // 返回结果
        return dp[row-1][col-1];
    }
}

现在我们来对比路径模型和背包模型,背包模型很明显就是一个阶段一群状态(背包的重量),而且所有可能的状态在每个阶段是一样的。而路径模型没有那么一板一眼,而是知道了全部阶段所有可能的状态

知道了路径模型,接下来就是开练,多多练习,从练习中寻找感觉,也是一种学习之道。

  1. LCR 166. 珠宝的最高价值
  2. 120. 三角形最小路径和
  3. 62. 不同路径
  4. 63. 不同路径 II

别具一格的打家劫舍&股票模型?

接下来要介绍的模型和前面的两种模型有着不小的差别,我们需要细细体会其中的不同。废话不多说,上题!

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

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

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

根据题意,我们很容易就分析出了这是一道最值问题,然后就可以思考是否可以利用回溯解决,进一步思考是否可以利用动态规划解决。这里我们肯定是知道了这道题可以利用动态规划来解决,接下来用我提供的三步走思考模板来思考。

第一步:**判断是否可以利用回溯解决问题?**再一次强调,回溯的核心:构建多阶段决策模型。于是我们可以按照这样的思路来解决问题,我们可以按照是否偷来分为阶段,有多少个房子就分为多少个阶段,根据题目的条件限制,我们可以画出这样的多阶段决策树

在这里插入图片描述

你可以倒回去看看,这棵决策树和背包模型那棵决策树有什么不同?即上一步的决策影响了下一步的决策选择列表。说人话就是上一步的决策会导致下一步的某些决策不能做

第二步:**选择哪个属性作为状态?**通常而言是选择决策之后的某个属性作为状态,但是这道题由于每个阶段的可选择的决策不一样,即有些阶段只能选择偷,而有些阶段既可以选择偷也可以选择不偷。所以当我们列举出全部状态的可能的时候,就不得不分类讨论了。即做出偷决策的所有可能的状态,做出不偷决策的所有可能的状态。在这里我们选择在决策一个房间之后,小偷口袋中的钱作为状态。因为不同的决策有不同影响,所有我们需要分类讨论即dp[i][0] 表示在第i个房间选择不偷的时候,小偷口袋中最大的金额,dp[i][1]表示在第i个房间选择偷的时候,小偷口袋中最大的金额。

第三步:推导状态转移方程!由于状态被分类了,所以状态转移方程也会被分类。即 dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]) 另一个是 dp[i][1] = dp[i-1][0] + nums[i] 最后的结果是:Math.max(dp[n-1][0],dp[n-1][1])

当你分析完三步状态之后,就可以按照这三步中的思路给出对应的代码了。参考代码如下

class Solution {
    public int rob(int[] nums) {
        // 定义状态
        int n = nums.length;
        int[][] dp = new int[n][2];
        // 初始化 DONE

        // 设置初项
        dp[0][0] = 0;
        dp[0][1] = nums[0];

        // 填表
        for (int i = 1; i< n;i++) {
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i];
        }

        // 返回结果
        return Math.max(dp[n-1][0],dp[n-1][1]);
    }
}

好了现在到了对比时刻,打家劫舍模型&股票模型与背包模型最大的不同就在于决策树的不同,即当前阶段决策之后的状态影响了下一个阶段的可以决策的操作列表。所以需要分类讨论。

题目列表:

  1. 213. 打家劫舍 II
  2. 337. 打家劫舍 III (选做)
  3. 714. 买卖股票的最佳时机含手续费
  4. 309. 买卖股票的最佳时机含冷冻期

暂时完结…

到了这里我们已经搞定了三个模型,一定有6个模型,我们完成了一半,但是爬楼梯模型很简单,还需要详细聊聊的就是匹配模型,但是如果你有这三个模型的基础,可以尝试自己做做那些相关的题目。我可以骄傲的说,如果你搞定了这三个模型以及通用的解决思路,你能独立完成80%的题目了,以及看懂100%的题解。这对于这篇文章而言就够了,之后后面的三个模型,我会在后面补上的。

题目列表:

  • 爬楼梯模型
    1. 70. 爬楼梯
    2. 322. 零钱兑换
    3. LCR 131. 砍竹子 I
    4. LCR 165. 解密数字
    5. 139. 单词拆分
  • 匹配模型 (较难)
    1. 1143. 最长公共子序列
    2. 72. 编辑距离
  • 其他模型 (有难度)
    1. 300. 最长递增子序列
    2. 437. 路径总和 III

温馨提示:针对DP新手而言,后面两个模型没有必要深入学习,可以在熟练前三个模型之后再去研究,毕竟动态规划日常开发用的不多,主要还是面试,笔试中。正如八二定律一样,掌握20%的核心内容就好

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

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

相关文章

前端性能优化全面指南

前端性能优化是提升用户体验的关键&#xff0c;页面加载速度、响应时间和交互流畅度直接影响用户的留存率和满意度。以下是常用的前端性能优化方法&#xff0c;从网络层、资源加载、JavaScript 执行、渲染性能等方面进行全方位优化。 减少 HTTP 请求 合并文件&#xff1a;将多…

markdown里粘贴图片的同时保存路径 在vscode里实现

下载扩展Markdown Image 设置保存路径 参考链接 https://blog.cxplay.org/works/vscode-to-markdown-editor/#markdown-image

HDLBits中文版,标准参考答案 | 3.2.5 Finite State Machines | 有限状态机(2)

关注 望森FPGA 查看更多FPGA资讯 这是望森的第 17 期分享 作者 | 望森 来源 | 望森FPGA 目录 1 Lemmings 1 2 Lemmings 2 3 Lemmings 3 4 Lemmings 4 5 One-hot FSM | 独热 FSM 6 PS/2 packet parser | PS/2 数据包解析器 7 PS/2 packet parser anddatapath | PS/2 数…

55 WebSocket

55 WebSocket 参考资料 WebSocket SpringBoot使用WebSocket SpringBoot 集成WebSocket详解 前言 WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和…

AI大模型微调产品经理面试必备全攻略,非常详细收藏我这一篇就够了

前言 这两天跟很多做程序员的朋友聊天&#xff0c;怎么看全网火爆的大模型。让我挺意外的是&#xff0c;大家的反馈普遍都很焦虑 。 在AI大模型微调领域的产品经理面试中&#xff0c;总会遇到一系列与技术细节、项目经验、市场趋势以及职业规划相关的问题。以下是一些建议的面…

RabbitMQ中如何解决消息堆积问题,如何保证消息有序性

RabbitMQ中如何解决消息堆积问题 如何保证消息有序性 只需要让一个消息队列只对应一个消费者即可

cdr激活码序列号coredraw2024安装包破解版coreldraw2024永久序列号最新

&#x1f31f;设计界的新宠儿来啦&#xff01;CorelDRAW 2024震撼登场&#x1f389; ### &#x1f308; 开篇狂想曲&#xff1a;设计师们的“瑞士军刀”升级记&#xff01;&#x1f3a8; 嘿&#xff0c;亲爱的创意达人们&#x1f44b;&#xff01;今天&#xff0c;就让我们共同…

6.将扩散模型与其他生成模型的关联(1)

在本章中&#xff0c;我们首先介绍其他5种重要的生成模型&#xff0c;包括变分自编码器抗网络、归一化流、自回归模型和基于能量的模型&#xff0c;分析它们的优点和局限性&#xff0c;并说明这些生成模型是如何通过纳入扩散模型而得到促进的。1 .变分自编码器与扩散模型 …

Java | Leetcode Java题解之第455题分发饼干

题目&#xff1a; 题解&#xff1a; class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int m g.length, n s.length;int count 0;for (int i 0, j 0; i < m && j < n; i, j) {while (j < n &&…

鸿蒙开发之ArkUI 界面篇 二十五 点赞综合案例

要实现如下图效果&#xff0c;红框处是点赞数&#xff0c;点击会变色和数字增加1&#xff1a; 我们首先分析布局结构&#xff0c;整体式垂直方向的布局&#xff0c;外层容器自然是Colum&#xff0c;上层是图片组件Image&#xff0c;接下来是Text组件&#xff0c;接下来是Row组件…

Unity实战案例全解析 类宝可梦回合制的初级案例 源码分析(加了注释和流程图)

这是一个老教程了&#xff0c;但是对于没有写过回合制的初级程序同学来讲是比较适合的&#xff0c;也可以直接看源码&#xff0c;半小时内可以解决战斗 当然&#xff0c;我也没写过回合制系统所以就到处找&#xff0c;思路明白了就能自己修改了 视频教程 - 油管链接 Turn-Bas…

ComfyUI | 5分钟部署最新Flux大模型

Midjourney 和 Stable Diffusion 都是目前流行的 AI 图像生成工具&#xff0c;它们能够根据文本描述生成高质量的图像。都是基于深度学习技术的文本到图像生成模型&#xff0c;但它们各自基于不同的大模型。 但最近推出了一款比前两者更强大&#xff0c;生成图像更加逼真&…

UE4 材质学习笔记04(着色器性能优化)

一.着色器性能优化 1.衡量着色器的性能 衡量着色器性能的主要方法有三个 第一个&#xff1a;可以使用场景的视图模式的优化视图模式的着色器复杂度 下面的滑条代表了着色器指令的复杂度 如果场景大部分是绿色的&#xff0c;说明着色器耗能低&#xff0c;反之白色则是很糟糕…

VS Code安装以及配置

安装 1. 下载安装 VScode官网 注意&#xff0c;这一步最好全部打勾 2. 设置默认terminal为cmd 3. 修改Run Code的配置 参考&#xff1a; https://blog.csdn.net/weixin_46474921/article/details/132841711

2024年诺贝尔物理学奖 机器学习与神经网络领域前景面面观 如何抉择

近日&#xff0c;2024年诺贝尔物理学奖颁发给了机器学习与神经网络领域的研究者&#xff0c;这是历史上首次出现这样的情况。这项奖项原本只授予对自然现象和物质的物理学研究作出重大贡献的科学家&#xff0c;如今却将全球范围内对机器学习和神经网络的研究和开发作为了一种能…

【element-tiptap】如何增加一个扩展项,为文字渲染颜色?

源码地址&#xff1a; https://github.com/Leecason/element-tiptap 可以看到&#xff0c;当前这个页面的文字&#xff0c;都是黑色的&#xff08;除了链接&#xff09; 酱紫有些单调&#xff0c;我喜欢五颜六色的。那么这篇文章就来看下菜单项里面如何增加一个颜色的扩展&…

Anaconda保姆安装教程

步骤1&#xff1a;下载Anaconda安装包 访问官网&#xff1a; 进入Anaconda官网下载页面&#xff0c;官网会根据电脑的操作系统自动选择适合的操作系统安装程序。 尝试进入清华大学开源软件镜像站&#xff0c;选择想要的版本进行下载&#xff0c;通常下载速度较快。 本文以从…

OpenAI重磅发布交互界面canvas,让ChatGPT成为写作和编程利器

OpenAI 宣布推出类似 Anthropic 的 Artifacts 的应用 canvas&#xff0c;并称「这是一种使用 ChatGPT 写作和编程的新方式」。 在 Claude 中试过 Artifacts 的朋友都知道&#xff0c;这能极大提升 LLM 输出结果的表现力&#xff0c;其支持输出文本文件、代码、网页、SVG 等等。…

Windows11 24H2 64位专业精简版:告别卡顿,流畅运行!

今日&#xff0c;系统之家小编给您分享2024年最新发布的Windows11 24H2精简版系统下载&#xff0c;该版本系统采用微软官方Windows11 24H2 26100.2033 专业版离线制作&#xff0c;安全无毒&#xff0c;不符合硬件要求的电脑也能升级。本次更新修复了系统蓝屏、绿屏的安全问题&a…

【赵渝强老师】K8s中的有状态控制器StatefulSet

在K8s中&#xff0c;StatefulSets将Pod部署成有状态的应用程序。通过使用StatefulSets控制器&#xff0c;可以为Pod提供持久存储和持久的唯一性标识符。StatefulSets控制器与Deployment控制器不同的是&#xff0c;StatefulSets控制器为管理的Pod维护了一个有粘性的标识符。无论…