动态规划-第2篇

前言:在上一篇文章中,我们了解了动态规划的基本概念和解决问题的基本思路。通过分解问题、存储子问题的解,动态规划为我们提供了高效的解决方案。然而,动态规划并不是一成不变的,它有很多不同的技巧和变种,能够应对各类复杂问题。

在本篇文章中,我们将深入探讨一些常见的动态规划问题及其解法,学习如何巧妙地设计状态转移方程,优化空间复杂度,并进一步掌握动态规划的核心思想。通过具体实例,你将能够更好地理解如何在实际开发中运用动态规划来解决复杂问题。🌼🌼

 7. 礼物的最⼤价值(medium)
 1. 题⽬链接:LCR 166. 珠宝的最高价值
2.解法(动态规划):

算法思路:

 1. 状态表⽰:

对于这种「路径类」的问题,我们的状态表⽰⼀般有两种形式:

i. 从 [i, j] 位置出发,巴拉巴拉;

ii. 从起始位置出发,到达 [i, j] 位置,巴拉巴拉。

这⾥选择第⼆种定义状态表⽰的⽅式:

dp[i][j] 表⽰:⾛到 [i, j] 位置处,此时的最⼤价值。

2. 状态转移⽅程:

对于 dp[i][j] ,我们发现想要到达 [i, j] 位置,有两种⽅式:

i. 从 [i, j] 位置的上⽅ [i - 1, j] 位置,向下⾛⼀步,此时到达 [i, j] 位置能

拿到的礼物价值为 dp[i - 1][j] + grid[i][j] ;

ii. 从 [i, j] 位置的左边 [i, j - 1] 位置,向右⾛⼀步,此时到达 [i, j] 位置能拿到的礼物价值为 dp[i][j -1] + grid[i][j]

我们要的是最⼤值,因此状态转移⽅程为:

dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j] 。

3. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

i. 辅助结点⾥⾯的值要「保证后续填表是正确的」;

ii. 「下标的映射关系」。

在本题中,「添加⼀⾏」,并且「添加⼀列」后,所有的值都为 0 即可。

4. 填表顺序:

根据「状态转移⽅程」,填表的顺序是「从上往下填写每⼀⾏」,「每⼀⾏从左往右」。

5. 返回值:

根据「状态表⽰」,我们应该返回 dp[m][n] 的值。

3.C++ 算法代码:
class Solution {
public:int jewelleryValue(vector<vector<int>>& vv) {int m=vv.size();int n=vv[0].size();vector<vector<int>>dp(m+1,vector<int>(n+1));dp[0][0]=0;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=max(dp[i-1][j],dp[i][j-1])+vv[i-1][j-1];}}return dp[m][n];}
};
8.下降路径最⼩和(medium)
 1. 题⽬链接:931. 下降路径最小和 - 力扣(LeetCode)
2. 解法(动态规划):

算法思路:

关于这⼀类题,由于我们做过类似的,因此「状态表⽰」以及「状态转移」是⽐较容易分析出来的。

⽐较难的地⽅可能就是对于「边界条件」的处理。

1. 状态表⽰:

对于这种「路径类」的问题,我们的状态表⽰⼀般有两种形式:

i. 从 [i, j] 位置出发,到达⽬标位置有多少种⽅式;

ii. 从起始位置出发,到达 [i, j] 位置,⼀共有多少种⽅式

这⾥选择第⼆种定义状态表⽰的⽅式:

dp[i][j] 表⽰:到达 [i, j] 位置时,所有下降路径中的最⼩和。

2. 状态转移⽅程:

对于普遍位置 [i, j] ,根据题意得,到达 [i, j] 位置可能有三种情况: i. 从正上⽅ [i - 1, j] 位置转移到 [i, j] 位置;

ii. 从左上⽅ [i - 1, j - 1] 位置转移到 [i, j] 位置;

iii. 从右上⽅ [i - 1, j + 1] 位置转移到 [i, j] 位置;

我们要的是三种情况下的「最⼩值」,然后再加上矩阵在 [i, j] 位置的值。

于是 dp[i][j] = min(dp[i - 1][j], min(dp[i - 1][j - 1], dp[i - 1][j + 1])) + matrix[i][j]

3. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

i. 辅助结点⾥⾯的值要「保证后续填表是正确的」;

ii. 「下标的映射关系」。

在本题中,需要「加上⼀⾏」,并且「加上两列」。所有的位置都初始化为⽆穷⼤,然后将第⼀⾏

初始化为 0 即可。

4. 填表顺序:

根据「状态表⽰」,填表的顺序是「从上往下」。

5. 返回值:

注意这⾥不是返回 dp[m][n] 的值!

题⽬要求「只要到达最后⼀⾏」就⾏了,因此这⾥应该返回「 dp 表中最后⼀⾏的最⼩值」

3.C++ 算法代码

class Solution
{
public:
int minFallingPathSum(vector<vector<int>>& matrix)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回结果
int n = matrix.size();
vector<vector<int>> dp(n + 1, vector<int>(n + 2, INT_MAX));
// 初始化第⼀⾏
for(int j = 0; j < n + 2; j++) dp[0][j] = 0;for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i - 1][j
+ 1])) + matrix[i - 1][j - 1];
int ret = INT_MAX;
for(int j = 1; j <= n; j++)
ret = min(ret, dp[n][j]);
return ret;
}
}
9. 最⼩路径和(medium)
1.题目链接:64. 最小路径和 - 力扣(LeetCode) 
2.  解法(动态规划):

算法思路:

像这种表格形式的动态规划,是⾮常容易得到「状态表⽰」以及「状态转移⽅程」的,可以归结到

「不同路径」⼀类的题⾥⾯。

1. 状态表⽰:

对于这种路径类的问题,我们的状态表⽰⼀般有两种形式:

i. 从 [i, j] 位置出发,巴拉巴拉;

ii. 从起始位置出发,到达 [i, j] 位置,巴拉巴拉。

这⾥选择第⼆种定义状态表⽰的⽅式:

dp[i][j] 表⽰:到达 [i, j] 位置处,最⼩路径和是多少。

2. 状态转移:

简单分析⼀下。如果 dp[i][j] 表⽰到达 到达 [i, j] 位置处的最⼩路径和,那么到达 [i, j] 位置之前的⼀⼩步,有两种情况:

i. 从 [i - 1, j] 向下⾛⼀步,转移到 [i, j] 位置;

ii. 从 [i, j - 1] 向右⾛⼀步,转移到 [i, j] 位置。

由于到 [i, j] 位置两种情况,并且我们要找的是最⼩路径,因此只需要这两种情况下的最⼩值,再加上 [i, j] 位置上本⾝的值即可。

也就是: dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]

3. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

i. 辅助结点⾥⾯的值要「保证后续填表是正确的」;

ii. 「下标的映射关系」。

在本题中,「添加⼀⾏」,并且「添加⼀列」后,所有位置的值可以初始化为⽆穷⼤,然后让 dp[0][1] = dp[1][0] = 1 即可。

4. 填表顺序:

根据「状态转移⽅程」的推导来看,填表的顺序就是「从上往下」填每⼀⾏,每⼀⾏「从左往

后」。

5. 返回值:

根据「状态表⽰」,我们要返回的结果是 dp[m][n] 。

3.C++ 算法代码:
class Solution
{
public:
int minPathSum(vector<vector<int>>& grid)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回结果
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
dp[0][1] = dp[1][0] = 0;
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j -
1];
return dp[m][n];
}
}
10. 地下城游戏(hard)
 1. 题⽬链接:174. 地下城游戏 - 力扣(LeetCode)
2. 解法(动态规划):

算法思路:

1. 状态表⽰:

这道题如果我们定义成:从起点开始,到达 [i, j] 位置的时候,所需的最低初始健康点数。那么我们分析状态转移的时候会有⼀个问题:那就是我们当前的健康点数还会受到后⾯的路径的影响。也就是从上往下的状态转移不能很好地解决问题。

这个时候我们要换⼀种状态表⽰:从 [i, j] 位置出发,到达终点时所需要的最低初始健康点

数。这样我们在分析状态转移的时候,后续的最佳状态就已经知晓。

综上所述,定义状态表⽰为:

dp[i][j] 表⽰:从 [i, j] 位置出发,到达终点时所需的最低初始健康点数。

2. 状态转移⽅程:

对于 dp[i][j] ,从 [i, j] 位置出发,下⼀步会有两种选择(为了⽅便理解,设 dp[i] [j] 的最终答案是 x):

i. ⾛到右边,然后⾛向终点

那么我们在 [i, j] 位置的最低健康点数加上这⼀个位置的消耗,应该要⼤于等于右边位置的最低健康点数,也就是: x + dungeon[i][j] >= dp[i][j + 1] 。

通过移项可得: x >= dp[i][j + 1] - dungeon[i][j] 。因为我们要的是最⼩值,因此这种情况下的 x = dp[i][j + 1] - dungeon[i][j] ;

ii. ⾛到下边,然后⾛向终点

那么我们在 [i, j] 位置的最低健康点数加上这⼀个位置的消耗,应该要⼤于等于下边位置的最低健康点数,也就是: x + dungeon[i][j] >= dp[i + 1][j] 。

通过移项可得: x >= dp[i + 1][j] - dungeon[i][j] 。因为我们要的是最⼩值,因此这种情况下的 x = dp[i + 1][j] - dungeon[i][j] ;

综上所述,我们需要的是两种情况下的最⼩值,因此可得状态转移⽅程为:

dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]

但是,如果当前位置的 dungeon[i][j] 是⼀个⽐较⼤的正数的话, dp[i][j] 的值可能变

成 0 或者负数。也就是最低点数会⼩于 1 ,那么骑⼠就会死亡。因此我们求出来的

如果⼩于等于 0 的话,说明此时的最低初始值应该为 1 。处理这种情况仅需让  dp[i] [j]  与  1  取⼀个最⼤值即可: 

dp[i][j] = max(1, dp[i][j])

3. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

i. 辅助结点⾥⾯的值要「保证后续填表是正确的」;

ii. 「下标的映射关系」。

在本题中,在 dp 表最后⾯添加⼀⾏,并且添加⼀列后,所有的值都先初始化为⽆穷⼤,然后让 dp[m][n - 1] = dp[m - 1][n] = 1 即可。

4. 填表顺序:

根据「状态转移⽅程」,我们需要「从下往上填每⼀⾏」,「每⼀⾏从右往左」

5. 返回值:

根据「状态表⽰」,我们需要返回 dp[0][0] 的值。

3.C++ 算法代码:
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>>& dungeon)
{
int m = dungeon.size(), n = dungeon[0].size();
// 建表 + 初始化
vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
dp[m][n - 1] = dp[m - 1][n] = 1;
// 填表
for(int i = m - 1; i >= 0; i--)
for(int j = n - 1; j >= 0; j--)
{
dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j];
dp[i][j] = max(1, dp[i][j]);
}
// 返回结果
return dp[0][0];
}
}

简单多状态 dp 问题

11. 按摩师(easy)

打家劫舍问题的变形~ ⼩偷变成了按摩师

1. 题⽬链接:面试题 17.16. 按摩师
 2. 解法(动态规划):

算法思路:

1. 状态表⽰:

对于简单的线性 dp ,我们可以⽤「经验 + 题⽬要求」来定义状态表⽰:

i. 以某个位置为结尾,巴拉巴拉;

ii. 以某个位置为起点,巴拉巴拉。

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:

dp[i] 表⽰:选择到 i 位置时,此时的最⻓预约时⻓。

但是我们这个题在 i 位置的时候,会⾯临「选择」或者「不选择」两种抉择,所依赖的状态需要

细分:

▪ f[i] 表⽰:选择到 i 位置时, nums[i] 必选,此时的最⻓预约时⻓;

▪ g[i] 表⽰:选择到 i 位置时, nums[i] 不选,此时的最⻓预约时⻓。

2. 状态转移⽅程:

因为状态表⽰定义了两个,因此我们的状态转移⽅程也要分析两个:

对于 f[i] :

▪ 如果 nums[i] 必选,那么我们仅需知道 i - 1 位置在不选的情况下的最⻓预约时⻓,

然后加上 nums[i] 即可,因此 f[i] = g[i - 1] + nums[i] 。

对于 g[i] :

▪ 如果 nums[i] 不选,那么 i - 1 位置上选或者不选都可以。因此,我们需要知道 i置上选或者不选两种情况下的最⻓时⻓,因此 g[i] = max(f[i - 1], g[i])

3. 初始化:

这道题的初始化⽐较简单,因此⽆需加辅助节点,仅需初始化 f[0] = nums[0], g[0] = 0即可

4. 填表顺序

根据「状态转移⽅程」得「从左往右,两个表⼀起填」。

5. 返回值

根据「状态表⽰」,应该返回 max(f[n - 1], g[n - 1]) 。

3.C++ 算法代码:
class Solution {
public:
int massage(vector<int>& nums) {
// 1. 创建⼀个 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回值
int n = nums.size();
if(n == 0) return 0; // 处理边界条件
vector<int> f(n);
auto g = f;
f[0] = nums[0];
for(int i = 1; i < n; i++)
{
f[i] = g[i - 1] + nums[i];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[n - 1], g[n - 1]);
}
};
12. 打家劫舍II (medium)
1.题目链接: 213. 打家劫舍 II - 力扣(LeetCode)
2. 解法(动态规划)

算法思路:

这⼀个问题是「打家劫舍I」问题的变形。

上⼀个问题是⼀个「单排」的模式,这⼀个问题是⼀个「环形」的模式,也就是⾸尾是相连的。但

是我们可以将「环形」问题转化为「两个单排」问题:

a. 偷第⼀个房屋时的最⼤⾦额 x ,此时不能偷最后⼀个房⼦,因此就是偷 [0, n - 2] 区间的房⼦;

b. 不偷第⼀个房屋时的最⼤⾦额 y ,此时可以偷最后⼀个房⼦,因此就是偷 [1, n - 1] 区间的房⼦;

两种情况下的「最⼤值」,就是最终的结果。

因此,问题就转化成求「两次单排结果的最⼤值」

3. C++算法代码:
class Solution {
public:int rob(vector<int>& nums) {int n=nums.size();int f1=test(nums,2,n-2)+nums[0];//第一个偷的话int g1=test(nums,1,n-1);//第一个不偷的话return max(f1,g1);}int test(vector<int>& nums,int left,int right)//与按摩问题一样{if(left>right)return 0;int n=nums.size();vector<int> f(n);f[left]=nums[left];auto g=f;g[left]=0;for(int i=left+1;i<=right;i++){f[i]=g[i-1]+nums[i];g[i]=max(f[i-1],g[i-1]);}return max(f[right],g[right]);}
};

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

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

相关文章

基于Redis实现限流

限流尽可能在满足需求的情况下越简单越好&#xff01; 1、基于Redsi的increment方法实现固定窗口限流 Redis的increment方法保证并发线程安全窗口尽可能越小越好(太大可能某一小段时间就打满请求剩下的都拿不到令牌了)这个原理其实就是用当前时间戳然后除窗口大小 在这个窗口大…

【工具使用】IDEA 社区版如何创建 Spring Boot 项目(详细教程)

IDEA 社区版如何创建 Spring Boot 项目&#xff08;详细教程&#xff09; Spring Boot 以其简洁、高效的特性&#xff0c;成为 Java 开发的主流框架之一。虽然 IntelliJ IDEA 专业版提供了Spring Boot 项目向导&#xff0c;但 社区版&#xff08;Community Edition&#xff09…

探索高性能AI识别和边缘计算 | NVIDIA Jetson Orin Nano 8GB 开发套件的全面测评

随着边缘计算和人工智能技术的迅速发展&#xff0c;性能强大的嵌入式AI开发板成为开发者和企业关注的焦点。NVIDIA近期推出的Jetson Orin Nano 8GB开发套件&#xff0c;凭借其40 TOPS算力、高效的Ampere架构GPU以及出色的边缘AI能力&#xff0c;引起了广泛关注。本文将从配置性…

紧急救援!MySQL数据库误删后的3种恢复方案

一、误删场景分类与恢复策略 ‌常见误操作场景‌: DROP TABLE 误删单表(高频事故)DELETE 误删数据(可通过事务回滚抢救)DROP DATABASE 删除整个库(需全量备份)服务器rm -rf(物理文件删除)‌恢复方案选择矩阵‌: 场景推荐方案时间窗口表结构删除(DROP)备份恢复 + B…

开源免费日志服务ELK Syack代替syslog

一、ELK Stack 采集 syslog 日志的主要方式 通常&#xff0c;ELK Stack 使用 Logstash 或者 Filebeat 来采集 syslog 日志。 Beats 通常更轻量级&#xff0c;适合作为代理部署在各个日志源服务器上&#xff0c;而 Logstash 则功能更强大&#xff0c;可以进行更复杂的日志处理和…

单片机设计暖脚器研究

标题:单片机设计暖脚器研究 内容:1.摘要 本文聚焦于基于单片机设计暖脚器的研究。背景方面&#xff0c;在寒冷季节&#xff0c;暖脚器能有效改善脚部寒冷状况&#xff0c;提升人们的舒适度&#xff0c;但传统暖脚器存在功能单一、温控不准确等问题。目的是设计一款智能、高效且…

蓝桥杯省赛真题C++B组2024-握手问题

一、题目 【问题描述】 小蓝组织了一场算法交流会议&#xff0c;总共有 50 人参加了本次会议。在会议上&#xff0c;大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手(且仅有一次)。但有 7 个人&#xff0c;这 7 人彼此之间没有进行握手(但这…

C#+AForge 实现视频录制

C#AForge 实现视频录制 ​ 在C#中&#xff0c;使用AForge 库实现视频录制功能是一个比较直接的过程。AForge 是一个开源的.NET框架&#xff0c;提供了许多用于处理图像和视频的类库。 开发步骤 安装AForge库 ​ 首先&#xff0c;确保你的项目中已经安装了 AForge.Video和AFo…

PHP框架加载不上.env文件中的变量

以lumen5.5框架为例&#xff0c;根目录中bootstrap文件夹下的app.php文件中 (new Dotenv\Dotenv(__DIR__./../))->load(); 是读取所有.env中的文件的&#xff0c;这个是正常的&#xff0c;但是在代码中的任何位置或者在config目录下的databases.php里&#xff0c;代码如…

21.Linux 线程库的使用与封装

在linux内核中并没有线程的概念&#xff0c;只有轻量级进程LWP的概念&#xff0c;linux下的线程都是是由LWP进行模拟实现的。因此linux操作系统中不会提供线程的相关接口&#xff0c;只会提供轻量级线程的接口&#xff08;如vfork&#xff0c;clone等&#xff09;。但是在我们的…

Aliyun CTF 2025 web 复现

文章目录 ezoj打卡OKoffens1veFakejump server ezoj 进来一看是算法题&#xff0c;先做了试试看,gpt写了一个高效代码通过了 通过后没看见啥&#xff0c;根据页面底部提示去/source看到源代码&#xff0c;没啥思路&#xff0c;直接看wp吧&#xff0c;跟算法题没啥关系,关键是去…

《鸿蒙系统下AI模型训练加速:时间成本的深度剖析与优化策略》

在当今数字化浪潮中&#xff0c;鸿蒙系统凭借其独特的分布式架构与强大的生态潜力&#xff0c;为人工智能的发展注入了新的活力。随着AI应用在鸿蒙系统上的日益普及&#xff0c;如何有效降低模型训练的时间成本&#xff0c;成为了开发者与研究者们亟待攻克的关键课题。这不仅关…

Git使用(一)--如何在 Windows 上安装 Git:详细步骤指南

如果你想在 Windows 机器上安装 Git&#xff0c;可以按照以下详细指南进行操作。 第一步&#xff1a;下载 Git 可通过官网下载 适用于 Windows 的 Git 最新版本。 如果下载速度较慢&#xff0c;可以通过下面提供的百度网盘 链接下载安装包&#xff0c; https://git-scm.com/d…

基于Prometheus+Grafana的Deepseek性能监控实战

文章目录 1. 为什么需要专门的大模型监控?2. 技术栈组成2.1 vLLM(推理引擎层)2.2 Prometheus(监控采集层)2.3 Grafana(数据可视化平台)3. 监控系统架构4. 实施步骤4.1 启动DeepSeek-R1模型4.2 部署 Prometheus4.2.1 拉取镜像4.2.2 编写配置文件4.2.3 启动容器4.3 部署 G…

本地Git仓库搭建(DevStar)与Git基本命令

本地Git仓库搭建&#xff08;DevStar&#xff09;与Git基本命令 实验环境搭建平台Git基本命令的使用本地仓库的创建代码提交代码合并版本发布 总结 实验环境 搭建平台 按照DevStar的Github仓库要求&#xff0c;在终端中执行下列命令&#xff0c;即可成功安装DevStar到本地部署…

stm32 蓝桥杯 物联网 独立键盘的使用

在蓝桥杯物联网平台里面&#xff0c;有5个外接设备&#xff0c;其中有一个就是6个独立按键。首先&#xff0c;我们先看一下按键有关的电路图。 电路图与cubemx设定 由图可见&#xff0c;独立键盘组由两行三列构成&#xff0c;我们通过行列来锁定要访问的独立按键在哪。ROW1挂…

set_clock_groups

一、命令参数与工具处理逻辑 核心参数定义 参数定义工具行为工具兼容性-asynchronous完全异步时钟组&#xff0c;无任何相位或频率关系&#xff08;如独立晶振、不同时钟树&#xff09;工具完全禁用组间路径的时序分析&#xff0c;但需用户自行处理跨时钟域&#xff08;CDC&a…

工作记录 2017-01-06

工作记录 2017-01-06 序号 工作 相关人员 1 协助BPO进行Billing的工作。 修改CSV、EDI837的导入。 修改邮件上的问题。 更新RD服务器。 郝 修改的问题&#xff1a; 1、 In “Full Job Summary” (patient info.), sometime, the Visit->Facility is missed, then …

Adaptive AUTOSAR UCM模块——快速入门

Adaptive AUTOSAR中的UCM模块介绍 概述 Adaptive AUTOSAR(AUTomotive Open System ARchitecture)是一个开放的行业标准,旨在为现代汽车电子系统提供一个灵活且可扩展的软件框架。在这个框架中,更新与配置管理(Update and Configuration Management, UCM)模块扮演着至关…

解决跨域问题的6种方案

解决跨域问题&#xff08;Cross-Origin Resource Sharing, CORS&#xff09;是 Web 开发中常见的需求&#xff0c;以下是 6 种主流解决方案&#xff0c;涵盖前端、后端和服务器配置等不同层面&#xff1a; 一、CORS&#xff08;跨域资源共享&#xff09; 原理 通过服务器设置…