《王道机试指南》刷题总结

第一章(略)

第二章 暴力求解

  • 习题2.1 注意审题,给的是有关的概念,求的是无关的平方和;
  • 习题2.2 注意审题,强调总的购买数是100只,并不在意是否要尽可能花光给的钱;
  • 习题2.10 路径打印:多叉树的插入和遍历
  • 习题2.11 思路分析:坠落的蚂蚁

第三章 排序与查找

  • 例题3.2 成绩排序 注意C++与C中定义结构体类型的区别;
  • 习题3.2 sort(arr,arr+n)的第二个参数时数组的结束地址,而不是最后一个元素的起始地址,即:n的值是元素的个数,不是最后一个元素的序号不是元素个数减1;
  • 习题3.7 查找://本题重要思想:为了避免统计过的字母在后续的循环中重复被统计,把统计过的位置上的字母变为’*’,从而避开统计;即查找过程中也是可以改动查找对象的,不要拘泥于“不能改动原对象”

第四章 字符串

  • 例题 4.2 读取一行字符串:getline(cin,str);
  • 习题 4.2 string:swap 交换本字符串和参数的字符串
  • 习题 4.4 浮点数相加
  • 习题 4.5 对vector类型数据 进行排序:
    • vector str;
    • sort(str.begin(),str.end());
  • 例题 4.6 kmp算法;
  • 习题 4.6 cctype库里的 isalpha()判断是否是字母、tolower()得到字母的小写等函数

第五章 数据结构一

  • 例题 5.6 cctype库里的 isdigit()判断字符是否是数字
  • 习题 5.1 getchar()函数的使用要注意,他能读取任何字符,包括换行符和空格,注意和scanf、cin的区别;

第六章 数学问题

大整数的各类运算符的重载不能在其他定义的新的结构体中使用!

  • 例题6.2 进制转化
    • 字符串的取模运算:等同于对最低位取模运算,与正常取模功能等同;
    • 字符串的除法功能:同纸上做除法,从高到低逐位除以除数,不能整除则保留余数,余数乘以10位和低位一起进行处理,模拟除法效果;这种做法存在前置多出0的情况,因此计算完成后,需要取首个非0之后的字符串。
  • 例题6.3 二进制转十进制:十进制转二进制的逆运算->字符串乘法+字符串加法
    • 十进制转二进制是取模运算得到余数(二进制的低位)再除以除数得到商,这是一轮,经过一轮,最后除到商为0;那么二进制转十进制应该把上述运算逆转过来:商(初始的商为0,因为十进制转二进制时最后商除到了0)乘以原来的除数再加上余数(二进制的高位)得到上一轮除法运算的商,循环相乘和加余数,一直到把余数加完即是原来的十进制数。
    • 进制转换总结:十进制转其他进制,用除法和取模;其他进制转十进制,用乘法和加法。
  • 例题6.5 最大公约数:欧几里得算法
  • 例题6.6 最小公倍数:两个数的最小公倍数为两数的乘积除以最大公约数
  • 例题6.8 素数筛法:用于统计较大范围内的素数
    • 命题一:若一个数不是素数,则必然存在一个小于它的素数为其因数
    • 命题二:若已经获得了小于一个数的所有素数,则只需确定这个数不能被这些素数所整除,那么这个数即为素数。
    • 解决方案:找到一个素数,然后把它在给定范围内的所有倍数标记为非素数,当遍历到某个数时,若它未被任何小于它的素数因为是其倍数而标记为非素数时,这个数即可判定为素数。
    • 小技巧:为什么素数筛法如果i是素数,标记非素数要从ii开始,而不从i2开始?这是因为:ik(k<i)必定已经在求得k的某个素因数时被标记过,即ik同时也是k的素因数的倍数。所以直接从i*i开始标记,避免重复标记从而提高效率。
  • 例题6.9 质因数的个数:对于一个数n(1<n<10^9),求它的质因数个数
    • 小技巧:求素数时只需筛到MAXN=sqrt(10^9)+1,为什么?因为对于数n,它的大于sqrt(n)的质因数显然最多只有一个。如果n确实存在大于sqrt(n)的质因数,那么我们在不断除以n的其他的质因数后,最后剩下的一定是这个唯一的大于sqrt(n)的质因数,因此不必要求到整个范围内的素数。
  • 习题6.8 整除问题 通过质因子的幂指数关系来判定两者能否整除以及能够循环整除多少次;
  • 习题6.9 快速幂:积的模等于模的积+二分求幂+数学证明

第七章 贪心策略

  • 贪心策略的应用范围:

    • 遇到求最大、最小、最多等最值问题时,应优先考虑是否能够用贪心策略求解。
    • 若问题满足最优子结构性质,即该问题具备无后效性,则全局的最优解可由求子问题的最优解得到。
    • 无法通过贪心策略求解的最优化问题,通常要用到动态规划。
  • 习题7.1 此题使用贪心策略的合理性证明见程序注释。

  • 例题7.4 区间贪心:从众多区间选取最多的两两互不相交的区间

  • 例题7.5 难题!!!

  • 习题7.2 今天琢磨一下午加晚上了

  • 启发:贪心问题 首先把问题规模缩小,找寻局部最优解,之后扩大数据规模。

第八章 递归与分冶

  • 全排列 next_permutation(str.begin(), str.end()),每循环使用一次循环该函数,就会返回下一个排列,直到指空。习题
  • 习题8.2 此题不使用全排列的话,办法较复杂
  • 例题8.4 利用分治法
  • 习题8.3 获取某个数的某个二进制位,不需要使用除K取余法存入数组这么麻烦,只需要用右移运算符加上按位与运算符即可获得:n>>i&&1;由此即可获得第i位(从高到低)的二进制位是否为1;
  • 习题9.1 玛雅人的密码:
    • C++中find函数的运用可直接运用于字符串的匹配,不需要自己写kmp函数;
    • 关于map<string,int> 的使用,可以简化很多步骤

第九章 BFS和DFS(略)

第十章 数据结构二

一、二叉树遍历和二叉树搜索

  • 如何判断两个序列同属一个二叉树:通过前序或后序遍历结果是否相同判定,原因如下:
    a. 对于任意二叉树而言,前序遍历和中序遍历唯一确定一课二叉树;
    b. 而对于二叉排序树而言,只要元素都相同,那么由这些元素构成的任意二叉排序树的中序遍历都一定是一样的,结果都是元素按大小有序的排列;
    c. 因此要区分相同元素不同序列构成的二叉排序树是否是同一棵,中序遍历并不能区分,因为一定都是一样的。但如果不属于同一棵二叉排序树,他们的前序或者后序遍历结果一定不一样,因为如果一样,那么根据前序和中序唯一确定一棵二叉树的理论,他们将属于同一棵二叉树,与前设矛盾。

二、优先队列的使用和哈弗曼树的构造

三、map的使用
1. map的最后一个元素的迭代器:map.rbegin();

第十一章 图论

一、邻接矩阵、邻接表的概念

二、并查集:常用来判断图是否为连通图,或用来求图的联通分量。

  • 初始化(Initial):把每个结点的父亲赋成自己
  • 查找(Find):用于判断两个元素是否属于同一集合
  • 合并(Union):总是将高度较低的树作为高度较高的树的子树进行合并
  • 路径压缩:优化树结构,为后续查找工作节约了大量时间
    • 习题11.1:
      i. 建立二叉树不一定用链表,也可以用数组,这样访问中间的元素可以直接访问,不需要遍历整棵二叉树,可以解决一些特殊的问题。此题既需要运用到并查集的方法,但是又因为一个结点对应两个父节点,因此线性结构已不能满足,必须要用二叉树来表现这种结构,又因为需要用到并查集,因此选择用数组存储的二叉树来解决问题!!!
      ii. 以上用遍历的方法解,事实上此题还是用并查集解法最简单,此处稍微需要灵活一点,不要记录谁是谁的父母,虽然题目给的这样,但在我们处理数据时,我们应该倒过来,记录谁是谁的儿子,这样才能用并查集。题目给出的是一棵倒着的树,我们要把他变成正着的树。

三、最小生成树(Minumum Spanning Tree,MST)

  • 定义:包含原图中所有顶点和部分边,,且这个子图不存在回路,所有生成树中边权和最小的即为最小生成树。
  • 两类算法的区别:
    • 普里姆算法:是不断从剩下的顶点之中选取一个加入MST的顶点集合,要求选取的顶点和当前集合的顶点之间的边权值最小。
    • 克鲁斯卡尔算法:不断从剩下的边的集合中选取边权值最短的边,要求这个边的两个顶点分属不同的集合。

四、单源最短路径——Dijkstra算法

  • 关于图的构造:邻接表:顶点+边
  • 设置一个数为无穷大:const int INF = INT_MAX;
  • fill(first,last,val):把目标单元赋成指定的值;
  • 使用此算法时,图不能是负权图。

五、拓扑排序

六、关键路径

  • 事件的最早开始时间:所有先序活动中最晚完成的活动的时间;
  • 事件的最晚开始时间:所有后续活动中最早开始的时间减去本活动的时间;

补充:关于王道机试指南习题11.6 上海交通大学复试上机题解:最短路径

第十二章 动态规划

一、递推求解

二、最大连续子序列和

  • 一维
  • 二维

三、最长递增子序列(Longest Increasing Subsequence,LIS):求给定序列的所有递增子序列中最长的那个子序列长度

  • 令dp[i]表示以A[i]作为末尾的最长递增子序列的长度;
    • 情形一:A[i]之前的元素都比A[i]大,即最长递增子序列只有A[i]本身,即dp[i]=1;
    • 情形二:A[i]之前存在比A[i]小的元素A[j],那么dp[i]=max(dp[j]+1|j<i&&A[j]<A[i])
      • 此处注意,dp[i]并不一定等于最近的位置j(满足A[j]<A[i])的元素的dp值加1,而是等于前序所有满足A[j]<A[i]的具有最大的dp值的dp[j]+1。
      • 举例:1 2 4 7 8 3 9.对于dp[6]来说,离它最近满足条件的是A[5]=3,dp[5]=3,如果按最近的处理,那么dp[6]=4,序列为1 2 3 9,这显然是错误的。正确的dp[6]=dp[4]+1=6,序列为1 2 4 7 8 9
  • 如此得到状态转移方程:dp[i]=max{1,dp[j]+1||j<i&&A[j]<A[i]},时间复杂度为O(n^2)

四、最长公共子序列(Longest Common Subsequence,LCS)

五、揹包问题(状态转移方程详解见机试指南P237,P240,P242)

  1. 0-1揹包:每种物品至多只能选择一件,在揹包中该物品的数量只有0和1两种情况;
    • 内循环从大到小至j>=weight[i]
    • 为什么贪心策略不能解决0-1揹包问题?
    • 答:贪婪准则:价值vi,质量wi,每一项计算ri=vi/wi,即价值和质量之比,再按比值的降序来排序,从第一项开始装揹包,然后是第二项,依次类推,尽可能的多放,直到装满揹包。这种策略并不能保证得到最优解。利用此策略试解n= 3 ,w=[40,35,35], v=[40,30,20], c=70 时的最优解。直觉上它可能是对的,得到的结果是选择w[0]=40,剩余30的空间容量不能装下其他物品,总价值量为40。很显然,这是错误的,真正可以获得的最大价值是30+20=50。事实上这一方法只是解决普通揹包问题的最优解,因为按此方法在选择物品i装入揹包时,我们是可以选择物品i的一部分的,不一定要全部装入揹包。但是实际上0-1揹包问题的特点就是每个个体是不可拆分的,那么此时再按贪心策略就不能证明结果是正确的,相反我们可以举出反例证明它是错误的。这两者的关系其实有点类似于概率论中古典概型和几何概型问题的区分。对于一个方法是否正确,有时我们虽然不能直接通过理论证明它是正确的或是不正确,但如果是错误的,一定可以通过反例证明。理论不能证明正确的方法一般就不要使用,因为有可能存在反例使它出现错误的结果。而对于动态规划的解法,它的本质是枚举,这个方法无疑是证明正确的。
    1. 0-1揹包变体求最小值,初始值一般设为INF=INT_MAX/10;
    2. 完全揹包,揹包中每类物品数量无线,循环时从小到大开始,从j=weight[i]到m为止。
    3. 多重揹包:即每种物品的数量既不是有限的,也不是只能取一次,而是有限个k次。
      a. 解决办法:转化为0-1揹包问题,每种物品均视为k种重量和价值都相同的不同物品,如此时间复杂度为O(m*∑24_(i=0)^n▒k_i ),但如此复杂度较高。
      b. 进一步优化,将一类物品拆分为若干组,将每组物品视为一件物品,价值和重量为该组所有物品的价值重量总和。每组物品包含的原物品的个数分别为20,21,……,2(c−1),k-2c+1,其中c是使得k-2c+1≥0的最大整数。这种类似于二进制的拆分,不仅将物品数量大大降低,同时通过由若干原物品得到新物品的不同组合,可以得到0到k之间的任意件物品的价值重量和,所以对所有这些新物品做0-1揹包,即可得到多重揹包的解。

六、其他问题

附:动态规划:最长递增子序列问题的时间复杂度由O(n^2)降低为O(nlogn)的改良算法的自我理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章