news 2026/5/7 11:47:31

05-经典算法——背包、LCS、N皇后、汉诺塔与更多

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
05-经典算法——背包、LCS、N皇后、汉诺塔与更多

老程序员回炉补基础(五):经典算法——背包、LCS、N皇后、汉诺塔与更多

这一章是我的"算法练兵场"。从递归入门的汉诺塔和阶乘,到动态规划的背包和LCS,再到回溯的N皇后,每一种算法思想都让我对"如何把现实问题抽象为数学模型"有了更深的理解。


一、递归入门:阶乘与汉诺塔

阶乘

学习时间:2023年9月27日

publicclassfactorial{publiclongcfactorial(longn){if(n==0){return1;}returnn*cfactorial(n-1);}}

最简单的递归。n! = n × (n-1)!,边界条件是 0! = 1。

递归三要素:

  1. 递归体n * cfactorial(n - 1)
  2. 终止条件n == 0
  3. 返回值:每次递归向终止条件逼近(n 不断减小)

汉诺塔

学习时间:2023年9月26日

publicclasshtower{publicvoidf(intn,Stringfrom,Stringmid,Stringto){if(n==1){System.out.println(n+":"+from+"->"+to);}else{f(n-1,from,to,mid);// 上面n-1个盘子从A移到BSystem.out.println(n+":"+from+"->"+to);// 最大的盘子从A移到Cf(n-1,mid,from,to);// n-1个盘子从B移到C}}}

汉诺塔是理解递归最好的例子。3个盘子:

f(3, A, B, C): f(2, A, C, B) → 把上面2个从A移到B(借C) 移动盘子3: A→C → 把最大的从A移到C f(2, B, A, C) → 把2个从B移到C(借A)

关键洞察:不管有多少个盘子,问题都可以分解为三步——把上面的移走、移最底下的、把上面的移回来。这就是分治的思想。


二、动态规划:0-1背包问题

学习时间:2023年10月7日

物品定义

publicclassStuff{privatedoubleweigth;// 重量privatedoublevalue;// 价值privateintnumber;// 编号publicStuff(intnumber,doubleweigth,doublevalue){this.number=number;this.value=value;this.weigth=weigth;}}

核心算法

publicclassKnapsack{/** * 0-1 背包问题 * @param list 物品列表 * @param weigth 背包容量 * @return 动态规划表 c[i][w] 表示前i个物品、容量为w时的最大价值 */publicint[][]f(List<Stuff>list,intweigth){intc[][]=newint[list.size()+1][weigth+1];for(inti=1;i<=list.size();i++){c[i][0]=0;for(intw=1;w<=weigth;w++){Stuffobj=list.get(i-1);if(obj.getWeigth()<=w){if(obj.getValue()+c[i-1][(int)(w-obj.getWeigth())]>c[i-1][w]){c[i][w]=(int)(obj.getValue()+c[i-1][(int)(w-obj.getWeigth())]);}else{c[i][w]=c[i-1][w];}}else{c[i][w]=c[i-1][w];}}}returnc;}}

状态转移方程

c[i][w] = max( c[i-1][w], // 不选第i个物品 value[i] + c[i-1][w - weight[i]] // 选第i个物品 )

测试用例

物品:[{编号:1, 重量:3, 价值:4}, {编号:5, 重量:9, 价值:13}, {编号:2, 重量:4, 价值:5}, {编号:3, 重量:7, 价值:10}, {编号:4, 重量:8, 价值:11}] 背包容量:17

我的理解

动态规划的核心是找到状态转移方程。背包问题的状态是"考虑前i个物品、容量为w时的最大价值",每个状态只依赖上一行的状态。

动态规划表(DP Table)是理解算法的关键:

w=0 w=1 w=2 w=3 w=4 ... w=17 i=0 0 0 0 0 0 ... 0 i=1 0 0 0 4 4 ... 4 i=2 0 0 0 4 4 ... 13 ...

从最后一行最后一列就能读出最优解。

架构师视角

背包问题的思想在工程中广泛应用:

  • 服务器资源分配:有限的CPU/内存,如何分配给多个服务使总收益最大?
  • 任务调度:有限时间内,选择哪些任务执行使产出最大?
  • 缓存策略:有限的缓存空间,缓存哪些数据使命中率最高?

三、动态规划:最长公共子序列(LCS)

学习时间:2023年9月27日

publicclassLCS{publicvoidf(char[]X,char[]Y,intl[][]){for(inti=1;i<=X.length;i++){for(intj=1;j<=Y.length;j++){if(X[i-1]==Y[j-1]){l[i][j]=l[i-1][j-1]+1;}else{intm=0,n=0;m=l[i-1][j];n=l[i][j-1];if(m<n){l[i][j]=m;// BUG:应该是 n}else{l[i][j]=n;// BUG:应该是 m}}}}}}

状态转移方程

if X[i] == Y[j]: l[i][j] = l[i-1][j-1] + 1 else: l[i][j] = max(l[i-1][j], l[i][j-1])

一个真实的 Bug

我在这段代码中犯了一个错误:当m < n时,应该取较大值n,但我写成了l[i][j] = m。这意味着当两个字符不匹配时,我取的是较小值而不是较大值

这个 Bug 不是抄代码会犯的错误——抄代码至少会抄对逻辑。它是理解不够深入时手写代码的典型问题:我记住了要比较,但搞反了取值的方向。

修正后的代码:

if(m<n){l[i][j]=n;// 取较大值}else{l[i][j]=m;// 取较大值}

测试

X = {'A','B','C','B','D','A','B'} Y = {'B','D','C','A','B','A'} LCS 长度 = 4("BCBA" 或 "BDAB")

架构师视角

LCS 的应用场景:

  • Git diff:比较两个版本的代码差异,本质是 LCS 问题
  • DNA 序列比对:生物信息学中比对基因序列的相似性
  • 拼写检查:找到最相似的单词

四、回溯:N皇后问题

学习时间:2023年10月13日

N皇后问题:在 N×N 的棋盘上放置 N 个皇后,使它们互不攻击(不在同一行、同一列、同一对角线上)。

递归解法

privateintn=8;privateintp[];publicvoidNqueue(intk){if(k==n){// 所有皇后都放好了,输出一种解法System.out.println(Arrays.toString(p));return;}for(inti=0;i<n;i++){intj=0;for(;j<k;j++){// 检查是否和已放置的皇后冲突if(p[j]==i||Math.abs(p[j]-i)==Math.abs(k-j)){break;}}if(j==k){// 没有冲突p[k]=i;Nqueue(k+1);// 放下一行的皇后}}}

我的理解

这是一个经典的回溯算法p[k] = i表示第 k 行的皇后放在第 i 列。

冲突检测只需两个条件:

  • p[j] == i:同一列
  • Math.abs(p[j] - i) == Math.abs(k - j):同一对角线(行差等于列差)

回溯的思想是:走一步看一步,走不通就退回来换一条路for循环尝试当前行的每一列,如果某一列不冲突就放下去,递归处理下一行;如果所有列都冲突,递归自然返回,上一行就会尝试下一个位置。

迭代解法

我还写了一个非递归版本wNqueue(),用while循环手动模拟递归的回溯过程:

publicvoidwNqueue(){inti,j,count=1;intpos1[]=newint[n+1];for(i=1;i<=n;i++)pos1[i]=0;j=1;while(j>=1){pos1[j]=pos1[j]+1;while(pos1[j]<=n&&!isplace(pos1,j)){pos1[j]=pos1[j]+1;}if(pos1[j]<=n&&j==n){// 找到一组解System.out.print("第 "+(count++)+":");for(i=1;i<=n;i++)System.out.print(pos1[i]);System.out.println();}if(pos1[j]<=n&&j<n){j=j+1;// 放下一行}else{pos1[j]=0;j=j-1;// 回溯到上一行}}}

非递归版本的核心:j是当前行号,pos1[j]是当前尝试的列号。当本行所有列都试过了还不行(pos1[j] > n),就重置当前行并回退到上一行。

8皇后的解

8皇后共有92种不同的放置方案。


五、更多算法练习

最大子段和

Sum.java中包含了最大子段和问题的两种解法:

分治法(O(n log n)):

intmaxSum(intarray[],intleft,intright){if(left>=right){returnarray[left]>0?array[left]:0;}intmid=(left+right)/2;intleftMax=maxSum(array,left,mid);intrightMax=maxSum(array,mid+1,right);// 跨中点的最大子段和ints0=0,s1=0,lefts=0,rights=0;for(inti=mid;i>left;i--){lefts+=array[i];if(lefts>s0)s0=lefts;}for(inti=mid+1;i<right;i++){rights+=array[i];if(rights>s1)s1=rights;}returnmax(leftMax,rightMax,s0+s1);}

Kadane 算法(O(n),最优解):

intmaxSum(int[]array){intmax=0,temp=0;for(inti=0;i<array.length;i++){temp+=array[i];if(temp>max)max=temp;if(temp<0)temp=0;// 前缀和为负就丢弃}returnmax;}

Kadane 算法是一个经典的贪心/DP混合思路:如果一段子数组的和已经是负数了,那它不可能成为最大子段和的一部分,直接丢弃从头开始。

最大公约数(欧几里得算法)

intgcd(inta,intb){intrem=0;while(b>0){rem=a%b;a=b;b=rem;}returna;}

辗转相除法,公元前 300 年欧几里得发明的算法,至今仍在使用。

快速幂

publicdoublepower(doublei,longn){if(n==3)returni*i*i;elseif(n==2)returni*i;elseif(n==1)returni;if(n%2==0){returnpower(i*i,n/2);}else{returnpower(i*i,n/2)*i;}}

快速幂将 O(n) 的乘法降到 O(log n):x^n = (x²)^(n/2)

页码统计

voidpageNumber(){intpages=99;intnumber[]=newint[10];for(inti=1;i<=pages;i++){intnum=i;do{intrem=num%10;number[rem]++;num=num/10;}while(num>0);}}

统计一本书从第1页到第99页,每个数字(0-9)出现了多少次。这是一个简单的数学问题,但对理解数位操作有帮助。


五种算法思想总结

算法思想代表问题核心思路
递归汉诺塔、阶乘大问题拆成同类小问题
分治最大子段和、归并排序分成子问题分别求解再合并
动态规划0-1背包、LCS记录子问题的解避免重复计算
回溯N皇后试探+撤销,走不通就回头
贪心Kadane算法、快速幂每一步选局部最优

写在最后:五个月的学习总结

从2023年8月到2024年1月,我用5个月时间补完了这些基础:

月份学习内容
2023.8链表、栈、队列
2023.9排序算法、递归
2023.10图、树、经典算法
2023.11堆排序、Java基础
2023.12JVM(类加载、内存)
2024.1快速排序(收官)

代码中有 Bug(LCS),有未完成的功能(Prim),有拼写错误(BSF/DSF)。但这些"不完美"恰恰证明了:这是真实的学习过程,而不是复制粘贴的成果展示

作为一个43岁的架构师,我想说的是:永远不要觉得补基础是浪费时间。你写的每一行底层代码,都会在未来的某一天,帮你做出更好的架构决策。


系列完

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 11:45:39

为什么选择vue-markdown?与其他Markdown渲染器的全面对比分析

为什么选择vue-markdown&#xff1f;与其他Markdown渲染器的全面对比分析 【免费下载链接】vue-markdown vue-markdown: 是一个用于Vue.js的Markdown渲染器组件&#xff0c;允许在Vue应用中轻松展示Markdown格式的内容。 项目地址: https://gitcode.com/gh_mirrors/vu/vue-ma…

作者头像 李华
网站建设 2026/5/7 11:45:38

Linux下部署MySQL5.7.35

1.MySQL下载 &#xff08;1&#xff09;登录到以下网站 https://downloads.mysql.com/archives/community/ &#xff08;2&#xff09;选择需要的版本 &#xff0c;以及操作系统 &#xff0c;这里是Red Hat Enterprise Linux / Oracle Linux 5.7.35 版本。 &#xff08;3&…

作者头像 李华
网站建设 2026/5/7 11:45:35

猫抓浏览器扩展:免费开源资源嗅探工具终极指南

猫抓浏览器扩展&#xff1a;免费开源资源嗅探工具终极指南 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 还在为网页视频无法下载而烦恼吗&#x…

作者头像 李华
网站建设 2026/5/7 11:41:39

国产替代之FQD6N40TM与VBE14R04参数对比报告

N沟道功率MOSFET参数对比分析报告一、产品概述FQD6N40TM&#xff1a;安森美&#xff08;onsemi&#xff09;N沟道硅MOSFET&#xff0c;采用QFET技术&#xff0c;耐压400V&#xff0c;具有低导通电阻和快速开关性能。封装&#xff1a;DPAK (TO-252)。适用于开关电源、DC-DC转换器…

作者头像 李华
网站建设 2026/5/7 11:34:59

Open UI5 源代码解析之1339:IsDate.js

源代码仓库: https://github.com/SAP/openui5 源代码位置:src\sap.ui.integration\src\sap\ui\integration\designtime\baseEditor\validator\IsDate.js IsDate.js 详细解析 文件定位与整体判断 IsDate.js 位于 sap.ui.integration 模块下的 designtime/baseEditor/vali…

作者头像 李华
网站建设 2026/5/7 11:34:51

原神60帧限制终极解决方案:免费解锁高帧率体验完整指南

原神60帧限制终极解决方案&#xff1a;免费解锁高帧率体验完整指南 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 你是否在原神游戏中感到画面卡顿不流畅&#xff1f;当其他游戏都能充分…

作者头像 李华