动态规划(DP)的原理、实现及应用

article/2025/9/29 9:59:22

文章目录

  • 1. 由一个例子说开: 斐波那契(fibonacci)数列
      • 性能测试
      • 原因分析
  • 2. 记忆化搜索
  • 3. 动态规划(Dynamic Programming,DP)
      • 最优子结构
      • 总结一下这几个解法:
      • 几个例题
        • LeetCode 70 Climbing Stairs
  • 4. 动态规划的核心:状态与状态转移方程
    • LeetCode 343 Integer Break
    • LeetCode 198 House Robber
        • 暴力解法
        • 动态规划
        • LeetCode上与此题相似的题目:
  • 5. 动态规划经典应用:背包问题
      • 三种解法
      • 背包问题动态规划解法的实现及优化
      • 背包问题的变种
      • 背包问题实例
        • LeetCode上与此题相似的题目:
  • 6. 动态规划应用: 最长上升子序列(LIS)
      • 300 Longest Increasing Subsequence
      • 最长公共子序列: LCS问题
  • 7. 关于动态规划的其他:
  • 8. 如何用动态规划给出具体的解:返回去找

 

1. 由一个例子说开: 斐波那契(fibonacci)数列

斐波那契数列是由0和1开始,之后的数就是前两个数的和。
首几个费波那契系数是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……
那我们如何用计算机来生成这些数呢,也就是说,求斐波那契数列第n位的值。
很简单,用递归就可以了:

# 自上而下地递归
def fib(n):if n in [0,1]:return nreturn fib(n-1) + fib(n-2)print(fib(10))

输出: 55
  • 1

结果没有问题,接下来看一下递归性能如何。

性能测试

写个简单的计时的测试代码:

import time
n = 40
tic = time.clock()
res = fib(n)
toc = time.clock()
print('fib({}) = {}'.format(n,res))
print('runtime: {}s '.format(toc-tic))

我们看下n=10的结果:

fib(10) = 55
runtime: 2.4462208330078283e-05s 

再看下 n=40 时的结果:

fib(40) = 102334155
runtime: 40.73219172568648s 

我们发现:当n变大时,这段递归代码用时变得很久,而且性能是急剧下降。这是为什么呢?

原因分析

拿fib(5)来举例,要计算fib(5),得知道fib(4)的值,要计算fib(4)的值,又得知道fib(3)的值,以此类推,直到递归到底时,fib(0)=0。下图是个fib(5)求解的展开图,可以看到,这里面有很过的重复计算,比如浅粉色框框住的fib(2)就重复计算了三次,可想而知当n很大时,这些重复计算的次数将呈指数上升,这就是递归效率不高的原因——重叠子问题
fib(5)示意图


2. 记忆化搜索

改进方法就是,将计算过的fib()值存储下来,下次用到直接查就行,这种方法叫记忆化搜索
于是在递归代码的基础上可以很轻松地改写成记忆化搜索,代码如下:

# 记忆化搜索:自上而下地求解
def fib_memo(n):def fib(n):if n in [0,1]:return n# 如果memo数组中没有计算过fib(n),则需计算一下并存到memo[n]中if memo[n] == -1:memo[n] = fib(n-1) + fib(n-2)return memo[n]memo = [-1 for i in range(n+1)]  # memo[0...n]中所有元素初始值为-1,表示未计算return fib(n)

 

fib(40)的测试结果:

fib(40) = 102334155
runtime: 2.4746652798057767e-05s 

经过简单的记忆化改造,效率由 40.7s 提升到了 0.0000247s !
记忆化搜索本质还是一种递归,只是用额外的变量来存储中间值而已,属于典型的空间换时间的方式。由于递归是一种自顶向下的方式,所以记忆化搜索也是一种自顶而下的搜索方法,即计算顺序是

   fib(n) → fib(n-1) → fib(n-2) → ... → fib(1) → fib(0)

最后再从fib(0)一路回溯给fib(n),done!

那么可不可以自底向上地算出斐波那契数列呢?当然有啊,这就是接下来要隆重请出的动态规划


3. 动态规划(Dynamic Programming,DP)

思路很简单,知道了 fib(0) 和 fib(1),就可以相加得fib(2),fib(1)+fib(2)又得fib(3),依次类推直到 fib(n)。自底向上的求解过程:

   fib(0) → fib(1) → fib(2) → ... → fib(n-1) → fib(n)

没错,这也极其符合人的直观感觉,看着很舒服很顺眼。
于是很容易写出代码:

# 动态规划:自下而上地解决问题
def fib(n):memo = [-1 for i in range(n+1)]memo[0] = 0memo[1] = 1for i in range(2,n+1):memo[i] = memo[i-1] + memo[i-2]return memo[n]

看下测试结果:

fib(40) = 102334155
runtime: 1.6782212696853094e-05s 

0.0000167s 比记忆化搜索的 0.0000247s 又提高了一丢丢,这主要是因为记忆化搜索中多出了反复递归的开销。

最优子结构

其实fib数列不是重点,重点是这个:这里面fib(2)是fib(3)的子问题,fib(3)又是fib(4)的子问题,以此类推,所以这是一个通过子问题推得原问题的过程。我们可以把上面的过程总结为这句话:通过求子问题的最优解,可以获得原问题的最优解。
这里的子问题称为最优子结构
好,接下类引出动态规划的定义:

将原问题拆解成若干个子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。

好,介绍完毕。

总结一下这几个解法:

在递归问题中如果存在重叠子问题,那么就可以进行以下两种改造:

  1. 自顶而下地解决问题:记忆化搜索
  2. 自底而上地解决问题:动态规划
    动态规划

几个例题

LeetCode 70 Climbing Stairs

LeetCode 70. 爬楼梯
解析:要爬到n阶台阶,有 ①从n-1阶再爬一阶到达 ②也可从n-2阶再爬2阶到达,这两种方式,所以爬到n阶的方法数等于爬到n-1阶的方法数与爬到n-2的方法数的和。接下来,n-1阶的方法数也等于n-2阶的加上n-3阶的,以此类推直到爬1阶和爬0阶。所以,这个问题本质就是一个fibonacci数列的求解! 而且这里面也存在很多重复计算的子块,例如下图蓝色子块。同上面的代码一样,这个问题完全可以用原生的递归方法、记忆化搜索和动态规划求解的。
爬楼梯

LeetCode上 其他相似的例题:
在这里插入图片描述
在这里插入图片描述


4. 动态规划的核心:状态与状态转移方程

前面我们将动态规划描述为一种 通过求子问题的最优解,从而求得原问题的最优解 的方法,那么有人可能会问了:

  1. 如何将原问题切分成子问题?
  2. 又怎么将子问题的解延伸回原问题,也就是说子问题和原问题是怎么连接起来的?

对于问题1,如何切分子问题,在DP中被称为状态的定义,其中子问题被称为状态
对于问题2:,如何从子问题到原问题,在DP被称为状态转移方程

这两个东西才是动态规划的核心。

到底是怎么回事,直接拿LeetCode上两个例子说话:

LeetCode 343 Integer Break

LeetCode 343 Integer Break
解析:

  1. 我们定义 v[n] 分割正数n的最大乘积 ,这是状态的定义
  2. 每次将n分解为两个数的和,如:1+(n-1), 2+(n-2), 3+(n-3)…,对于每一种分割 i+(n-i),最大乘积 v[n] 的来源于 i 和 n-i 的乘积 和 i 和 v[n-i] 的乘积 中较大的那个,而最终的最大v[n]是在所有 1<=i<=n上最大的那个,即v[n] = max( i×(n-i), i×v[n-i] ) for 1<=i<=n-1 ,这就是从v[n-1] 到 v[n] 的状态转移方程
  3. 这里存在重叠子问题,如下图中蓝块。
    在这里插入图片描述
    思路: 递归、记忆化搜索、动态规划
  1. 状态定义: v[n] 分割正数n的最大乘积
  2. 状态转移: v[n] = max( i×(n-i), i×v[n-i] ) for 1<=i<=n-1

代码
我将递归、记忆化搜索和动态规划三种方法放在了一个类中:

class Solution:def integerBreak_rec(self, n):""":type n: int:rtype: int原生的递归方法:效率低"""def breakInteger(n):if n==1:return 1res = -1for i in range(1,n):# n = i + (n-i)res = max( res, i*(n-i), i*breakInteger(n-i) )return resreturn breakInteger(n)def integerBreak_memo(self, n):""":type n: int:rtype: int第一种改进: 自上而下的记忆化搜索"""def breakInteger(n):if n==1:return 1if memo[n] != -1:return memo[n]res = -1for i in range(1,n):# n = i + (n-i)res = max( res, i*(n-i), i*breakInteger(n-i) )memo[n] = resreturn resassert n>=2memo = [-1 for i in range(n+1)]return breakInteger(n)def integerBreak_dp(self, n):""":type n: int:rtype: int第二种改进:自底向上动态规划"""assert n>=2memo = [-1 for i in range(n+1)]memo[1] = 1for i in range(2,n+1):for j in range(1,i):memo[i] = max( memo[i], j*(i-j), j*memo[i-j] )return memo[n]if __name__ == '__main__':      n = Solution().integerBreak_dp(10)print(n)

LeetCode上与此题相似的题目:

  • 279 Perfect Squares
    给定一格正整数n,寻找最少的完全平方数,使他们的和为n。
  • 91 Decode Ways
    数字字符串的解析
  • 62 Unique Paths
    机器人从m×n的矩阵左上角出发到达右下角(每次只能向右或向下),共有多少种路径?
  • 63 Unique Paths II
    (接上题)若矩阵中含有障碍物,此时共有多少种路径?

LeetCode 198 House Robber

小偷洗劫一条街上的所有房子,每个房子有不同价值的把宝物,但不能连续抢劫俩相邻房子。求最多可以偷到多少宝物?

暴力解法

检查所有房子的组合,对每一个组合,检查是否有相邻的房子,如果没有,记录其价值,找最大值。
时间复杂度: O( (2^n)*n )
这个方法效率非常之低。

动态规划

考虑如下结构
在这里插入图片描述

"""
198. House Robber转态定义:考虑偷取[x...n)范围里的房子
状态转移方程:f(0) = max{ v(0)+f(2),v(1)+f(3),v(2)+f(4),...v(n-3)+f(n-1),v(n-2),v(n-1)}  
"""
class Solution:def rob_rec(self, nums):""":type nums: List[int]:rtype: int递归方法"""# 考虑抢劫nums[index:]范围内的所有房子def tryRob(index):if index >= len(nums):return 0res = 0for i in range(index,len(nums)):res = max( res, nums[i]+tryRob(i+2) )return resreturn tryRob(0)def rob_memo(self, nums):""":type nums: List[int]:rtype: int记忆化搜索"""# 考虑抢劫nums[index:]范围内的所有房子def tryRob(index):if index >= len(nums):return 0if memo[index] != -1:return memo[index]res = 0for i in range(index,len(nums)):res = max( res, nums[i]+tryRob(i+2) )memo[index] = resreturn res# memo[i] 表示考虑抢劫 nums[i...n) 所能获得的最大收益memo = [-1 for i in range(n)]return tryRob(0)def rob_dp(self, nums):""":type nums: List[int]:rtype: int动态规划法"""n = len(nums)if n == 0:return 0# memo[i] 表示考虑抢劫 nums[i...n) 所能获得的最大收益memo = [-1 for i in range(n)]memo[n-1] = nums[n-1]for i in range(n-2,-1,-1):# memo[i]for j in range(i,n):if j<n-2:memo[i] = max(memo[i],nums[j]+(memo[j+2]) else:memo[i] = max(memo[i],nums[j]) # 末尾两个元素只能是自己return memo[0]if __name__ == '__main__':          nums = [2,7,9,3,1]res = Solution().rob_dp(nums)   print(res)

LeetCode上与此题相似的题目:

  • 213 House Robber II
    此处将街道改为环形街道,即最后一个元素和第一个元素为邻居。求窃得财产的最大值。
  • 337 House Robber III
    此处将街道给为小区,小区是二叉树结构,即不能同时选择相邻节点。
  • 309 Best Time to Buy and Sell Stock with Cooldown
    股票买卖时机的选择,使利润最大化。

5. 动态规划经典应用:背包问题

在动态规划中,有类非常经典的问题称为0-1背包问题。有一个背包,容量是C,现有那种不同的物品,编号为0…n-1
,其中每一件物品重量为w(i),价值为v(i)。问可以向包中放哪些物品,使得在不超过背包容量的基础上,物品总价值最大。

三种解法

暴力解法:每一件物品都可以放进或者不放进,遍历所有可能性。 时间复杂度O((2^n)*n)。
贪心算法:优先放入平均价值最大的物品,结果只是近似解,而不是最优解。
动态规划

状态: F(n,C) 考虑将n个物品放进容量为C的背包,使得价值最大。(这里约束条件为n和C)
状态转移方程: F(i,C) = max( F(i-1,C), v(i)+F(i-1,C-w(i)) )
对应 1.不放入第i个物品 2.放入第i个物品 这两种状态中值最大的那个。

背包问题动态规划解法的实现及优化

时间复杂度为O(nC)已经几乎没有优化空间了,但是可以从空间复杂度上进行优化。

  1. 动态规划解法: n行版
    原始动态规划,建立一个n行C+1列的矩阵 memo[n-1][C+1]
    时间复杂度:O(nC)
    空间复杂度:O(nC)

  2. 动态规划解法: 两行版
    改进1:由于第i行元素只依赖于第i-1行元素。理论上,memo只需要保证两行元素。
    空间复杂度 O(2C)=O( C)

  3. 动态规划解法: 一行版
    改进2:由于某位置的元素只依赖于上一行的对应位置和上一行的左侧元素。所以可以从右边开始更新,这样memo只需一行就可以完成。
    空间复杂度 O( C)

背包问题的变种

  • 多重背包问题:每个物品不止1个,有num(i)个
  • 完全背包问题:每个物品可以无限使用
  • 多维费用背包问题:要考虑物品的体积和重量两个维度(原问题上增加一个约束条件,memo数组变为三维即可)
  • 物品间加入更多约束:物品间可以相互排斥;也可以相互依赖

背包问题实例

416 Patition Equal Subset Sum 分割等和子集

典型的背包问题:在n个物品中选出一定物品,填满sum/2的背包
与原背包问题不同的是:我们不需要物品价值最大,目标是能填满。或者也可以理解为物品价值均为1。

LeetCode上与此题相似的题目:

  • 322 Coin Change
    给定不同面值的硬币,问需要多少硬币(可反复使用),可以凑成指定的金额?
  • 377 Combination Sum IV
    给定一整数数组(无重复数字,但数字可重复使用),问有多少种可能,使用这个数组中的数字,凑成一个指定的整数target。
  • 474 Ones and Zeros
  • 139 Word Break
    判断字符串数组中可否有不同字符串收尾相连组成target字符串。
  • 494 Target Sum
    给定非0数字数组,在这些数前面加上+或-,使其结果为给定的整数S。

6. 动态规划应用: 最长上升子序列(LIS)

300 Longest Increasing Subsequence

给定一整数序列,求其中的最长上升子序列的长度。

  • 方法1:动态规划解法
    1. 状态定义: LIS(i) 表示以第i个数字为结尾的最长上升子序列的长度。即表示[0…i]范围内,选择数组nums[i] 可以获得的最长上升子序列的长度
    2. 状态转移: LIS(i) = max( 1+LIS(j) if nums[i]>nums[j] for j<i )
    时间复杂度O(n^2)
  • 方法2:二分搜索法
    这里不做介绍。
    时间复杂度O(nlogn)

LeetCode上与此题相似的题目:

  • 376 Wiggle Subsequence
    求最长轮流交替子序列

最长公共子序列: LCS问题

给出两个字符串S1和S2,求这两个字符串的最长公共子序列的长度。
状态定义

LCS(m,n) 是S1[0…m]和S2[0…n]的最长公共子序列的长度。

转移方程

  1. S1[m] == S2[n]: LCS(m,n) = 1+ LCS(m-1,n-1)
  2. S1[m] != S2[n]: LCS(m,n) = max(LCS(m-1,n),LCS(m,n-1))

7. 关于动态规划的其他:

  • 动态规划其实相当常见,譬如 dijkstra单源最短路径算法也是动态规划

    状态定义: shortestPath(i)为从start到i的最短路径长度
    状态转移: shortestPath(i) = min( shortestPath(i) + w(a->i))

  • 动态规划是一个非常大的问题范畴,因为它不仅他可以解决的问题是非常灵活的,还因为它是公认的在算法设计上艺术含量非常高的算法,因为动态规划似乎没有一个固定的套路。
  • LeetCode上更多关于动态规划的问题,找dp标签的题。
  • 单论面试,面试中动态规划考察较少,更多的是考察更基础的问题,所以可以相对放松对LeetCode上动态规划题目的要求。

8. 如何用动态规划给出具体的解:返回去找

例1: 300. 最长上升子序列。
例2: 0-1背包问题

References:
非常好的动态规划总结,DP总结
背包问题的总结
动态规划(dp) 之 状态转移方程


http://chatgpt.dhexx.cn/article/N2wDgrPi.shtml

相关文章

Hi3519AV100与Hi3559AV100在芯片规格 上主要差异

表1-1简要对比了Hi3519AV100与Hi3559AV100在规格方面的差异&#xff0c;Hi3519AV100的具体规格请参见《Hi3519AV100 ultra-HD Mobile Camera SoC 用户指南》。

海思平台(hi3559av100)异构多系统的使用Linux(2*A53+2*A73)+liteos(A53)+liteos(M7)

在文档《SDK安装及升级使用说明》中有对linuxliteos异构多系统的烧写有介绍。这里对其中的一些注意的地方记录以下&#xff0c;以备查验。 由于我的目标是要搭建一个ISP调试环境&#xff0c;就是使用海思的ittp_stream工具能够连接上开发板&#xff0c;并能够实时查看摄像头的…

M302H-ZN-Hi3798MV300/MV300H-当贝纯净桌面-卡刷固件包

M302H-ZN-Hi3798MV300&#xff0f;MV300H-当贝纯净桌面-卡刷固件包-内有教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用…

华为海思 hikey970 详细介绍

前几天申请到了华为的开发板&#xff1a;hikey970 用来做项目的。 板子是这样的: 下面是在网中收集到的信息总结&#xff1a; 基于麒麟970的AI智慧算力&#xff0c;HiKey 970除了支持CPU和GPU的AI运算外&#xff0c;还支持基于NPU的神经网络计算硬件加速。 公开资料显示&am…

海思Hi3519AV100 emmc flash方式 linux系统移植 hitool工具烧写

因为我这里的海思文档只有SPI NOR Flash方式的详细烧写步骤&#xff0c;没有emmc方式的&#xff0c;本文提供一个自己成功的案例仅供参考和记录 1. 准备SDK、安装交叉编译工具、编译osdrv 1.1 解压SDK包 将Hi3519AV100_SDK_Vx.x.x.x.tgz文件放入ubuntu系统下&#xff08;wind…

海思3559:MMZ内存、OS内存配置

前言 海思3559的DDR最大支持到8GB hi3559av100芯片的内存地址范围 (1)通过查阅数据手册可知《Hi3559AV100 专业型 Smart IP Camera SoC 用户指南》&#xff0c;芯片的内存地址范围是0x4000_0000-0x23FFF_FFFF&#xff0c;最大能支持8G内存&#xff1b;   (2)海思芯片把内存分…

劲爆!java架构师百度网盘

第一份资料:Kafka实战笔记 Kafka入门为什么选择KafkaKarka的安装、管理和配置Kafka的集群第一个Kafka程序afka的生产者 Kafka的消费者深入理解Kafka可靠的数据传递

10本Java架构师必读书籍推荐

##### 1.《大型网站系统与Java中间件开发实践》 本书围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。从分布式系统的知识切入&#xff0c;让读者对分布式系统有基本的了解&#xff1b;然后介绍大型网站随着数据量、访问量增长而发生的架构变迁&#xff1b;接着…

Java架构师需要哪些知识?

如何才能达到Java架构师技术要求标准&#xff1f;Java架构师需要熟练掌握复杂的数据结构和算法、熟练使用linux操作系统&#xff0c;Linux线上排除故障、熟悉tcp协议、系统集群、[负载均衡]、反向代理、动静分离&#xff0c;网站静态化、数据库设计能力、队列中间件等知识。 一…

JAVA架构师之路十六:设计模式之责任链模式

JAVA架构师之路十五&#xff1a;设计模式之策略模式 责任链模式 1. 责任链模式2. 登陆案例 3. 登陆案例优化 人生的游戏不在于拿了一副好牌&#xff0c;而在于怎样去打好坏牌&#xff0c;世上没有常胜将军&#xff0c;勇于超越自我者才能得到最后的奖杯。 1. 责任链模式 定义…

BAT面试高级进阶,Java架构师之路

说明 Java生鲜电商平台中由于采用了微服务架构进行业务的处理&#xff0c;买家&#xff0c;卖家&#xff0c;配送&#xff0c;销售&#xff0c;供应商等进行服务化&#xff0c;但是不可避免存在分布式事务的问题。 业界有很多的解决方案&#xff0c;对此我相信大家都百度一下…

JAVA架构师之路-视频学习

https://pan.baidu.com/s/1GK-HNdG_HsNTb_QQ6_L3Tg 目录&#xff1a; 第一套 JAVA高级架构师之旅 第2套 Java互联网架构师netty、mina、nio 第三套 阿里开源Dubbo 【第四套】互联网综合实战项目介绍 【第五套】高性能缓存Memcached服务深度原理及实战视频课程 【第六套】高级J…

JAVA架构师之路十五:设计模式之策略模式

JAVA架构师之路十四&#xff1a;设计模式之模板模式 策略模式 1. 策略模式2. 优惠券案例3. 支付案例 人生的游戏不在于拿了一副好牌&#xff0c;而在于怎样去打好坏牌&#xff0c;世上没有常胜将军&#xff0c;勇于超越自我者才能得到最后的奖杯。 1. 策略模式 定义 策略模式…

走向Java架构师之路:成为架构师要掌握的8大能力

架构师是什么?是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。一个架构师得需要足够的想像力,能把各种目标需求进行不同维度的扩展,为目标客户提供更为全面的需求清单。 如何才能达到Java架构师技术要求标准?Java架构师需要熟练…

JAVA架构师之路十一:设计模式之适配器模式

JAVA架构师之路十&#xff1a;设计模式之组合模式 适配器模式 1. 适配器模式2. 类适配器写法3. 对象适配器写法4. 接口适配器写法 钟表&#xff0c;可以回到起点&#xff0c;但已不是昨天。 生活中处处可见适配现象&#xff1a;手机充电器的充电头&#xff0c;电脑电源适配器&…

Java架构师:概述

一、Java架构师核心技术栈 二、架构师需要具备的其他能力 三、技术选型 四、早期传统JavaWeb开发模式 五、前后端分离开发模式 六、Maven聚合项目 七、数据库设计工具PDMan 八、数据库外键弊端【移除物理外键&#xff0c;而非逻辑外键】 数据库表与表之间字段间不要有物理外键…

Java架构师之路:微服务架构图解和详情

微服务框架搭建&#xff1a; 总体规划框架名称当前技术选型方案微服务框架搭建 开发框架 单体服务SpringBoot 分布式框架SpringCloud 最新框架SpringCloudAlibaba 服务配置中心 服务消息总线 阿里巴巴Nacos、 ConfigBusRabbitMQ配合使用、 携程apolo 服务网关 Spr…

java架构师进阶之路

要想进阶为架构师&#xff0c;不仅要有知识广度&#xff0c;还要有深度。 最近把今天收集的java学习资料整理了下&#xff0c;里面包含了计算机基础、算法和数据结构、常用工具、java核心知识、性能优化、基础框架、数据库、消息队列、缓存中间件、搜索引擎、大数据、RPC、网关…

通往Java架构师之路

Java架构师&#xff0c;应该算是一些Java程序员们的一个职业目标了吧,很多码农码了五六年的代码也没能成为架构师。那成为Java架构师要掌握哪些技术呢&#xff0c;总体来说呢&#xff0c;有两方面&#xff0c;一个是基础技术&#xff0c;另一个就是组织能力和提出解决方案能力。…

如何成为Java架构师?学习路线是怎样的?大佬总结成长之路跟着往上走,建议你提早开始准备!

导读 好程序员告诉你Java架构师学习路线&#xff0c;从初级Java工程师成长为Java架构师&#xff0c;你需要走很长的路&#xff0c;很多有计划的人在学习之初就在做准备。你知道Java架构师学习路线该怎么走吗?成为一个优秀的Java架构师究竟需要学什么?接下来就跟小编一起揭晓…