OpenJudge 刷题思路汇总

模拟

  • lc1424. 对角线遍历 II:如果按照这个顺序模拟一遍,遇到是空的话就跳过,但这样会超时。其实对角线上横纵座标和是一个定值,利用这个性质,可以正常遍历 nums,然后把每个元素加到一个二维数组中(第一维是 x + y),因为加进去的时候是倒序的,所以最后再倒序遍历一下即可

双指针

  • lc 面试题57. 和为s的连续正数序列双指针 滑动窗口 i=1,j=1i = 1, j = 1, ii 先动,如果 sum>targetsum > target了,那当前的 j 与后面的 ii 之间的区间和也一定超过 sumsum,所以 jj 可以必须往后走了,直到 sum<=targetsum <= target 才停下来,接着是具体问题的逻辑,最后再动 ii。时间复杂度:O(n)O(n) 数学公式推导 也可以用类似 lc 829. 连续整数求和 的方法去求数学公式 时间复杂度:O(n)O(\sqrt n)
  • lc 3. 无重复字符的最长子串:跟“和为s的连续正数序列”这题思路完全一样,都是用两个指针去维护一个滑动窗口

二分

  • lc374. 猜数字大小 + KickStart NumberGuessing + eoj3342. 经典的猜数字游戏交互题 猜数字游戏的本质就是个二分,题目会有一个数 pick,然后你在一个区间 [1,n][1, n] 中二分查找这个数。由于你实现不知道 pick 是多少,所以需要出题人告诉你这个数大了还是小了,还是猜中了(guess 函数的功能),这部分相当于二分中的 check 函数。注意:虽然范围是 [1,n][1,n],但用这范围如果出题人的数是最后一个,那最后一次会还没有猜就退出循环,所以保险起见最好用 while(true)while(true)

  • lc LCP 12. 小张刷题计划:最大值最小化,二分答案常用套路

动态规划

  • lc 300.最长上升子序列动态规划 O(n2)O(n^2)f[i]f[i] 表示以第 i 个数为结尾的上升子序列的集合,属性(最大值) 。贪心+二分 O(nlogn)O(nlogn) 对于一个上升子序列,其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。tail[i]tail[i] 表示长度为 i 的所有最长上升子序列结尾的最小值。具体地,每次来一个新的数 num,利用二分找到最后一个小于 num 的位置 l,然后让 tail[l+1]=numtail[ l + 1] = num,这样新的数有更多可能性接在 tail[l+1]tail[l+1]的后面

  • ac1024. 装箱问题01揹包问题 把物品的体积同时看做体积和价值,问题转化为在不超过揹包最大容量的情况下,最大价值是多少。由于 N=2e4N = 2e4,所以必须对代码进行等价变形,降低空间复杂度

  • ac900. 整数划分将 n 划分为若干个正整数之和的划分方案数 把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的揹包的总方案数。由推导得f[i][j]=f[i1][j]+f[i][ji]f[i][j] = f[i - 1][j] + f[i][j - i],注意初值 f[i][0]=1f[i][0] = 1(全不选也是一种方案),再对这公式进行等价变形。拓展: 将 n 划分为最大数不超过 k 的划分方案数(完全揹包返回前 k 种物品的方案数f[k][n]f[k][n])

  • 百炼 4119:复杂的整数划分问题

  1. N划分成K个正整数之和的划分数(状态转移难)
    状态表示:f[i][j]f[i][j]表示用 i 个物品(可重复),且恰好拼成j的方案数
    状态计算:此题状态转移比较难,f[i][ji]f[i][j - i]表示用i个物品(可重复)且每个物品都大于1(同时减1),f[i1][j1]f[i - 1][j - 1]表示用i个物品(可重复)且至少有一个物品是1(减去这个1)
    边界设置:只要设置f[0][0]=1f[0][0] = 1,则所有状态都能被算出来
  2. N划分成不同正整数之和的划分数(01揹包)
    状态表示:f[i][j]f[i][j]表示只用前i种物品(最多一次),恰好拼成j的方案数
    状态计算:f[i1][j]+f[i1][ji]f[i - 1][j] + f[i - 1][j - i](这两个集合不一定存在)
    边界设置:只要设置f[0][0]=1f[0][0] = 1,则所有状态都能被算出来
  3. N划分成若干奇正整数之和的划分数 (完全揹包)
    状态表示:f[i][j]f[i][j]表示只用前i种物品(只选奇数),恰好拼成j的方案数
    状态计算:f[i2][j]+f[i][ji]f[i - 2][j] + f[i][j - i] (这两个集合不一定存在)
    边界设置:只要设置f[1][0]=1f[1][0] = 1,则所有状态都能被算出来
  • ac 1307. 牡牛和牝牛:状态表示:f[i]表示符合题目要求的 i 头牛的排列方式,状态计算:根据第 i 头牛是"0"还是"1"去划分,边界设置:设置f[0] = 1后,f[1]~f[n]都能被计算出

  • lc 1218. 最长定差子序列:这题用DP四部曲分析,思路会很清晰,但在状态表示时用f[i]f[i] 表示所有以第 i 个数结尾,公差为 d 的子序列,复杂度 O(n2)O(n^2) 会超时,而状态表示用 f[i]f[i] 表示以 i 结尾,公差为 d 的所有子序列就可以优化时间复杂度,另外注意 i 可能是负数,所以要用 map

  • ac282. 石子合并 区间DP专题 核心:最后一次合并一定是左边连续的一部分和右边连续的一部分进行合并。
    状态表示:f[i][j]f[i][j] 表示将 iijj 合并成一堆的方案的集合,属性 Min。
    状态计算:
    i<ji < j 时,f[i][j]=minikj1{f[i][k]+f[k+1][j]+s[j]s[i1]}f[i][j] = \min\limits_{i\leq k \leq {j - 1}}\{f[i][k]+f[k+1][j] + s[j] -s[i - 1]\}
    i=ji = j 时, f[i][i]=0f[i][i] = 0 (合并一堆石子代价为 0)
    问题答案: f[1][n]f[1][n]
    所有的区间dp问题,第一维都是枚举区间长度,一般 len = 1 用来初始化,枚举从 len = 2 开始,第二维枚举起点 i (右端点 j 自动获得,j = i + len - 1)。其实也可以按左端点倒着枚举,因为只要保证状态转移所依赖的状态被提前计算出来即可 参考这里

  • lc 312. 戳气球区间DP 状态表示:f[i][j]f[i][j] 表示戳破 (i,j)(i,j) 之间所有气球的集合,属性(最大值)。这题的状态表示很巧妙,用到了开区间。状态计算:设 i 和 j 之间最后一个被戳破的气球为 k,那此时 (i,k)(i,k)(k,j)(k,j) 之间的气球已被戳破,最后 (i,j)(i,j) 之间只剩下气球 k ,相邻的就是气球 i 和 j
    ji>=2j - i >= 2时,f[i][j]=f[i][k]+f[k][j]+p[i]p[k]p[j]f[i][j] = f[i][k] + f[k][j] + p[i]*p[k]*p[j]
    状态穷举,最重要的一点是状态转移时用到的状态都已被提前计算出来,这题含有图解


  1. 状态表示(由经验所得):f[i][j]f[i][j]表示所有A[1,...,i]A[1,...,i]B[1,...,j]B[1,...,j]的公共子序列的集合,属性(最大值)
  2. 状态计算:划分依据 a[i]a[i]b[j]b[j] 是否相同
    f[i][j]={f[i1][j1]+1if (a[i]==b[j])max(f[i1][j],f[i][j1])if (a[i]!=b[j]) f[i][j] = \left\{ \begin{aligned} &f[i-1][j-1] + 1,&\text{if } (a[i] == b[j]) \\ &max(f[i-1][j],f[i][j-1]),&\text{if } (a[i]!=b[j]) \end{aligned} \right.
    说明:上面的 a[i] 表示 A 的第 i 个数
  3. 边界设置:只要设置 f[i][0]=0f[i][0] = 0f[0][j]=0f[0][j] = 0,则循环中所有状态都可以计算
  4. 问题答案f[1][n]f[1][n] (由状态表示的实际含义推得)
    说明:DP 问题求最大值的时候只要保证不遗漏就行了,可以重复,求最长公共子序列时,f[i1][j]f[i-1][j]包含00和01,而f[i][j1]f[i][j-1]包含00和10,所以他们是有交集的

  1. 状态表示f[i][j]f[i][j]表示A[1,..,i]A[1,..,i]B[1,..,j]B[1,..,j]的公共子串,且以A[i]A[i]B[j]B[j]结尾的结合,属性(最大值)

  2. 状态计算:划分依据 a[i]a[i]b[j]b[j] 是否相同
    f[i][j]={f[i1][j1]+1if (a[i]==b[j])0if (a[i]!=b[j]) f[i][j] = \left\{ \begin{aligned} &f[i-1][j-1] + 1,&\text{if } (a[i] == b[j]) \\ &0,&\text{if } (a[i]!=b[j]) \end{aligned} \right.

  3. 边界设置:只要设置 f[i][0]=0f[i][0] = 0f[0][j]=0f[0][j] = 0,则循环中所有状态都可以计算

  4. 问题答案max1in,1jmf[i][j]\mathop{max} \limits _{1\leq i \leq n,1\leq j \leq m}f[i][j] (由状态表示的实际含义推得)

  • lc 813. 最大平均值和的分组:状态表示: f[i][j]f[i][j]表示前 i 个元素分成 j 组的方案集合,属性(最大值),状态计算:把前面的 j - 1 组用一个状态表示,但要注意有些集合不一定存在,画一个分析图会很清楚!

数学

  • lc204. 计数质数线性筛素数 核心:每个数只会被最小的质因数筛掉
  • lc372. 超级次方快速幂 a[1,5,2,6]=(a[1,5,2])10a6a^{[1,5,2,6]} = (a^{[1,5,2]})^{10} a^6 可以看到问题的规模减小了,所以可以用递归来解决
  • lc96. 不同的二叉搜索树卡特兰数 模版题
  • lc 829. 连续整数求和数学公式推导 起点 ii,长度 nn,求和公式(2i+n1)n/2=N(2*i+n-1)n/2 = N,解出 i=(Nn(n1)/2)/ni = (N-n(n-1)/2)/n。我们从11开始遍历长度 nnii 一开始会很大),如果i<1N<n(n+1)/2i < 1即N < n(n+1)/2退出循环;如果 (Nn(n1)/2)%n==0(N - n * (n - 1)/2) \% n == 0 说明起点 ii 存在。时间复杂度: O(N)O(\sqrt N) 另一种思路:3 个连续整数(a,b,c)时,b 比 a 大 1,c 比 a 大 2,如果 N - 1 - 2 能整除 3,则商、商+1 与商+2 构成 N,其余同理

分治

  • 剑指offer. 丑数三路归并 {ai}\{a_i\} 表示至少包含一个因子 2 的丑数集合…,借助 {ai/2},{bi/3},{ci/5}\{a_i / 2\},\{b_i/3\},\{c_i / 5\} 去还原 {ai},{bi},{ci}\{a_i \},\{b_i\},\{c_i \}。当我们先把 1 加到答案集合后,每当我需要用 aia_i 这个值时,可以发现在一步步把丑数添加到答案集合的过程中 ai/2a_i / 2 这个值一定已经被求出,所以乘 2 后就得到了 aia_i,同理可知 bjb_jckc_k 这样就可以进行三路合并了。
  • lc 313. 超级丑数K路合并 和求第 n 个丑数类似,只不过那里是三路合并,这里是 K 路合并

编程竞赛记录

算法编程竞赛的一些经验和教训

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