├── .gitignore ├── LICENSE ├── README.md └── docs └── Dynamic-programming ├── README.md └── figure ├── Binary-search-tree-1.png ├── Binary-search-tree-2.png ├── Binary-search-tree-3.png ├── Binary-search-tree-4.png ├── Binary-search-tree-5.png ├── Binary-search-tree-6.jpg ├── Binary-search-tree-7.jpg ├── DP-convex-triangle.jpg ├── DP-solved-LCS-1.png ├── DP-solved-LCS-2.png ├── Euclid-problem.jpg ├── KMP-1.png ├── KMP-2.png ├── KMP-3.png ├── KMP-4.png ├── LIS.png ├── Manacher-1.jpg ├── Manacher-2.jpg ├── Manacher-3.jpg ├── Maximum-disjoint-subsection.gif ├── N-empress.png ├── clip-problem-1.png ├── clip-problem-2.png └── squared-problem.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Angel_Kitty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithm 2 | 这里将会记录着我在学习算法中的一些总结 3 | -------------------------------------------------------------------------------- /docs/Dynamic-programming/README.md: -------------------------------------------------------------------------------- 1 | ## 浅谈我对动态规划的一点理解---大家准备好小板凳,我要开始吹牛皮了~~~ 2 | 3 | ### 前言 4 | 5 | 作为一个退役狗跟大家扯这些东西,感觉确实有点。。。但是,针对网上没有一篇文章能够很详细的把动态规划问题说明的很清楚,我决定还是拿出我的全部家当,来跟大家分享我对动态规划的理解,我会尽可能的把所遇到的动态规划的问题都涵盖进去,博主退役多年,可能有些地方会讲的不完善,还望大家多多贡献出自己的宝贵建议,共同进步~~~ 6 | 7 | ### 概念 8 | 9 | 首先我们得知道动态规划是什么东东,百度百科上是这么说的,动态规划 `(dynamic programming)` 是运筹学的一个分支,是求解决策过程 `(decision process)` 最优化的数学方法。20世纪50年代初美国数学家 `R.E.Bellman` 等人在研究多阶段决策过程 `(multistep decision process)` 的优化问题时,提出了著名的最优化原理 `(principle of optimality)` ,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著 `《Dynamic Programming》` ,这是该领域的第一本著作。 10 | 11 | 小伙伴们估计看到这段话都已经蒙圈了吧,那么动态规划到底是什么呢?这么说吧,动态规划就是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。 12 | 13 | 举个例子,我们都是从学生时代过来的人,学生时代我们最喜欢的一件事是什么呢?就是等待寒暑假的到来,因为可以放假了啊^-^,嘻嘻,谁会不喜欢玩呢~~可是呢,放假之前我们必须经历的一个过程就是期末考试,期末没考好,回家肯定是要挨板子的,所以我们就需要去复习啦,而在复习过程中我们是不是要去熟记书中的每一个知识点呢,书是一个主体,考试都是围绕着书本出题,所以我们很容易知道书本不是核心,书本中的若干个知识点才是核心,然后那个若干个知识点又可以拆解成无数个小知识点,是不是发现有点像一棵倒立的树呢,但是呢,当我们要运用这些知识点去解题时,每一道题所涉及的知识点,其实就是这些知识点的一个排列组合的所有可能结果的其中一种组合方式,这个能理解的了嘛? 14 | 15 | 对这个排列组合,我们举个例子,比如小明爸爸叫小明去买一包5元钱的香烟,他给了一张 `5` 元的,三张 `2` 元的,五张 `1` 元的纸币,问小明有几种付钱的方式?这个选择方式我们很容易就知道,我们可以对这些可能结果进行一个枚举。 16 | 17 | 这个组合方式有很多种,我们可以对其进行一个分类操作: 18 | 19 | - 当我们只用一张纸币的时候:一张 `5` 元纸币 20 | 21 | - 当我们需要用两张纸币的时候:结果不存在 22 | 23 | - 当我们需要用三张纸币的时候:两张 `2` 元纸币和一张 `1` 元纸币 24 | 25 | - 当我们需要用四张纸币的时候:一张2元纸币,三张一元纸币 26 | 27 | - 当我们需要用五张纸币的时候:五张一元纸币 28 | 29 | 从上面的分类分析来看,我们知道,排列组合的方式共有四种,而我如果问你,我现在需要花费的纸币张数要最小,我们应该选取哪种方式呢,很显然我们直接选取第一种方法,使用一张 `5` 元的纸币就好了啊,这个就是求最优解的问题啦,也就是我们今天需要研究的问题,动态规划问题 30 | 31 | 相信大家到了这里,对动态规划应该有了初步的认识吧,我也很高兴带大家一起畅游算法的美妙,那么请继续听我吹牛皮吧,啦啦啦~~~ 32 | 33 | ### 基本思想 34 | 35 | 若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 36 | 37 | 简单来说,还是拿上面的例子来讲,比如说你做一道数学难题,难题,无非就是很难嘛,但是我们需要做的就是把这道难题解出来,对于一个数学水平很菜的选手来讲,做出一道难题是不是会感觉非常困难呢?其实换个角度来看待这个问题,一道难题其实是由若干个子问题构成,而每一个子问题也许会是一些很基础的问题,一个入门级的问题,类似于像 `1+1=2` 这样的问题,相信大家只要有所接触都能熟练掌握,而针对这些难题,我们也应该去考虑把它进行一个分解,我现在脑边还能回忆起中学老师说过的话,做不来的题目你可以把一些解题过程先写出来,把最基本的思路写出来,写着写着说不定答案就出来了呢?相信看完我这篇文章的人水平都能再上一个台阶,不仅如此,对于当前的全球热潮 `Artificial Intelligence` 也是如此,看似非常复杂繁琐的算法,把它进行一个拆解,其实就是若干个数学公式的组合,最根本的来源还是基础数学,所以啊,学好数学,未来是光明的~~~ 38 | 39 | ### 分治与动态规划 40 | 41 | **共同点**:二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题.然后将子问题的解合并,形成原问题的解. 42 | 43 | **不同点:**分治法将分解后的子问题看成相互独立的,通过用递归来做。 44 | 45 | 动态规划将分解后的子问题理解为相互间有联系,有重叠部分,需要记忆,通常用迭代来做。 46 | 47 | ### 问题特征 48 | 49 | 最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。 50 | 51 | 重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。 52 | 53 | 我认为可能会和回溯的部分问题有点类似,有兴趣的同学可以自行阅读一下我曾经写过的文章[回溯算法入门及经典案例剖析(初学者必备宝典)](http://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html) 54 | 55 | ### 解题步骤 56 | 57 | **1.找出最优解的性质,刻画其结构特征和最优子结构特征,将原问题分解成若干个子问题;** 58 | 59 | 把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决,子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。 60 | 61 | **2.递归地定义最优值,刻画原问题解与子问题解间的关系,确定状态;** 62 | 63 | 在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 64 | 65 | **3.以自底向上的方式计算出各个子问题、原问题的最优值,并避免子问题的重复计算;** 66 | 67 | 定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。 68 | 69 | **4.根据计算最优值时得到的信息,构造最优解,确定转移方程;** 70 | 71 | 状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。 72 | 73 | ### 实例分析 74 | 75 | #### 1.01背包问题 76 | 77 | 有 `N` 件物品和一个容量为 `V` 的背包。第i件物品的费用是 `c[i]` ,价值是 `w[i]` 。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 78 | 79 | `f[i][v]`表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:`f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}` 。 80 | 81 | 将前i件物品放入容量为 `v` 的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前 `i-1` 件物品的问题。如果不放第i件物品,那么问题就转化为"前 `i-1` 件物品放入容量为 `v` 的背包中";如果放第 `i` 件物品,那么问题就转化为"前 `i-1` 件物品放入剩下的容量为 `v-c[i]` 的背包中”,此时能获得的最大价值就是 `f [i-1][v-c[i]]` 再加上通过放入第i件物品获得的价值 `w[i]` 。 82 | 83 | 对 `01` 背包不清楚的或者有兴趣阅读的同学请移步至[这里](http://www.cnblogs.com/ECJTUACM-873284962/p/6815610.html) 84 | 85 | 下面贴下 `01` 背包的模板 86 | 87 | ```C++ 88 | void backtrack(int i,int cp,int cw) 89 | { 90 | if(i>n) 91 | { 92 | if(cp>bestp) 93 | { 94 | bestp=cp; 95 | for(i=1;i<=n;i++) bestx[i]=x[i]; 96 | } 97 | } 98 | else 99 | { 100 | for(int j=0;j<=1;j++) 101 | { 102 | x[i]=j; 103 | if(cw+x[i]*w[i]<=c) 104 | { 105 | cw+=w[i]*x[i]; 106 | cp+=p[i]*x[i]; 107 | backtrack(i+1,cp,cw); 108 | cw-=w[i]*x[i]; 109 | cp-=p[i]*x[i]; 110 | } 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | 最终我们可以去得到答案: 117 | 118 | ```c++ 119 | int n,c,bestp;//物品个数,背包容量,最大价值 120 | int p[10000],w[10000],x[10000],bestx[10000];//物品的价值,物品的重量,物品的选中情况 121 | int main() 122 | { 123 | bestp=0; 124 | cin>>c>>n; 125 | for(int i=1;i<=n;i++) cin>>w[i]; 126 | for(int i=1;i<=n;i++) cin>>p[i]; 127 | backtrack(1,0,0); 128 | cout<>n; 149 | int i,j,k,len; 150 | memset(dp,0,sizeof(dp)); 151 | //len是设置步长,也就是j减i的值 152 | for(i=0;i>a[i]; 153 | for(i=0;i` , `Y=` ,求 `LCS` 与最长公共子串。 182 | 183 | **暴力解法:** 184 | 185 | 假设 `m` 是 `X` 与 `Y` 的 `LCS` , 我们观察到 190 | 191 | - 如果 `xm=yn` ,则 `zk=xm=yn` ,有 `Zk−1` 是 `Xm−1` 与 `Yn−1` 的 `LCS` ; 192 | - 如果 `xm≠yn` ,则 `Zk` 是 `Xm` 与 `Yn−1` 的 `LCS` ,或者是 `Xm−1` 与 `Yn` 的 `LCS` 。 193 | 194 | 因此,求解 `LCS` 的问题则变成递归求解的两个子问题。但是,上述的递归求解的办法中,重复的子问题多,效率低下。改进的办法——**用空间换时间**,用数组保存中间状态,方便后面的计算。这就是动态规划 `(DP)` 的核心思想了。 195 | 196 | **DP求解LCS** 197 | 198 | 用二维数组 `c[i][j]` 记录串 `x1x2⋯xi` 与 `y1y2⋯yj` 的 `LCS` 长度, 199 | 200 | 用 `i` , `j` 遍历两个子串 `x` , `y` ,如果两个元素相等就 `+1` ,不等就用上一个状态最大的元素 201 | 202 | ![DP-solved-LCS](./figure/DP-solved-LCS-1.png) 203 | 204 | 实现过程如下: 205 | 206 | ```c++ 207 | int lcs(string str1, string str2, vector>& vec) { 208 | int len1 = str1.size(); 209 | int len2 = str2.size(); 210 | vector> c(len1 + 1, vector(len2 + 1, 0)); 211 | for (int i = 0; i <= len1; i++) { 212 | for (int j = 0; j <= len2; j++) { 213 | if (i == 0 || j == 0) { 214 | c[i][j] = 0; 215 | } 216 | else if (str1[i - 1] == str2[j - 1]) { 217 | c[i][j] = c[i - 1][j - 1] + 1; 218 | vec[i][j] = 0; 219 | } 220 | else if (c[i - 1][j] >= c[i][j - 1]){ 221 | c[i][j] = c[i - 1][j]; 222 | vec[i][j] = 1; 223 | } 224 | else{ 225 | c[i][j] = c[i][j - 1]; 226 | vec[i][j] = 2; 227 | } 228 | } 229 | } 230 | 231 | return c[len1][len2]; 232 | } 233 | 234 | void print_lcs(vector>& vec, string str, int i, int j) 235 | { 236 | if (i == 0 || j == 0) 237 | { 238 | return; 239 | } 240 | if (vec[i][j] == 0) 241 | { 242 | print_lcs(vec, str, i - 1, j - 1); 243 | printf("%c", str[i - 1]); 244 | } 245 | else if (vec[i][j] == 1) 246 | { 247 | print_lcs(vec, str, i - 1, j); 248 | } 249 | else 250 | { 251 | print_lcs(vec, str, i, j - 1); 252 | } 253 | } 254 | 255 | int _tmain(int argc, _TCHAR* argv[]) 256 | { 257 | string str1 = "123456"; 258 | string str2 = "2456"; 259 | vector> vec(str1.size() + 1, vector(str2.size() + 1, -1)); 260 | int result = lcs(str1, str2, vec); 261 | 262 | cout << "result = " << result << endl; 263 | 264 | print_lcs(vec, str1, str1.size(), str2.size()); 265 | 266 | getchar(); 267 | return 0; 268 | } 269 | ``` 270 | 271 | **DP求解最长公共子串** 272 | 273 | 前面提到了子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组c[i,j]c[i,j]用来记录具有这样特点的子串——结尾为母串x1x2⋯xix1x2⋯xi与y1y2⋯yjy1y2⋯yj的结尾——的长度。 274 | 275 | 区别就是因为是连续的,如果两个元素不等,那么就要=0了而不能用之前一个状态的最大元素 276 | 277 | ![DP-solved-LCS](./figure/DP-solved-LCS-2.png) 278 | 279 | 最长公共子串的长度为 `max(c[i,j])` , `i∈{1,⋯,m}` , `j∈{1,⋯,n}` 。 280 | 281 | 实现过程如下: 282 | 283 | ```c++ 284 | int lcs_2(string str1, string str2, vector>& vec) { 285 | int len1 = str1.size(); 286 | int len2 = str2.size(); 287 | int result = 0; //记录最长公共子串长度 288 | vector> c(len1 + 1, vector(len2 + 1, 0)); 289 | for (int i = 0; i <= len1; i++) { 290 | for (int j = 0; j <= len2; j++) { 291 | if (i == 0 || j == 0) { 292 | c[i][j] = 0; 293 | } 294 | else if (str1[i - 1] == str2[j - 1]) { 295 | c[i][j] = c[i - 1][j - 1] + 1; 296 | vec[i][j] = 0; 297 | result = c[i][j] > result ? c[i][j] : result; 298 | } 299 | else { 300 | c[i][j] = 0; 301 | } 302 | } 303 | } 304 | return result; 305 | } 306 | 307 | void print_lcs(vector>& vec, string str, int i, int j) 308 | { 309 | if (i == 0 || j == 0) 310 | { 311 | return; 312 | } 313 | if (vec[i][j] == 0) 314 | { 315 | print_lcs(vec, str, i - 1, j - 1); 316 | printf("%c", str[i - 1]); 317 | } 318 | else if (vec[i][j] == 1) 319 | { 320 | print_lcs(vec, str, i - 1, j); 321 | } 322 | else 323 | { 324 | print_lcs(vec, str, i, j - 1); 325 | } 326 | } 327 | int _tmain(int argc, _TCHAR* argv[]) 328 | { 329 | string str1 = "123456"; 330 | string str2 = "14568"; 331 | vector> vec(str1.size() + 1, vector(str2.size() + 1, -1)); 332 | int result = lcs_2(str1, str2, vec); 333 | 334 | cout << "result = " << result << endl; 335 | 336 | print_lcs(vec, str1, str1.size(), str2.size()); 337 | 338 | getchar(); 339 | return 0; 340 | } 341 | ``` 342 | 343 | #### 4.走金字塔 344 | 345 | 给定一个由 `n` 行数字组成的数字三角型,如图所示。设计一个算法,计算从三角形的顶至底的一条路径,使该路径经过的数字总和最大。路径上的每一步都只能往左下或右下走,给出这个最大和。 346 | ​ 7 347 | ​ 3 8 348 | ​ 8 1 0 349 | 2 7 4 4 350 | 4 5 2 6 5 351 | 352 | 对于这种问题,我们可以有正向和反向两种思考方式。正向思考这个问题, `dp[i][j]` 表示从第一行第一列到第i行第j列最大的数字总和;反向思考这个问题, `dp[i][j]` 表示从第i行第j列到最后一行最大的数字总和。反向思考的代码要简洁一些。 353 | 354 | 正向思考: 355 | 356 | ```c++ 357 | int triangle[110][110],dp[110][110]; 358 | int main() 359 | { 360 | int N; 361 | cin>>N; 362 | memset(dp,0,sizeof(dp)); 363 | memset(triangle,0,sizeof(triangle)); 364 | for(int i=1;i<=N;i++) 365 | { 366 | for(int j=1;j<=i;j++) 367 | { 368 | cin>>triangle[i][j]; 369 | } 370 | } 371 | dp[1][1]=triangle[1][1]; 372 | for(int i=2;i<=N;i++) 373 | { 374 | for(int j=1;j<=i;j++) 375 | { 376 | if(j!=1) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+triangle[i][j]); 377 | if(j!=i) dp[i][j]=max(dp[i][j],dp[i-1][j]+triangle[i][j]); 378 | } 379 | } 380 | int max=-1; 381 | for(int i=1;i<=N;i++) 382 | { 383 | if(dp[N][i]>max) max=dp[N][i]; 384 | } 385 | cout<>N; 398 | memset(dp,0,sizeof(dp)); 399 | memset(triangle,0,sizeof(triangle)); 400 | for(int i=1;i<=N;i++) 401 | { 402 | for(int j=1;j<=i;j++) 403 | { 404 | cin>>triangle[i][j]; 405 | } 406 | } 407 | for(int i=1;i<=N;i++) 408 | { 409 | dp[N][i]=triangle[N][i]; 410 | } 411 | for(int i=N-1;i>=1;i--) 412 | { 413 | for(int j=1;j<=i;j++) 414 | { 415 | dp[i][j]=max(dp[i+1][j]+triangle[i][j],dp[i+1][j+1]+triangle[i][j]); 416 | } 417 | } 418 | cout<a[j]&&b[j]>max) 454 | { 455 | max=b[j]; 456 | } 457 | } 458 | b[i]=max+1; 459 | } 460 | max=0; 461 | for(i=0;i>n; 485 | int a[1010]; 486 | 487 | for(i=0;i>a[i]; 490 | } 491 | int dp[MAX_N]; 492 | fill(dp,dp+n,INF); 493 | for(i=0;is31)&&(i>low)) 524 | { 525 | s31+=a[i-1]; 526 | i--; 527 | } 528 | j=mid+1; 529 | s32=a[mid+1]; 530 | while((s32+a[j+1]>s32)&&(j0` 时 `b[j]=b[j-1]+a[j]` ,否则 `b[j]=a[j]` 。故 `b[j]` 的动态规划递归式为: `b[j]=max(b[j-1]+a[j],a[j])` , `1<=j<=n` 。 549 | 550 | ```c++ 551 | int maxsub(int a[],int n) 552 | { 553 | int sum=0,b=0; 554 | for(int i=0;i<=n;i++) 555 | { 556 | if(b>0) b+=a[i]; 557 | else b=a[i]; 558 | if(b>sum) sum=b; 559 | } 560 | return sum; 561 | } 562 | 563 | int main() 564 | { 565 | int a[6]={-2,11,-4,13,-5,-2}; 566 | cout<>n; 588 | for(int i=1;i<=n;i++) 589 | { 590 | for(int j=1;j<=n;j++){scanf("%d", &array[i][j]);} 591 | } 592 | memset(f, 0, sizeof(f)); 593 | int ans=-inf; 594 | for(int i=1;i<=n;i++) 595 | { 596 | for(int j=1;j<=n;j++) 597 | { 598 | int sum=0; 599 | for(int k=j;k<=n;k++) 600 | { 601 | sum+=array[i][k]; 602 | f[i][j][k]=max(f[i-1][j][k]+sum,sum);//i是指行,j是起始列,k是终结列,f存的值为在ijk范围内的元素和最大值 603 | ans=max(ans,f[i][j][k]); 604 | } 605 | } 606 | } 607 | cout<j) 778 | { 779 | cout<<"d"<=j` 的情况。同时定义 `dist(i,j)` 为点 `Pi` 到 `Pj` 之间的直线距离。根据题意我们需要求的是 `d(n,n)` 。 798 | 关于子问题 `d(i,j)` 的求解,分三种情况: 799 | 当 `j1e-3) 861 | { 862 | dp[i-1][i]=dp[i][i-1]=dp[k][i-1]+dis[k][i]; 863 | } 864 | } 865 | for(int k=1;k1e-3) 868 | { 869 | dp[i][i]=dp[k][i]+dis[k][i]; 870 | } 871 | } 872 | } 873 | printf("%.2f\n",dp[n][n]); 874 | } 875 | return 0; 876 | } 877 | ``` 878 | 879 | #### 11.最大不相交子段和 880 | 881 | 求两个不相交子段加起来的最大值。 882 | 883 | ![Maximum-disjoint-subsection](./figure/Maximum-disjoint-subsection.gif) 884 | 885 | 将数组a[]从下标k处划分成两部分 `a[1-k]` 和 `a[k+1,N]` ,用 `m1` 表示 `a[1-k]` 的最大字段和, `m2` 表示 `a[k+1,N]` 的最大字段和,则 `S = Max { m1, m2 } ` ( 其中 `k` 属于区间 `[2-N-1] ` , `k` 不能取 `1` 和 `N` ,因为题目中 `j= 0 ) 910 | b[i] = b[i-1] + a[i]; 911 | else 912 | b[i] = a[i]; 913 | m1[i] = b[i] > m1[i-1] ? b[i] : m1[i-1]; 914 | } 915 | 916 | for( int i = n; i >= 1; -- i ) { 917 | if( b[i+1] >= 0 ) 918 | b[i] = b[i+1] + a[i]; 919 | else 920 | b[i] = a[i]; 921 | m2[i] = b[i] > m2[i+1] ? b[i] : m2[i+1]; 922 | } 923 | 924 | maxSum = INF; 925 | for( int i = 1; i < n; ++ i ) { 926 | if( m1[i] + m2[i+1] > maxSum ) 927 | maxSum = m1[i] + m2[i+1]; 928 | } 929 | printf( "%d\n", maxSum ); 930 | } 931 | return 0; 932 | } 933 | ``` 934 | 935 | #### 12.最长回文子串 936 | 937 | 回文是指正着读和倒着读,结果一些样,比如 `abcba` 或 `abba` 。 938 | 939 | 举个例子,我们要在一个字符串中要到最长的回文子串。 940 | 941 | **暴力求解:** 942 | 943 | 最容易想到的就是暴力破解,求出每一个子串,之后判断是不是回文,找到最长的那个。 944 | 945 | 求每一个子串时间复杂度 `O(N^2)` ,判断子串是不是回文 `O(N)` ,两者是相乘关系,所以时间复杂度为 `O(N^3)` 。 946 | 947 | ```c++ 948 | string findLongestPalindrome(string &s) 949 | { 950 | int length=s.size();//字符串长度 951 | int maxlength=0;//最长回文字符串长度 952 | int start;//最长回文字符串起始地址 953 | for(int i=0;i=tmp2&&j-i>maxlength) 963 | { 964 | maxlength=j-i+1; 965 | start=i; 966 | } 967 | } 968 | if(maxlength>0) 969 | return s.substr(start,maxlength);//求子串 970 | return NULL; 971 | } 972 | ``` 973 | 974 | **动态规划** 975 | 976 | 回文字符串的子串也是回文,比如 `P[i,j]` (表示以 `i` 开始以 `j` 结束的子串)是回文字符串,那么 `P[i+1,j-1]` 也是回文字符串。这样最长回文子串就能分解成一系列子问题了。这样需要额外的空间 `O(N^2)` ,算法复杂度也是 `O(N^2)` 。 977 | 978 | 首先定义状态方程和转移方程: 979 | 980 | `P[i,j]=0` 表示子串 `[i,j]` 不是回文串。 `P[i,j]=1` 表示子串 `[i,j]` 是回文串。 981 | 982 | ```c++ 983 | string findLongestPalindrome(string &s) 984 | { 985 | const int length=s.size(); 986 | int maxlength=0; 987 | int start; 988 | bool P[50][50]={false}; 989 | for(int i=0;i=2) 1011 | return s.substr(start,maxlength); 1012 | return NULL; 1013 | } 1014 | ``` 1015 | 1016 | **中心扩展** 1017 | 1018 | 中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为 `O(N^2)` 。 1019 | 1020 | 但是要考虑两种情况: 1021 | 1022 | 1、像 `aba` ,这样长度为奇数。 1023 | 1024 | 2、像 `abba` ,这样长度为偶数。 1025 | 1026 | 实现过程如下: 1027 | 1028 | ```c++ 1029 | string findLongestPalindrome(string &s) 1030 | { 1031 | const int length=s.size(); 1032 | int maxlength=0; 1033 | int start; 1034 | 1035 | for(int i=0;i=0&&kmaxlength) 1041 | { 1042 | maxlength=k-j+1; 1043 | start=j; 1044 | } 1045 | j--; 1046 | k++; 1047 | } 1048 | } 1049 | 1050 | for(int i=0;i=0&&kmaxlength) 1056 | { 1057 | maxlength=k-j+1; 1058 | start=j; 1059 | } 1060 | j--; 1061 | k++; 1062 | } 1063 | } 1064 | if(maxlength>0) 1065 | return s.substr(start,maxlength); 1066 | return NULL; 1067 | } 1068 | ``` 1069 | 1070 | **Manacher法** 1071 | 1072 | `Manacher` 法只能解决例如 `aba` 这样长度为奇数的回文串,对于 `abba` 这样的不能解决,于是就在里面添加特殊字符。我是添加了 `“#”` ,使 `abba` 变为 `a#b#b#a` 。这个算法就是利用已有回文串的对称性来计算的,具体算法复杂度为 `O(N)` ,我没看出来,因为有两个嵌套的 `for` 循环。 1073 | 1074 | 具体原理利用已知回文串的左半部分来推导右半部分 1075 | 1076 | 首先,在字符串 `s` 中,用 `rad[i]` 表示第i个字符的回文半径,即 `rad[i]` 尽可能大,且满足: 1077 | `s[i-rad[i],i-1]=s[i+1,i+rad[i]]` 1078 | 很明显,求出了所有的rad,就求出了所有的长度为奇数的回文子串. 1079 | 至于偶数的怎么求,最后再讲. 1080 | 假设现在求出了 `rad[1..i-1]` ,现在要求后面的 `rad` 值,并且通过前面的操作,得知了当前字符 `i` 的 `rad` 值至少为j.现在通过试图扩大 `j` 来扫描,求出了 `rad[i]` 。再假设现在有个指针 `k` ,从1循环到 `rad[i]` ,试图通过某些手段来求出 `[i+1,i+rad[i]]` 的 `rad` 值. 1081 | 根据定义,黑色的部分是一个回文子串,两段红色的区间全等. 1082 | 因为之前已经求出了 `rad[i-k]` ,所以直接用它。有 `3` 种情况: 1083 | 1084 | ![Manacher](./figure/Manacher-1.jpg) 1085 | 1086 | ① `rad[i]-krad[i-k] 1095 | 如图,rad[i-k]的范围为青色.因为黑色的部分是回文的,且青色的部分在黑色的部分里面,根据定义,很容易得出:rad[i+k]=rad[i-k].为了方便下文,这里的rad[i+k]=rad[i-k]=min(rad[i]-k,rad[i-k]). 1096 | 1097 | 根据上面两种情况,可以得出结论:当rad[i]-k!=rad[i-k]的时候,rad[i+k]=min(rad[i]-k,rad[i-k]). 1098 | 注意:当rad[i]-k==rad[i-k]的时候,就不同了,这是第三种情况: 1099 | 1100 | ![Manacher](./figure/Manacher-3.jpg) 1101 | 1102 | 如图,通过和第一种情况对比之后会发现,因为青色的部分没有超出黑色的部分,所以即使橙色的部分全等,也无法像第一种情况一样引出矛盾,因此橙色的部分是有可能全等的,但是,根据已知的信息,我们不知道橙色的部分是多长,因此就把 `i` 指针移到 `i+k` 的位置, `j=rad[i-k]` (因为它的 `rad` 值至少为 `rad[i-k]` ),等下次循环的时候再做了. 1103 | 整个算法就这样,至于时间复杂度为什么是 `O(n)` ,我已经证明了,但很难说清楚,所以自己体会吧. 1104 | 上文还留有一个问题,就是这样只能算出奇数长度的回文子串,偶数的就不行.怎么办呢?有一种直接但比较笨的方法,就是做两遍(因为两个程序是差不多的,只是 `rad` 值的意义和一些下标变了而已).但是写两个差不多的程序是很痛苦的,而且容易错.所以一种比较好的方法就是在原来的串中每两个字符之间加入一个特殊字符,再做.如: `aabbaca` ,把它变成 `(#a#a#b#b#a#c#a#)` ,左右的括号是为了使得算法不至于越界。这样的话,无论原来的回文子串长度是偶数还是奇数,现在都变成奇数了. 1105 | 1106 | 测试代码中我没过滤掉 `“#”` 。 1107 | 1108 | ```c++ 1109 | #define min(x, y) ((x)<(y)?(x):(y)) 1110 | #define max(x, y) ((x)<(y)?(y):(x)) 1111 | string findLongestPalindrome3(string s) 1112 | { 1113 | int length=s.size(); 1114 | for(int i=0,k=1;i=0&&i+jmax) 1138 | { 1139 | max=rad[i]; 1140 | center=i; 1141 | } 1142 | } 1143 | return s.substr(center-max,2*max+1); 1144 | 1145 | } 1146 | ``` 1147 | 1148 | #### 13.KMP算法 1149 | 1150 | KMP算法用于字符串匹配, `kmp` 算法完成的任务是:给定两个字符串 `O` 和 `f` ,长度分别为 `n` 和 `m` ,判断f是否在O中出现,如果出现则返回出现的位置。常规方法是遍历a的每一个位置,然后从该位置开始和 `b` 进行匹配,但是这种方法的复杂度是 `O(nm)` 。 `kmp` 算法通过一个 `O(m)` 的预处理,使匹配的复杂度降为 `O(n+m)` 。 1151 | 1152 | 在这里我只能简单的进行个小结,有兴趣的同学可以参考我之前写过的一篇文章[KMP算法学习(详解)](http://www.cnblogs.com/ECJTUACM-873284962/p/6751457.html) 1153 | 1154 | 朴素匹配算法需要两个指针i,j都遍历一遍字符串,故复杂度 `m*n` 1155 | 1156 | `KMP` 算法 `i` 指针不回溯,j指针的回溯参考 `next` 数组,体现了动态规划的思想 1157 | 1158 | 原理如下: 1159 | 1160 | 蓝色表示匹配,红色为失配 1161 | 1162 | ![KMP](./figure/KMP-1.png) 1163 | 1164 | 分析蓝色部分 1165 | 1166 | ![KMP](./figure/KMP-2.png) 1167 | 1168 | 如果存在最长公共前后缀的话,比如这样: 1169 | 1170 | ![KMP](./figure/KMP-3.png) 1171 | 1172 | 就可以在下次匹配的时候用,这样避免了i的回溯 1173 | 1174 | ![KMP](./figure/KMP-4.png) 1175 | 1176 | `next` 数组的意义:当模式匹配串T失效的时候, `next` 数组对应的元素知道应该使用 `T` 串的哪个元素进行下一轮匹配 1177 | 1178 | 实现过程如下: 1179 | 1180 | ```c++ 1181 | void get_next(string T, int *next) 1182 | { 1183 | int i = 1; //后缀 1184 | int j = 0; //前缀 1185 | next[1] = 0; 1186 | while (i < T[0]) //T[0]表示字符串长度 1187 | { 1188 | if (j == 0 || T[i] == T[j]) 1189 | { 1190 | i++; 1191 | j++; 1192 | next[i] = j; 1193 | } 1194 | else 1195 | j = next[j]; 1196 | } 1197 | } 1198 | 1199 | int KMP(string S, string T, int pos) 1200 | { 1201 | int i = pos; //标记主串S下标 1202 | int j = 1; //匹配串下标 1203 | int next[255]; 1204 | get_next(T, next); 1205 | while (i <= S[0] && j <= T[0]) //0位置都放字符串长度 1206 | { 1207 | if (j == 0 || S[i] == T[j]) 1208 | { 1209 | i++; 1210 | j++; 1211 | } 1212 | else 1213 | j = next[j]; //j退回到合适位置,i不用再回溯了 1214 | if (j > T[0]) //如果存在j在匹配完最后一个元素后又++了,所以会大于长度 1215 | return i - T[0]; //i的位置减去匹配串的长度就是匹配串出现的位置 1216 | else 1217 | return 0; 1218 | } 1219 | } 1220 | ``` 1221 | 1222 | 会出现一种特殊情况: 1223 | 1224 | `S = “aaaabcde”` 1225 | 1226 | `T = "aaaaax"` 1227 | 1228 | 这样的话 `next` 数组为 `012345` ,实际上由于前面都是 `a` ,直接调到第一个 `a` 就可以了,期望的 `next` 数组为 `000005` 1229 | 1230 | 这样 `next` 数组构造改为 `12-15` 行 1231 | 1232 | 改进方案: 1233 | 1234 | ```c++ 1235 | void get_next(string T, int *next) 1236 | { 1237 | int i = 1; //后缀 1238 | int j = 0; //前缀 1239 | next[1] = 0; 1240 | while (i < T[0]) //T[0]表示字符串长度 1241 | { 1242 | if (j == 0 || T[i] == T[j]) 1243 | { 1244 | i++; 1245 | j++; 1246 | if (T[i] != T[j]) 1247 | next[i] = j; 1248 | else 1249 | next[i] = next[j]; 1250 | } 1251 | else 1252 | j = next[j]; 1253 | } 1254 | } 1255 | ``` 1256 | 1257 | #### 14.硬币找零问题 1258 | 1259 | 假设有几种硬币,如 `1 5 10 20 50 100` ,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。 1260 | 1261 | 解法: 1262 | 1263 | 用待找零的数值 `k` 描述子结构/状态,记作 `sum[k]` ,其值为所需的最小硬币数。对于不同的硬币面值 `coin[0...n]` ,有 `sum[k] = min(sum[k-coin[0]]` , `sum[k-coin[1]], ...)+1` 。对应于给定数目的找零 `total` ,需要求解 `sum[total]` 的值。 1264 | 1265 | 注意要从前往后算,从后往前算无法保存状态,需要递归,效率很低,就不是动态规划了 1266 | 1267 | ```c++ 1268 | #define MaxNum pow(2,31) - 1 1269 | int main() 1270 | { 1271 | int n; 1272 | while (cin >> n) 1273 | { 1274 | vector c(n + 1, 0); 1275 | for (int i = 1; i <= n; i++) 1276 | { 1277 | if (i == 1 || i == 5 || i == 10 || i == 20 || i == 50 || i == 100) 1278 | { 1279 | c[i] = 1; 1280 | continue; 1281 | } 1282 | int curMin = MaxNum; 1283 | if (i - 1 > 0) 1284 | curMin = c[i - 1] < curMin ? c[i - 1] : curMin; 1285 | if (i - 5 > 0) 1286 | curMin = c[i - 5] < curMin ? c[i - 5] : curMin; 1287 | if (i - 10 > 0) 1288 | curMin = c[i - 10] < curMin ? c[i - 10] : curMin; 1289 | if (i - 20 > 0) 1290 | curMin = c[i - 20] < curMin ? c[i - 20] : curMin; 1291 | if (i - 50 > 0) 1292 | curMin = c[i - 50] < curMin ? c[i - 50] : curMin; 1293 | if (i - 100 > 0) 1294 | curMin = c[i - 100] < curMin ? c[i - 100] : curMin; 1295 | c[i] = curMin + 1; 1296 | } 1297 | cout << c[n] << endl; 1298 | } 1299 | system("pause"); 1300 | return 0; 1301 | } 1302 | ``` 1303 | 1304 | #### 15.找平方个数最小 1305 | 1306 | 给一个正整数 `n` , 找到若干个完全平方数(比如 `1, 4, 9, ... ` )使得他们的和等于 `n` 。你需要让平方数的个数最少。 1307 | 给出 `n = 12` , 返回 `3` 因为 `12 = 4 + 4 + 4` 。 1308 | 给出 `n = 13`, 返回 `2` 因为 `13 = 4 + 9`。 1309 | 1310 | 实现过程如下: 1311 | 1312 | ```c++ 1313 | int findMin(int n) 1314 | { 1315 | int *result = new int(n + 1); 1316 | result[0] = 0; 1317 | for (int i = 1; i <= n; i++) 1318 | { 1319 | int minNum = i; 1320 | for (int j = 1;; j++) 1321 | { 1322 | if (i >= j * j) 1323 | { 1324 | int tmp = result[i - j*j] + 1; 1325 | minNum = tmp < minNum ? tmp : minNum; 1326 | } 1327 | else 1328 | break; 1329 | } 1330 | result[i] = minNum; 1331 | } 1332 | return result[n]; 1333 | } 1334 | 1335 | int main() 1336 | { 1337 | int n; 1338 | while (cin >> n) 1339 | cout << findMin(n) << endl; 1340 | } 1341 | ``` 1342 | 1343 | #### 16.N*N方格内的走法问题 1344 | 1345 | 有一个 `n*n` 的方格,从左上角走到右下角有多少种最短路径的走法? 1346 | 1347 | 若不加最短路径则 `n^(n-1)` 种走法。加上了最短路径就是说横向的距离为 `n-1` ,纵向的距离为 `n-1` ,总共的距离是 `2(n-1)` 步走到。 1348 | 1349 | ![squared-problem](./figure/squared-problem.png) 1350 | 1351 | 实现过程如下: 1352 | 1353 | ```c++ 1354 | int main() 1355 | { 1356 | int n; 1357 | while (cin >> n) 1358 | { 1359 | vector> dp(n+1, vector(n+1, 1)); 1360 | for (int i = 1; i <= n;i++) 1361 | { 1362 | for (int j = 1; j <= n;j++) 1363 | { 1364 | dp[i][j] = dp[i][j - 1] + dp[i - 1][j]; 1365 | } 1366 | } 1367 | cout << dp[n][n] << endl; 1368 | } 1369 | } 1370 | ``` 1371 | 1372 | #### 17.楼层抛珠问题 1373 | 1374 | 某幢大楼有 `100` 层。你手里有两颗一模一样的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。这幢大楼有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。 1375 | 1376 | 例如:有这样一种方式,第一次选择在 `60` 层扔,若碎了,说明临界点在 `60` 层及以下楼层,这时只有一颗珠子,剩下的只能是从第一层,一层一层往上实验,最坏的情况,要实验 `59` 次,加上之前的第一次,一共 `60` 次。若没碎,则只要从 `61` 层往上试即可,最多只要试 `40` 次,加上之前一共需 `41` 次。两种情况取最多的那种。故这种方式最坏的情况要试 `60` 次。仔细分析一下。如果不碎,我还有两颗珠子,第二颗珠子会从 `N+1` 层开始试吗?很显然不会,此时大楼还剩 `100-N` 层,问题就转化为 `100-N` 的问题了。 1377 | 1378 | 那该如何设计方式呢? 1379 | 1380 | 根据题意很容易写出状态转移方程:`N` 层楼如果从 `n` 层投下玻璃珠,最坏的尝试次数是:![clip-problem](./figure/clip-problem-1.png) 1381 | 1382 | 那么所有层投下的最坏尝试次数的最小值即为问题的解:![clip-problem](./figure/clip-problem-2.png)。其中 `F(1)=1` 。 1383 | 1384 | 实现过程如下: 1385 | 1386 | ```c++ 1387 | int max(int a, int b) 1388 | { 1389 | return (a > b)? a : b; 1390 | } 1391 | 1392 | int dp[101]; 1393 | //N<=100; 1394 | int floorThr(int N) 1395 | { 1396 | for (int i = 2; i <= N; i++) 1397 | { 1398 | dp[i] = i; 1399 | for (int j = 1; j pAEnd) 1449 | { 1450 | if (pBBegin > pBEnd) 1451 | return 0; 1452 | else 1453 | return pBEnd - pBBegin + 1; 1454 | } 1455 | if (pBBegin > pBEnd) 1456 | { 1457 | if(pABegin > pAEnd) 1458 | return 0; 1459 | else 1460 | return pAEnd - pABegin + 1; 1461 | } 1462 | if (strA[pABegin] == strB[pBBegin]) 1463 | { 1464 | return calStringDis(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd); 1465 | } 1466 | else 1467 | { 1468 | int t1 = calStringDis(strA,pABegin+1,pAEnd,strB,pBBegin+2,pBEnd); 1469 | int t2 = calStringDis(strA,pABegin+2,pAEnd,strB,pBBegin+1,pBEnd); 1470 | int t3 = calStringDis(strA,pABegin+2,pAEnd,strB,pBBegin+2,pBEnd); 1471 | 1472 | return minValue(t1,t2,t3)+1; 1473 | } 1474 | } 1475 | ``` 1476 | 1477 | 在递归的过程中,有些数据被重复计算了。 1478 | 1479 | 很经典的可使用动态规划方法解决的题目,和计算两字符串的最长公共子序列相似。 1480 | 1481 | 设 `Ai` 为字符串 `A(a1a2a3 … am)` 的前i个字符(即为 `a1,a2,a3 … ai` ) 1482 | 设 `Bj` 为字符串 `B(b1b2b3 … bn)` 的前 `j` 个字符(即为 `b1,b2,b3 … bj` ) 1483 | 1484 | 设 `L(i,j)` 为使两个字符串和 `Ai` 和 `Bj` 相等的最小操作次数。 1485 | 当 `ai==bj` 时 显然 `L(i,j) = L(i-1,j-1)` 1486 | 当 `ai!=bj` 时: 1487 | 1488 | - 若将它们修改为相等,则对两个字符串至少还要操作 `L(i-1,j-1)` 次 1489 | - 若删除 `ai` 或在 `bj` 后添加 `ai` ,则对两个字符串至少还要操作 `L(i-1,j)` 次 1490 | - 若删除 `bj` 或在 `ai` 后添加 `bj` ,则对两个字符串至少还要操作 `L(i,j-1)` 次 1491 | 1492 | 此时 `L(i,j) = min( L(i-1,j-1), L(i-1,j)` , ` L(i,j-1) ) + 1 ` 1493 | 1494 | 显然,`L(i,0)=i` ,`L(0,j)=j` , 再利用上述的递推公式,可以直接计算出 `L(i,j)` 值。 1495 | 1496 | 代码实现如下: 1497 | 1498 | ```c++ 1499 | int minValue(int a, int b, int c) 1500 | { 1501 | int t = a <= b ? a:b; 1502 | return t <= c ? t:c; 1503 | } 1504 | 1505 | int calculateStringDistance(string strA, string strB) 1506 | { 1507 | int lenA = (int)strA.length()+1; 1508 | int lenB = (int)strB.length()+1; 1509 | 1510 | int **c = new int*[lenA]; 1511 | for(int i = 0; i < lenA; i++) 1512 | c[i] = new int[lenB]; 1513 | 1514 | for(int i = 0; i < lenA; i++) c[i][0] = i; 1515 | for(int j = 0; j < lenB; j++) c[0][j] = j; 1516 | c[0][0] = 0; 1517 | for(int i = 1; i < lenA; i++) 1518 | { 1519 | for(int j = 1; j < lenB; j++) 1520 | { 1521 | if(strB[j-1] == strA[i-1]) 1522 | c[i][j] = c[i-1][j-1]; 1523 | else 1524 | c[i][j] = minValue(c[i][j-1], c[i-1][j], c[i-1][j-1]) + 1; 1525 | } 1526 | } 1527 | 1528 | int ret = c[lenA-1][lenB-1]; 1529 | 1530 | for(int i = 0; i < lenA; i++) 1531 | delete [] c[i]; 1532 | delete []c; 1533 | 1534 | return ret; 1535 | } 1536 | ``` 1537 | 1538 | #### 19.N皇后问题 1539 | 1540 | `N` 皇后问题是一个以国际象棋为背景的问题:如何能够在 `NxN` 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。 1541 | 1542 | 我们可以通过下面的图标来展示回溯法的过程 1543 | 从而更加有助于我们的理解 1544 | 1545 | 我们以4x4为例: 1546 | ![N-empress](./figure/N-empress.png) 1547 | 1548 | 我们在试探的过程中,皇后的放置需要检查他的位置是否和已经放置好的皇后发生冲突,为此需要以及检查函数来检查当前要放置皇后的位置,是不是和其他已经放置的皇后发生冲突 1549 | 1550 | 假设有两个皇后被放置在`(i,j)`和`(k,l)`的位置上,明显,当且仅当 `|i-k|=|j-l| ` 时,两个皇后才在同一条对角线上。 1551 | 1552 | 1) 先从首位开始检查,如果不能放置,接着检查该行第二个位置,依次检查下去,直到在该行找到一个可以放置一个皇后的地方,然后保存当前状态,转到下一行重复上述方法的检索。 1553 | 1554 | 2) 如果检查了该行所有的位置均不能放置一个皇后,说明上一行皇后放置的位置无法让所有的皇后找到自己合适的位置,因此就要回溯到上一行,重新检查该皇后位置后面的位置。 1555 | 1556 | ```c++ 1557 | int PLACE(int n) /* 检查当前列能否放置皇后 */ 1558 | { 1559 | //queen[i] == queen[n]用于保证元素不能再同一列 1560 | //abs(queen[i] - queen[n]) == abs(n - i)用于约束元素不能再同一行且不能再同一条斜线上 1561 | int i; 1562 | for(i = 0; i < n; i++) /* 检查横排和对角线上是否可以放置皇后 */ 1563 | { 1564 | if(queen[i] == queen[n] || abs(queen[i] - queen[n]) == abs(n - i)) 1565 | { 1566 | return 0; 1567 | } 1568 | } 1569 | return 1; 1570 | } 1571 | ``` 1572 | 1573 | 具体的实现代码如下: 1574 | 1575 | ```c++ 1576 | #define max 4 1577 | //sum用于描述解的可能的个数,每当输出一次复合要求的位置 1578 | //sum的数量就会被+1 1579 | int queen[max], sum=0; /* max为棋盘最大坐标 */ 1580 | 1581 | void show() /* 输出所有皇后的坐标 */ 1582 | { 1583 | int i; 1584 | printf("("); 1585 | //i代表行数,queen[i]代表当前行元素所处的列数, 1586 | //注意此处下标是从0开始的 1587 | 1588 | for(i = 0; i < max; i++) 1589 | { 1590 | printf(" %d", queen[i]+1); 1591 | } 1592 | printf(")\n"); 1593 | //每次输出一种解的时候,那么他的解的数量就会增加1 1594 | sum++; 1595 | } 1596 | 1597 | //此函数用于判断皇后当前皇后是否可以放在此位置 1598 | int PLACE(int n) /* 检查当前列能否放置皇后 */ 1599 | { 1600 | //queen[i] == queen[n]用于保证元素不能再同一列 1601 | //abs(queen[i] - queen[n]) == abs(n - i)用于约束元素不能再同一行且不能再同一条斜线上 1602 | int i; 1603 | for(i = 0; i < n; i++) /* 检查横排和对角线上是否可以放置皇后 */ 1604 | { 1605 | if(queen[i] == queen[n] || abs(queen[i] - queen[n]) == abs(n - i)) 1606 | { 1607 | return 0; 1608 | } 1609 | } 1610 | return 1; 1611 | } 1612 | 1613 | //核心函数,回溯法的思想 1614 | void NQUEENS(int n) /* 回溯尝试皇后位置,n为横坐标 */ 1615 | { 1616 | int i; 1617 | for(i = 0; i < max; i++) 1618 | { 1619 | //首先将皇后放在第0列的位置,对于第一次来说是肯定成立的 1620 | //所以第一次将皇后放在第0行0列的位置 1621 | queen[n] = i; /* 将皇后摆到当前循环到的位置 */ 1622 | if(PLACE(n)) 1623 | { 1624 | if(n == max - 1) 1625 | { 1626 | show(); /* 如果全部摆好,则输出所有皇后的坐标 */ 1627 | } 1628 | else 1629 | { 1630 | NQUEENS(n + 1); /* 否则继续摆放下一个皇后 */ 1631 | } 1632 | } 1633 | } 1634 | } 1635 | 1636 | int main() 1637 | { 1638 | NQUEENS(0); /* 从横坐标为0开始依次尝试 */ 1639 | printf("\n"); 1640 | printf("总共的解法有%d种\n", sum); 1641 | 1642 | return 0; 1643 | } 1644 | ``` 1645 | 1646 | ### 习题练习推荐 1647 | 1648 | - N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击),求出有多少种合法的放置方法。输出N皇后问题所有不同的摆放情况个数。---**九度OJ1254** 1649 | - 将一堆正整数分为 `2` 组,要求2组的和相差最小.---**51Nod 1007** 参考题解在这里:[http://www.cnblogs.com/ECJTUACM-873284962/p/7572069.html](http://www.cnblogs.com/ECJTUACM-873284962/p/7572069.html),关于01背包问题更多题目推荐参考这里:[http://www.cnblogs.com/ECJTUACM-873284962/category/997032.html](http://www.cnblogs.com/ECJTUACM-873284962/category/997032.html) 1650 | - 符号三角形的 第 `1` 行有 `n` 个由 `“+”` 和 `”-“` 组成的符号 ,以后每行符号比上行少 `1` 个, `2` 个同号下面是 `”+“` , `2` 个异 号下面是 `”-“ ` 。计算有多少个不同的符号三角形,使其所含 `”+“ ` 和 `”-“ ` 的个数相同 。---**hdu2510** 1651 | - 给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到, 而且每个字符的先后顺序和原串中的先后顺序一致。---**POJ1458** 1652 | - 求出最长上升子序列的长度。---**百练2757** 1653 | - 从三角形顶部数字走,每次只能走到这个数字的左下角或者右下角的数字,直到底部,计算走过的线路的数字之和,求这个和的最大值。---**POJ1163**。 1654 | - 给出两个串,分别为 `a` , `b` ,问 `a` 串在 `b` 串中出现了几次?(其实位置不同,就算不同的串)。---**hdu1686** 1655 | 1656 | 更多题目推荐未来待续更新 1657 | 1658 | ### 参考文献 1659 | 1660 | - 动态规划百度百科:https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/529408 1661 | - 回溯算法入门及经典案例剖析(初学者必备宝典):http://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html 1662 | - 动态规划:http://www.doc88.com/p-6408257243193.html 1663 | - 麻省理工学院公开课:算法导论 ---动态规划,最长公共子序列:http://open.163.com/movie/2010/12/L/4/M6UTT5U0I_M6V2U1HL4.html 1664 | 1665 | ### GitHub 1666 | 1667 | 本项目会持续更新我对于算法的一些理解以及收集一些优质的题目和writeup,欢迎大家star and fork,一起来打造这个学习的知识体系宝库。 1668 | 1669 | GitHub:[https://github.com/AngelKitty/Algorithm](https://github.com/AngelKitty/Algorithm) -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-1.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-2.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-3.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-4.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-5.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-6.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Binary-search-tree-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Binary-search-tree-7.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/DP-convex-triangle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/DP-convex-triangle.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/DP-solved-LCS-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/DP-solved-LCS-1.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/DP-solved-LCS-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/DP-solved-LCS-2.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Euclid-problem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Euclid-problem.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/KMP-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/KMP-1.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/KMP-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/KMP-2.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/KMP-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/KMP-3.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/KMP-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/KMP-4.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/LIS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/LIS.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Manacher-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Manacher-1.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Manacher-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Manacher-2.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Manacher-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Manacher-3.jpg -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/Maximum-disjoint-subsection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/Maximum-disjoint-subsection.gif -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/N-empress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/N-empress.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/clip-problem-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/clip-problem-1.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/clip-problem-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/clip-problem-2.png -------------------------------------------------------------------------------- /docs/Dynamic-programming/figure/squared-problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngelKitty/Algorithm/464bae9bdbaeaa0d6ad502af012f2c8f007de460/docs/Dynamic-programming/figure/squared-problem.png --------------------------------------------------------------------------------