tiankonguse blog
2024-03-17T13:34:29+08:00
https://github.tiankonguse.com
这里是一个程序员的生活缩影,记录的有: 1. 计算机技术与算法 2. 程序员生活的酸甜苦辣 3. 投资理财小知识 4. 健康知识:感冒、皮肤、健身、营养学等 5. 读书笔记 6. 电影观后感 7. 人生思考 这些都可以通过菜单快速找到。
tiankonguse
i@tiankonguse.com
leetcode 第 389 场算法比赛
2024-03-17T18:13:00+08:00
2024-03-17T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/03/17/leetcode-contest-389
<h2 id="零背景">零、背景</h2>
<p>今天比赛最后一题是三分+二分,代码量比较大,比赛期间没通过,赛后才调试通过。</p>
<p>A: 枚举判断逆向子串是否在子串里。<br />
B: 数学计算。<br />
C: 枚举下边界,二分找到上边界,计算出答案。<br />
D: 枚举索引位置,三分左边界找到最优答案。</p>
<p>比赛排名:最后一题做出来时公布。<br />
比赛代码:https://github.com/tiankonguse/leetcode-solutions/tree/master/contest</p>
<h2 id="一字符串及其反转中是否存在同一子字符串">一、字符串及其反转中是否存在同一子字符串</h2>
<p>题意:问是否存在长度为2的子串,反转后,依旧是子串。</p>
<p>思路:枚举所有长度为2的子串,反转,判断是否是子串。</p>
<h2 id="二统计以给定字符开头和结尾的子字符串总数">二、统计以给定字符开头和结尾的子字符串总数</h2>
<p>题意:问以字母 c 为收尾字符的子串的个数。</p>
<p>思路:数学计算题。</p>
<p>统计字母 c 的个数 num。<br />
如果子串长度为 1, 则有 num 个。<br />
如果子串的长度大于 1,则是 num 个字母里挑选两个当做收尾,即 <code class="language-plaintext highlighter-rouge">C(num, 2)</code>。</p>
<h2 id="三成为-k-特殊字符串需要删除的最少字符数">三、成为 K 特殊字符串需要删除的最少字符数</h2>
<p>题意:问最少删除多少个字符,使得任意两个字符的频率之差不超过 k。</p>
<p>思路:枚举+二分。</p>
<p>可以发现,至于统计的字符个数有关系,与字符自身没有关系。<br />
所以需要先统计字符的个数,并对个数进行排序。</p>
<p>之后枚举最终答案的最小频次 l ,最大频次则为 <code class="language-plaintext highlighter-rouge">l+k</code>。<br />
小于最小频次的字符,都需要删除。<br />
大于最大频次的字符,都需要删除与最大频次保持一致。</p>
<p>上面两个都可以通过前缀和来计算得到。</p>
<p>复杂度:<code class="language-plaintext highlighter-rouge">O(n log(n))</code></p>
<h2 id="四拾起-k-个-1-需要的最少行动次数">四、拾起 K 个 1 需要的最少行动次数</h2>
<p>题意:给一个 01 数组,任意选择一个坐标当做索引位置,问至少多少代价,才能捡起 k 个 1。</p>
<p>操作0:将索引位置的值捡起,代价为0。 <br />
操作1:将某个位置(非索引位置)的值设置为 1,代价为 1,至多 X 次。<br />
操作2:交换相邻两个位置的值,代价为 1,无限制次数。</p>
<p>思路:需要先枚举索引位置,然后判断当前位置的最优答案。</p>
<p>显然,如果选择操作1,必然是操作索引相邻的位置,这样答案会更优。<br />
故可以优先判断尽量的选择操作1,操作2只交换索引的相邻位置,是否可以得到答案。<br />
如果可以,就是当前索引位置的最优答案。</p>
<p>如果操作 1 用完了,还没答案,则需要再离索引较远的位置选择一些 1,通过操作 2 慢慢移动到索引位置。<br />
操作 1 每用一次,就可以创造出一个1,结合一次操作2,就可以捡起一个1.</p>
<p>对于剩余的 1,不妨设为 K 个, 需要通过操作2 从原数组移动到索引位置。<br />
显然,要移动的 K 个 1 是连续的。<br />
通过观察 K 个 1 的左边界与答案的关系,可以发现是先降低再增加。<br />
这种曲线需要使用三分来找到最优答案。</p>
<p>左边界确定后,也可以通过二分查找,确定右边界。<br />
之后就与第三题一样,通过下标偏移量的前缀和来计算出移动的距离和。</p>
<p>复杂度:<code class="language-plaintext highlighter-rouge">O(n log(n) log(n))</code></p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛最后两道题很类似。<br />
第三题直接二分找左右边界,然后前缀和计算答案。<br />
第四题先三分确定左边界,二分找到右边界,再通过前缀和计算答案。</p>
<p>年前比赛暂停了一个月,最近最后一题都会做,不过比赛的时候好像时间不够了。<br />
分析原因,最后一题只是大概想了算法就开始敲代码,对于细节是敲代码的时候再确定的,这就导致有写细节被遗漏,从而代码样例不通过。<br />
以后打算先在草稿上推导出完整的算法伪代码,再开始敲代码。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
最后一题代码量比较大。
leetcode 第 388 场算法比赛
2024-03-10T18:13:00+08:00
2024-03-10T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/03/10/leetcode-contest-388
<h2 id="零背景">零、背景</h2>
<p>今天比赛最后一题是动态规划不难,以前我都是写递归,这次写递推,被坑了,比赛期间没通过,以后还是要老老实实写递归。</p>
<p>A: 排序,从大到小判断 <br />
B: 排序,从大到小判断。 <br />
C: 枚举题,枚举并统计所有子串,然后枚举求答案。 <br />
D: 动态规划,推动出状态转移方程即可。</p>
<p>比赛排名:299<br />
比赛代码:https://github.com/tiankonguse/leetcode-solutions/tree/master/contest</p>
<h2 id="一重新分装苹果">一、重新分装苹果</h2>
<p>题意:给N堆苹果和M个箱子,问选择多少个箱子可以把所有苹果都装起来。</p>
<p>思路:苹果可以拆分,所以选择体积最大的若干个箱子即可。</p>
<p>对苹果求和,对箱子排序,从大到小选择箱子累计求和。</p>
<h2 id="二幸福值最大化的选择方案">二、幸福值最大化的选择方案</h2>
<p>题意:给N堆好苹果和K次选择,每次可以选择一堆苹果,选择后剩余的所有堆苹果都会坏掉一个。<br />
问如何选择才能选最多的好苹果。</p>
<p>思路:不断如何选择,第 i 次选择的苹果在之前都会坏掉 <code class="language-plaintext highlighter-rouge">i-1</code> 个。<br />
想要选择的好苹果最多,就要选择苹果最多的 k 堆苹果。</p>
<p>对苹果排序,从大到小选择,减去每次选择时坏掉的苹果即可。</p>
<h2 id="三数组中的最短非公共子字符串">三、数组中的最短非公共子字符串</h2>
<p>题意:给一个字符串数组,求一个字符串的最长子串,当前子串不是其他任何字符串的子串。</p>
<p>思路:数据量不大,统计所有字符串的所有子串集合,枚举当前字符串的子串是否是答案。</p>
<p>对于一个子串,如果统计字符串里只出现一次,则说明其他字符串没出现。</p>
<h2 id="四-k-个不相交子数组的最大能量值">四、 K 个不相交子数组的最大能量值</h2>
<p>题意:给一个数组,求选择 k 个不想交的子数组,求最大积分。</p>
<p>子数组和: 第 i 个子数组的和定义为 <code class="language-plaintext highlighter-rouge">sum[i]</code><br />
积分公式:<code class="language-plaintext highlighter-rouge">(-1)^(i+1) * sum[i] * (x - i + 1)</code></p>
<p>思路:定义状态<code class="language-plaintext highlighter-rouge">f(n,k)</code> ,含义为第 n 个位置之后的数组选择 k 个子数组的最优答案。</p>
<p>写出状态转移方程,分为两种情况。</p>
<p>1)不选择当前元素,则状态为 <code class="language-plaintext highlighter-rouge">f(n+1, k)</code>。<br />
2)选择当前元素,则可能选择的长度最少是1,最长不能让 k 不够选。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n*k*k)</code></p>
<p>优化:分析第二种情况,当前元素必须选择,提取出来,则刚好等价于<code class="language-plaintext highlighter-rouge">f(n+1,k)</code>必须选择第一个元素的情况。</p>
<p>所以定义新的状态<code class="language-plaintext highlighter-rouge">F(n,k)</code>代表当前元素必须选择的答案。</p>
<p><code class="language-plaintext highlighter-rouge">F(n,k)</code>的状态转移方程通用分为两种情况。</p>
<p>1)只选择一个元素,即<code class="language-plaintext highlighter-rouge">F(n,k)=V+f(n+1,k-1)</code><br />
2)选择多个元素,依旧有很多选择。<br />
提起第一个元素,发现等价于必须选择第一个元素。<br />
故为<code class="language-plaintext highlighter-rouge">F(n,k)=V+F(n+1,k)</code>。</p>
<p>两个状态转移方程结合起来,就可以写为</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>f(n,k) = max(f(n+1,k), F(n,k))
F(n,k) = max(V+f(n+1,k-1), V+F(n+1,k))
</code></pre></div></div>
<p>当然,这道题在计算当前元素的值时,存在符号问题。<br />
所以状态需要增加一个维度,来标记当前的符号。</p>
<p>新的状态为 <code class="language-plaintext highlighter-rouge">f(s, n, k)</code> 和 <code class="language-plaintext highlighter-rouge">F(s, n, k)</code>。</p>
<p>s 为 0 时代表负号,为 1 时为正号,V的值就为 <code class="language-plaintext highlighter-rouge">s * nums[n] * k</code>。</p>
<p>状态转移方程中,只有当前子数组选择完之后,符号才需要反转。<br />
故新的状态转移方程为</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>f(s,n,k) = max(f(s,n+1,k), F(s,n,k))
F(s,n,k) = max(V+f(1-s,n+1,k-1), V+F(s,n+1,k))
</code></pre></div></div>
<p>状态方程我写出来后,还没有到 11 点,一看榜单,竟然一个人也没通过最后一题。</p>
<p>在敲代码时,我在由于是使用递推还是递归。</p>
<p>由于状态定义已经在函数里定义好了,我就去写递推了。<br />
然后就面临一个抉择:如何初始化状态数组。</p>
<p>我一开始想的是偷懒一下,直接通过初始化来处理边界情况,就这样样例始终无法通过。<br />
赛后我赶紧用递归重写,5分钟就写完了递归代码。</p>
<p>通过递推,很容易发现存在三个边界。</p>
<p>1)最后一个元素,只能选择一个子数组。<br />
2)多个元素,只能选择一个子数组。<br />
3)多个元素,每个都是一个子数组。<br />
4)多个元素,任意选择。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛自己偷懒没去处理边界情况,结果就被边界问题卡主了。
以后打比赛还是老老实实写递归吧,这样代码简单清晰,还好写。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
最后一题不难,但是没通过。
leetcode 第 387 场算法比赛
2024-03-03T18:13:00+08:00
2024-03-03T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/03/03/leetcode-contest-387
<h2 id="零背景">零、背景</h2>
<p>今天比赛比较简单,不过我今天开始去练科目三了,所以没有参加比赛。</p>
<p>A: 构造题,按题意拆分数组,最后合并数组。 <br />
B: 枚举题,求出所有左上角的子矩阵和,统计个数。 <br />
C: 枚举题,统计两个区域每个值出现的次数,枚举两个区域的值,求最小值。 <br />
D: 构造题,通过树状数组/线段树/平衡树等数据结构统计大于某个值的个数,之后和第一题一样。</p>
<p>比赛代码:https://github.com/tiankonguse/leetcode-solutions/tree/master/contest</p>
<h2 id="一将元素分配到两个数组中-i">一、将元素分配到两个数组中 I</h2>
<p>题意:给一个数组,默认将前两个元素分别分配给两个数组,之后的元素按规则分配到两个数组中。</p>
<p>规则:如果数组1最后一个元素大于数组2的最后一个元素,则将当前元素加入到数组1,否则加入到数组2。</p>
<p>思路:定义两个数组,按题意模拟构造两个数组即可。</p>
<p>优化:可以指定定义一个答案数组,数组1从前到后插入,数组2从后到前插入,最后反转数组2即可。</p>
<h2 id="二元素和小于等于-k-的子矩阵的数目">二、元素和小于等于 k 的子矩阵的数目</h2>
<p>题意:给一个矩阵,问左上角的所有子矩阵中,矩阵和不大于 k 的元素个数。</p>
<p>思路:预处理出所有矩阵和,统计答案即可。</p>
<p>公式:<code class="language-plaintext highlighter-rouge">g[i][j]=g[i-1][j]+g[i][j-1]-g[i-1][j-1]</code></p>
<p>优化:可以在原数组上进行,从而避免申请额外的内存。</p>
<h2 id="三在矩阵上写出字母-y-所需的最少操作次数">三、在矩阵上写出字母 Y 所需的最少操作次数</h2>
<p>题意:给一个矩阵,按规则将矩阵分为两个区域,问将两个区域内的数值相等,不同区域数值不等的最小操作数。</p>
<p>思路:统计两个区域所有值出现的次数,枚举两个区域的所有值组合,求出最优答案。</p>
<p>小技巧:假设区域1的个数为 sum1,值1的个数为 v1,则修改为值1的操作数为 <code class="language-plaintext highlighter-rouge">sum1-v1</code>。</p>
<h2 id="四将元素分配到两个数组中-ii">四、将元素分配到两个数组中 II</h2>
<p>题意:与第一题类似,不过加入两个数组的规则变复杂了。</p>
<p>规则:统计数组中大于当前元素的个数。</p>
<p>思路:根据题意可以得知需要这样一个数据结构:可以动态插入元素,然后动态询问大于某个值的元素个数。</p>
<p>对于这个数据结构,是经典的带计数的平衡树。</p>
<p>不过我们可以通过数值离散化,把<code class="language-plaintext highlighter-rouge">10^9</code>范围内的数值转化为<code class="language-plaintext highlighter-rouge">10^5</code>范围内的下标,然后通过线段树或者树状数组来代替平衡树。</p>
<p>小提示:这种离散化,有一个专业名称叫做 <code class="language-plaintext highlighter-rouge">FenwickTree</code>。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛比较简单,你都会做了吗?</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
四道题都比较简单,不过没参加比赛。
leetcode 第 386 场算法比赛
2024-02-25T18:13:00+08:00
2024-02-25T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/02/25/leetcode-contest-386
<h2 id="零背景">零、背景</h2>
<p>今天比赛最后一题有点难,只有23个人做出来,赛后我使用暴力算法竟然一下通过了。</p>
<p>A: 统计题,统计值的个数。<br />
B: 模拟题,矩阵求交。<br />
C: 二分,直接构造数组,判断是否合法。<br />
D: 二分,贪心构造数据,判断是否合法。</p>
<h2 id="一分割数组">一、分割数组</h2>
<p>题意:给一个数组,问是否可以拆分为两个等长的子数组,子数组值互补相同。</p>
<p>思路:统计值的个数,不大于 2 即可构造。</p>
<p>统计方法1:hash 表<br />
统计方法2:排序,判断是否存在相邻三个元素是否相等。</p>
<h2 id="二求交集区域内的最大正方形面积">二、求交集区域内的最大正方形面积</h2>
<p>题意:给若干矩阵,任意两个矩阵的重叠区域可以找到一个正方形,求最大正方向。</p>
<p>思路:枚举所有矩阵组合,判断是否重叠,重叠了,求正方形。</p>
<p>是否重叠:判断横坐标以及纵坐标是否存在一根垂直线是否在另外一个矩阵中间。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (B0 >= T1 || B1 >= T0) continue; // 上下没有交集
if (L0 >= R1 || L1 >= R0) continue; // 左右没有交集
</code></pre></div></div>
<p>重叠区域:上下左右四个边界分别求最小值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll T2 = min(T0, T1);
ll L2 = max(L0, L1);
ll B2 = max(B0, B1);
ll R2 = min(R0, R1);
</code></pre></div></div>
<p>正方形:重叠区域的最短边。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll r = min(T2 - B2, R2 - L2);
</code></pre></div></div>
<h2 id="三标记所有下标的最早秒数-i">三、标记所有下标的最早秒数 I</h2>
<p>思路:给两个数组,第二个数组是操作命令,问最短多少秒可以将数组1所有下标标记。</p>
<p>操作1:第一个数组随机选一个下标,减一。<br />
操作2:数组1固定下标的值为0时,标记此下标完成。<br />
操作3:无操作</p>
<p>思路:显然需要二分,判断指定操作内是否可以完成。</p>
<p>简单分析可以得到下面几个信息。</p>
<p>数组1的所有下标都需要标记。<br />
每个下标只能通过操作2进行标记。<br />
对于一个下标,如果在操作命令里出现多次,标记的越晚越好,即可以在最后一次进行标记。</p>
<p>由于要求标记的时候值为0,前面需要选择对应若干个操作1进行减一。</p>
<p>前面的操作,用于减哪个值无所谓,只要最终所有值都可以减为 0 即可,但必须是前面的操作减后面的下标,后面的无法减前面的。</p>
<p>故可以从后到前循环,遇到最后一个下标,进行标记,并累计记录需要在前面减多少次。<br />
如果遇到重复的下标,则对累计值进行减一,但不能是负数。</p>
<p>最后,如果所有下标都进行标记,且累计值都被减完,则存在答案。</p>
<h2 id="四标记所有下标的最早秒数-ii">四、标记所有下标的最早秒数 II</h2>
<p>思路:给两个数组,第二个数组是操作命令,问最短多少秒可以将数组1所有下标标记。</p>
<p>操作1:第一个数组随机选一个下标,减一。<br />
操作2:数组1固定下标设置为任意非负数值。<br />
操作3:第一个数组随机选一个下标,进行标记。<br />
操作3:无操作</p>
<p>思路:显然需要二分,判断指定操作内是否可以完成。</p>
<p>但是进行判断的方法与第三题完全不同了。</p>
<p>操作2可以设置为任意值,显然最优解是直接设置为0。</p>
<p>故答案显然是选择若干操作2对下标设置为0,其他的操作全部用来减一或者标记完成。</p>
<p>选择哪些下标2进行操作呢?<br />
显然是选择值大的,且越大越好。</p>
<p>第一种贪心为从大到小尝试进行操作2,并判断操作后,是否可以满足后面的可以把前面的操作2都标记。</p>
<p>从大到小需要排序,复杂度 <code class="language-plaintext highlighter-rouge">O(m log(m))</code><br />
每一个选择会影响历史的选择的合法情况,所以需要判断整个历史选择是否可以合法,最简单的是遍历整个数组,判断当前的所有选择是否有冲突,复杂度<code class="language-plaintext highlighter-rouge">O(m^2)</code>
综合复杂度:<code class="language-plaintext highlighter-rouge">O(m^2 log(n))</code></p>
<p>看其他人的代码,还有人按第一次出现的下标,从后到前进行贪心选择。<br />
由于是从后到前选择的,前面的选择不会影响后面的选择的答案,故判断的复杂度为<code class="language-plaintext highlighter-rouge">O(1)</code><br />
综合复杂度:<code class="language-plaintext highlighter-rouge">O(m log^2(n))</code></p>
<p>PS:我没有证明按下标进行贪心的正确性,目测有badcase,一个小的靠后的下标选择后,前面的较大的下标就无法选择了。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛最后一题较难。</p>
<p>目前我想到的可以通过的方法复杂度较高。<br />
简单看了几个人的代码,贪心是通过比赛了,但是可以证明有 badcase。<br />
后面有时间了,再研究下其他人的代码,看是否有更优的解法。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
最后一题比赛期间没做出来。
leetcode 第 385 场算法比赛
2024-02-11T18:13:00+08:00
2024-02-11T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/02/18/leetcode-contest-385
<h2 id="零背景">零、背景</h2>
<p>今天大年初九,我休假还没回深圳上班。</p>
<p>一大早起床带我爸去了一趟医院,回来看还有时间,就参加了比赛,排名 45。</p>
<p>最后一题又是字符串题,我又使用字符串 hash 算法通过了。</p>
<p>A: 暴力循环判断。<br />
B: 字典树。<br />
C: 枚举。<br />
D: 字符串hash。</p>
<h2 id="一统计前后缀下标对-i">一、统计前后缀下标对 I</h2>
<p>题意:给一个字符串数组,问存在多少字符串对,满足前面的字符串同时是后面字符串的前缀和后缀。</p>
<p>思路:数据量不大,暴力枚举所有字符串对,判断是否满足前缀和后缀关系。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n^2*L)</code></p>
<h2 id="二最长公共前缀的长度">二、最长公共前缀的长度</h2>
<p>题意:两个数组分别选两个数字,会有一个公共前缀,求最长公共前缀的长度。</p>
<p>思路1:字典树。<br />
对一个数组构造字典上,然后另一个数组枚举查询最长公共前缀。</p>
<p>思路2:前缀集合。<br />
一个数组的所有前缀集合储存起来,然后另一个是这样枚举前缀集合进行判断。<br />
数字的前缀不超过 10 个,空间大小翻 10 倍可以接受。</p>
<p>思路3:字符串数组排序+二分查找。<br />
在《<a href="https://mp.weixin.qq.com/s/WyU9lAzilCDF6t-037cGtw">第133场比赛</a>》题解中,我提到可以使用字符串排序加二分查找来代替字典表。</p>
<p>本质区别是数组没压缩公共前缀,而字典树压缩了公共前缀。</p>
<h2 id="三出现频率最高的素数">三、出现频率最高的素数</h2>
<p>题意:给一个数字矩阵,求矩阵的直线路径组成的数字中,出现频率最高的、大于 10的素数。</p>
<p>思路:枚举所有路径,统计素数的频率,然后寻找最大频率即可。</p>
<h2 id="四统计前后缀下标对-ii">四、统计前后缀下标对 II</h2>
<p>题意:与第一题一样,数据范围变大。</p>
<p>数据范围:字符串个数 <code class="language-plaintext highlighter-rouge">10^5</code> 个,字符串字符总个数 <code class="language-plaintext highlighter-rouge">10^5</code>个。</p>
<p>思路:由于字符串个数比较多,不能枚举所有字符串对,只能判断确定一个字符串后,另一个字符串有多少个满足要求。</p>
<p>确定的字符串有两种方法,一种是确定左边的字符串,另一种事确定右边的字符串。</p>
<p>确定左边的字符串,需要对右边的所有字符串预处理,并统计出每个字符串前后缀相等时,对应前缀对应的字符串个数。<br />
前缀的个数与字符个数相等,故字符串个数为<code class="language-plaintext highlighter-rouge">10^5</code>,直接储存字符串空间会爆炸,所以需要储存字符串的 hash 值。</p>
<p>确定右边的字符串,则需要对左边的所有字符串预处理,统计每个字符串出现的频率。<br />
之后枚举右边字符串的所有前缀与后缀,相等时,查找前面出现的频率。<br />
每个字符串储存一次,空间复杂度与输入一致。</p>
<p>难点:如何在 <code class="language-plaintext highlighter-rouge">O(1)</code>的复杂度里判断前缀与后缀是否相等。<br />
显然,使用 hash 预处理的话,就可以直接<code class="language-plaintext highlighter-rouge">O(1)</code>判断了。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛第二题其实蛮有难度的,有三种解法。<br />
最后一题则需要使用大家都很熟悉的 hash 算法来判断两个字符串是否相等。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
hash 太强大了。
《热辣滚烫》,我不是目标受众
2024-02-13T18:13:00+08:00
2024-02-13T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/02/13/see-yolo
<h2 id="零背景">零、背景</h2>
<p><img src="https://res2024.tiankonguse.com/images/2024/02/13/001.png" alt="" /></p>
<p>上一次写影评是2021年,那一年贾玲导演了第一部电影《你好,李焕英》。<br />
如今已是2024年,贾玲恰好导演了第二部电影《热辣滚烫》。</p>
<p>2024年2月11日,龙年初二,和老婆去电影院看电影。<br />
老婆问是看《年会不能停》还是《热辣滚烫》,我说先看贾玲的《热辣滚烫》吧。</p>
<p>看完电影,我对电影人物的描绘感觉很奇怪,对电影里贾玲的突然转变觉得很突兀,总觉得缺少一些东西。</p>
<p>随后我看了贾玲的采访、看了宣传预告片、去看了原版电影《百元之恋》剧情,采访了老婆怎么看这个电影,最后思索良久,得到一个结论:这部电影并没有很清晰的把贾玲想表达的内容表达出来,即电影是不完整的。</p>
<p>对于不完整的电影,只有电影的目标受众才容易有共鸣,因为对于目标受众,只需要部分片段,即可引起共鸣。<br />
而对于其他人,看这部电影则会产生不解。<br />
比如我,觉得这部电影缺少一些逻辑,看的莫名其妙。</p>
<p>看了很多影评,不同人看这部电影,看到的结论不一样,大概分为三类:减肥励志、电影有缺陷、为自己而活。</p>
<h2 id="一减肥励志">一、减肥励志</h2>
<p>去看电影前,各种新闻都在说贾玲瘦了一百多斤,那电影内容必然有女主由胖变瘦的情节。</p>
<p>而大众看这部电影,最容易误认为是在讲女主原先很胖,经过拳击训练,变瘦的故事。</p>
<p>这也是很多人支持与反对这部电影的理由。</p>
<p>反对者会说,贾玲这么有钱,教练、医生、营养师、饮食都是最高端的,用着最新的高科技和药物,基数大减起来容易,其他人有这个人,也可以轻松减重一百多斤。</p>
<p>支持者会说,减重最难的是坚持,减重的是自己,吃苦的是自己,只有曾经尝试过减重的人,才能理解这里面的痛苦,支持贾玲。</p>
<p>其实,贾玲的这部电影并不想让大家讨论减重这个话题。<br />
不信你再看一遍这部电影,女主减重成功之后,电影剧情里,周围没有一个人对减重这个话题作出评价。</p>
<p>女主的爸爸说,年轻人要出去闯。<br />
女主的前男友说,这个人看着眼熟。<br />
其他人没有任何评价。<br />
这个是贾玲有意弱化这个话题的。</p>
<p>但是,对于大部分的大众,看这部电影,看到的也确实只有励志减肥。<br />
这个是不以导演的意志而改变的,尤其是电影在最后,以贾玲自己减肥的备忘录来结束,恰恰又让普通大众的正反方都找到自己的证据:看,这不是在证明这是一部减肥励志电影吗?</p>
<h2 id="二缺少逻辑">二、缺少逻辑</h2>
<p>对于那些理性的人,不会把电影与贾玲联系起来,而是去认真的看电影自身是否自洽。</p>
<p>电影开场介绍了美术、动作指导等,与电影完全没关系。<br />
电影结束,介绍了非电影人物的备忘录,与电影完全没关系。<br />
从这个角度看,电影的开场与结束都没有意义。</p>
<p>贾玲作为导演,她的电影技巧同样分为两段:第一段占据电影很大的篇幅,讲一个故事,但是隐藏部分信息,因此观影者会发现一些不符合逻辑的地方。<br />
第二段在结尾最后几分钟,告诉我们隐藏的信息,从而让之前不符合逻辑的剧情看起来变得有逻辑,从而让前面的故事变成一个新的故事。</p>
<p>《你好,李焕英》使用这个叙事技巧,使得两个故事发生了质的变化。<br />
但是《热辣滚烫》使用这个技巧并没有对应的作用。<br />
因为补充的隐藏的剧情并没有解释故事中的所有疑问。</p>
<p>故事里,有父母,妹妹,表妹,第一任男友,闺蜜,第二任男友,最新的拳击教练,有些人物的故事显然是多余的。</p>
<p>母亲有两段故事。<br />
第一段是采访,说当初只顾赚钱没时间看孩子,然后哭了,说也没赚到钱。<br />
当然,这个是为了娱乐效果,可以忽略。<br />
第二段是女主搬出家后打钱,直接转了两千,有被感动到。<br />
‘</p>
<p>父亲有两段故事。<br />
女主减肥成功后,父亲很冷静,说也好,趁着年轻闯一闯。<br />
另一段是父亲把女主喊回来给商铺帮忙,理由是母亲摔伤了,女主也没什么表现。</p>
<p>总体看来,父母与女主的关系很奇怪,没有体现出更多的关怀,可能是给的篇幅太少吧。</p>
<p>妹妹有一段故事。<br />
妹妹的孩子要上学了,想让女主把姥姥送给女主的房子过户到自己名下,从而可以让孩子上学。<br />
为此,两姐妹还摔桌子打了一架,妹妹说女主单身一辈子,老了还不是妹妹来给女主养老。</p>
<p>隐藏剧情:女主最后还是把房子过户给妹妹了。</p>
<p>不理解的地方:剧情里把妹妹打造成婚内出轨,不理解,没看懂要表达什么,与电影主题有啥关系。</p>
<p>前男友与闺蜜出轨,闺蜜求女主当伴娘,证明不是小三也挺怪的。<br />
当然,这个后面隐藏剧情进行了解释,即为自己,要学会去拒绝。</p>
<p>电视台采访,两个导师的打闹也莫名其妙。<br />
当然,可以解释是为了增加喜剧效果,但与电影主题完全无关。</p>
<p>最后,最不可理解的是,面对家人的不理解,爱情的背叛与不接受,亲戚的不尊重,女主跳楼后就突然醒悟了,然后努力训练。</p>
<p>这个突然醒悟,确实很突然,从而变得莫名其妙。</p>
<h2 id="三为自己而活">三、为自己而活</h2>
<p>这部电影真正想要表达的是要为自己而活,如电影英文名 YOLO, 含义是 You Only Live Once,生命只有一。</p>
<p>前半段,女主不懂得拒绝。</p>
<p>就如假如你有两个苹果,要分给朋友,会如何选择。<br />
以前,女主的朋友会以为女主不爱吃苹果,直接把所有苹果要走,女主都会答应。<br />
现在,女主会看心情,即根据自己的实际情况再做决定。</p>
<p>以前,妹妹要自己的房子,就转给妹妹了。<br />
以前,闺蜜和男友结婚,邀请自己当伴娘,女主就去当伴娘了。<br />
以前,表妹通过侮辱女主的方式拍纪录片,女主就去配合拍了。<br />
以前,喜欢的人邀请自己吃牛蛙,女主就去吃了。<br />
以前,喜欢的人抱着自己转圈撞到脚了,就默默忍着。<br />
这样的事情可能还有很多很多。</p>
<p>现在,女主开始为自己而活,学会了对他人说不,学会了拒绝别人。<br />
这时候前男友回头找女主,女主也果断的进行了拒绝。</p>
<p>这种理念很正能量,这才是真正的为自己而活。</p>
<h2 id="四最后">四、最后</h2>
<p>为自己而活这种理念值得鼓励,但确实很难从电影里直接解读出来。</p>
<p>我们更多的是从导演的采访中去了解,或者从影评中去了解,然后再尝试去解读电影,隐隐约约看出有为自己而活的影子,但是又有点牵强。</p>
<p>前半场是现实生活,一瞬间,主角觉醒,后半场像开挂了一样,走向成功。</p>
<p>因此,我看电影时没有大家说的热血沸腾,只有不解,也许,我不是这个电影的目标用户吧。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
后半场,看的有点困惑,觉得缺少一些东西。
leetcode 第 384 场算法比赛
2024-02-11T18:13:00+08:00
2024-02-11T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/02/11/leetcode-contest-384
<h2 id="零背景">零、背景</h2>
<p>今天大年初二,上午出门了,晚上回来做了下题,发现都比较简单。</p>
<p>A: 模拟, 循环找列最大值。<br />
B: 枚举, 两层循环判断是否匹配。<br />
C: 贪心构造, 统计字符奇偶次数,按长度从小到大构造回文串。<br />
D: hash 或 kmp,判断是否匹配。</p>
<h2 id="一修改矩阵">一、修改矩阵</h2>
<p>题意:给一个矩阵,将 -1 替换为每列的最大值。</p>
<p>思路:模拟,每列循环找最大值,再循环将 -1 替换。</p>
<h2 id="二匹配模式数组的子数组数目-i">二、匹配模式数组的子数组数目 I</h2>
<p>题意:给一个数组和连续子数组的大小关系,问多少个子数组满足关系。<br />
大小关系分为三种情况:大于、等于、小于。</p>
<p>思路:数据范围不大,枚举所有子数组,循环判断是否满足大小关系。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n*m)</code></p>
<h2 id="三回文字符串的最大数量">三、回文字符串的最大数量</h2>
<p>题意:给一个字符串数组,可以交换任意两个字符串的任意两个字符,问可以构造多少个回文字符串。</p>
<p>思路:贪心构造。</p>
<p>由于可以由于可以交于任意字符串的任意字符,显然优先构造字符串长度小的。</p>
<p>预处理1:统计字符有多少个偶数,多少个奇数。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">map</span><span class="o"><</span><span class="kt">char</span><span class="p">,</span> <span class="kt">int</span><span class="o">></span> <span class="n">h</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&</span> <span class="n">w</span> <span class="o">:</span> <span class="n">words</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">c</span> <span class="o">:</span> <span class="n">w</span><span class="p">)</span> <span class="p">{</span>
<span class="n">h</span><span class="p">[</span><span class="n">c</span><span class="p">]</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">one</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">two</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="p">[</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">]</span> <span class="o">:</span> <span class="n">h</span><span class="p">)</span> <span class="p">{</span>
<span class="n">two</span> <span class="o">+=</span> <span class="n">v</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">one</span> <span class="o">+=</span> <span class="n">v</span> <span class="o">%</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>预处理2:统计字符串的各个长度,从小到大排序。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">nums</span><span class="p">;</span>
<span class="n">nums</span><span class="p">.</span><span class="n">reserve</span><span class="p">(</span><span class="n">words</span><span class="p">.</span><span class="n">size</span><span class="p">());</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&</span> <span class="n">w</span> <span class="o">:</span> <span class="n">words</span><span class="p">)</span> <span class="p">{</span>
<span class="n">nums</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">w</span><span class="p">.</span><span class="n">length</span><span class="p">());</span>
<span class="p">}</span>
<span class="n">sort</span><span class="p">(</span><span class="n">nums</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">nums</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
</code></pre></div></div>
<p>构造:对于一个长度,分奇偶性处理。<br />
如果是偶数,判断统计的偶数字符是否够,够了即可构造。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">/</span> <span class="mi">2</span> <span class="o"><=</span> <span class="n">two</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ans</span><span class="o">++</span><span class="p">;</span>
<span class="n">two</span> <span class="o">-=</span> <span class="n">v</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>如果是奇数,先消耗一个奇数,再判断偶数字符是否够。<br />
如果没有统计的奇数,先消耗一个偶数,得到两个奇数,再做判断。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (one == 0 && two > 0) {
one += 2;
two -= 1;
}
if (v / 2 <= two && one > 0) {
ans++;
two -= v / 2;
one--;
}
</code></pre></div></div>
<h2 id="四匹配模式数组的子数组数目-ii">四、匹配模式数组的子数组数目 II</h2>
<p>题意:与第二题一模一样,数据范围变大。</p>
<p>思路:子串的匹配仅仅判断大小关系,预处理出原数组的大小关系,就是寻找有多少个大小关系子数组与输入匹配。</p>
<p>数组匹配基本的做法是 KMP,更简单的是 HASH 算法。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">ll</span> <span class="n">patternHash</span> <span class="o">=</span> <span class="n">Hash</span><span class="p">(</span><span class="n">pattern</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">m</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
<span class="n">Init</span><span class="p">(</span><span class="n">nums</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">n</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">ans</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="n">m</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">>=</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">Hash</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="n">m</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="n">patternHash</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ans</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">ans</span><span class="p">;</span>
</code></pre></div></div>
<h2 id="五最后">五、最后</h2>
<p>这次比赛比较简单,但是最后一题我是使用字符串 HASH 解决的。</p>
<p>后面有机会,研究下 KMP 算法和后缀数组,这些都使用经典的字符串算法做一次。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
最后一题还是使用 hash 代替 kmp。
leetcode 第 383 场算法比赛
2024-02-04T18:13:00+08:00
2024-02-04T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/02/04/leetcode-contest-383
<h2 id="零背景">零、背景</h2>
<p>晚上吃完饭,突然想起来今天周日,leetcode 比赛还没做。</p>
<p>便看了下,发现这次比赛的题都比较简单,而且最后一题可以使用字符串hash代替kmp和后缀树算法,百试不爽。</p>
<p>A: 循环判断前缀和。<br />
B: 枚举后缀是否与前缀相等。 <br />
C: 暴力预处理所有区域,最后求平均值。<br />
D: 字符串hash快速比较前缀与后缀是否相等。</p>
<h2 id="一边界上的蚂蚁">一、边界上的蚂蚁</h2>
<p>题意:给一个数组,代表左右移动的步长,问移动后到达原点的次数。</p>
<p>思路:求前缀和,判断是否在原点。</p>
<h2 id="二将单词恢复初始状态所需的最短时间-i">二、将单词恢复初始状态所需的最短时间 I</h2>
<p>题意:给一个字符串,每次可以删除前缀 k 个字符,并追加任意 k 个字符,问几次操作才能使得字符串与原字符串相等。</p>
<p>思路:假设进行 i 次后匹配,共删除 <code class="language-plaintext highlighter-rouge">i*k</code>个字符,剩余<code class="language-plaintext highlighter-rouge">n-i*k</code>个字符。<br />
说明长度为 <code class="language-plaintext highlighter-rouge">n-i*k</code> 的后缀与前缀相等。</p>
<p>换句话说,需要找到最小的 i,使得后缀与前缀相等。</p>
<p>数据范围不大,枚举所有 i,判断前缀与后缀是否相等即可。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n/k * n)</code></p>
<h2 id="三找出网格的区域平均强度">三、找出网格的区域平均强度</h2>
<p>题意:给一个数字矩阵,求每个位置所属区域平均值的平均值,如果不存在,返回原位置值。<br />
区域定义:如果一个<code class="language-plaintext highlighter-rouge">3*3</code>的正方形内相邻的数字的差都不大于 K,则称这个正方形为一个区域,区域的平均值为。<br />
区域平均值定义:区域内所有数字和除以数字个数。</p>
<p>思路:分为两个步骤。<br />
第一步,预处理出所有满足要求的区域平均值,储存在左上角位置。<br />
第二步,枚举一个位置所属的9个正方形,看是否有区域存在,存在了求平均值。</p>
<h2 id="四将单词恢复初始状态所需的最短时间-ii">四、将单词恢复初始状态所需的最短时间 II</h2>
<p>题意:与第二题一样,数据范围变大了。</p>
<p>思路:使用 hash 预处理字符串, 然后把第二题代码中 strncmp 换成前缀与后缀的hash判断即可。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛比较简单,最后一题直接使用前后缀字符串 hash 来暴力枚举即可轻松通过。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
万能的字符串hash,再也不需要kmp了。
leetcode 第 382 场算法比赛
2024-01-28T18:13:00+08:00
2024-01-28T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/01/28/leetcode-contest-382
<h2 id="零背景">零、背景</h2>
<p>这次比赛最后一题其实不难,但很有迷惑性。</p>
<p>A: 循环判断相邻字母变化次数。<br />
B: 枚举最大值,判断最大答案。<br />
C: 奇偶数个数。<br />
D: 从高位到低位贪心。</p>
<h2 id="一按键变更的次数">一、按键变更的次数</h2>
<p>题意:给一个字符串,问相邻字母值变化的次数。</p>
<p>思路:按题意循环判断即可。</p>
<h2 id="二子集中元素的最大数量">二、子集中元素的最大数量</h2>
<p>题意:给一个数组,求构造一个最长的数组,前一半相邻是平方关系,后一半是开方的关系。</p>
<p>思路:枚举起始位置或者中间位置即可。</p>
<p>枚举起始位置时,可能不存在答案,需要分情况判断。</p>
<p>枚举中间位置,由于对称性,只需要不断向两边扩展即可。</p>
<p>注意事项:1 比较有特殊,单独判断。</p>
<h2 id="三alice-和-bob-玩鲜花游戏">三、Alice 和 Bob 玩鲜花游戏</h2>
<p>题意:两个数字,两个人轮流挑一个数字减一,减一后两个数字都是 0 时胜利。<br />
告诉你两个数组的最大值,问第一个人胜利的数字对个数。</p>
<p>思路:数字和为奇数时胜利,枚举一个数字,统计另一个数字和的个数。</p>
<p>优化:矩阵和的奇偶性是对称的,所以每行奇数和偶数的个数确定的,可以直接分奇偶性找规律推导出公式直接计算答案。</p>
<h2 id="四给定操作次数内使剩余元素的或值最小">四、给定操作次数内使剩余元素的或值最小</h2>
<p>题意:给一个数组,每次操作可以选择相邻数字进行与运算,合并为一个数字,求 k 次操作后,剩余数字的或运算的最小值。</p>
<p>思路:贪心。</p>
<p>答案是剩余数字的或运算,显然优先处理高位,最后处理低位。</p>
<p>假设一个高位出现 x 个数字,则最少需要操作 x-1 次才能消除这一位,最多需要 x 次才能消除这一位。</p>
<p>问题:对于连续若干个数字高位都是 1,直接对连续区间合并,是否是最优的。</p>
<p>证明如下:<br />
连续区间先进行合并,如果没有剩余,答案为<code class="language-plaintext highlighter-rouge">x-1</code>。<br />
还有剩余,之后两边随便选择一个数字进行合并,答案为 <code class="language-plaintext highlighter-rouge">x</code>。</p>
<p>如果不对连续区间合并,则是区间一分为两半,左边的区间与左边的边界数字合并,右边的区间与右边的边界数字合并,答案为 <code class="language-plaintext highlighter-rouge">x</code>。</p>
<p>由此可以证明,连续区间合并,答案是最优的。</p>
<p>PS:比赛的时候我证明错了,证明的是贪心有反例,就放弃这道题了。</p>
<p>根据上面的贪心策略,从最高位开始,依次枚举,尝试消除这一位,看 k 次操作是否可行。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛最后一题其实不难,只要想到连续区间可以直接合并,从高位枚举即可。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
失误了。
leetcode 第 381 场算法比赛,排名45
2024-01-21T18:13:00+08:00
2024-01-21T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/01/21/leetcode-contest-381
<h2 id="零背景">零、背景</h2>
<p>leetcode 是越来越聪明了,现在只出两道题,相同题目,一道数据范围小,一道数据范围大。</p>
<p>A+C: 贪心,出现次数最多的字母放在最前面。<br />
B+D: 构造,分环前、环中、环后三种情况统计步长的个数,区间求和。</p>
<p>PS:这次比赛的时候,电脑开机失败,折腾了好久,比赛开始十几分钟才开机成功。</p>
<h2 id="一输入单词需要的最少按键次数-i">一、输入单词需要的最少按键次数 I</h2>
<p>题目:以前的老式手机是物理按键,按键上有多个字母,根据字母的位置,通过按多次可以按出字母。<br />
问如何重新排列字母,从而使得输入一个字符串按键次数最少。</p>
<p>思路:贪心。</p>
<p>统计每个字母出现的次数,显然次数越多的字母需要尽量排在最前面。</p>
<p>2到9共8个按键。<br />
频次最高的8个字母分别排在第一位。<br />
频次排名处于9到16的字母,分别排在第二位。<br />
依次递推即可。</p>
<h2 id="二按距离统计房屋对数目-i">二、按距离统计房屋对数目 I</h2>
<p>题目:街道上有一排房屋,相邻的房屋之间有条街道。<br />
现在某两个房屋之间建了一个街道,某两个房屋的最少街道个数称为距离。<br />
问距离为1~n的房屋对分别有多少个。</p>
<p>思路:构造体。</p>
<p>可以发现,某个点出发,到达非环上点的距离是连续递增的,环上的点有两条路,也是依次递增的。</p>
<p>只看一个直线,直线上随意一点可以分别向两端走,距离分别是 1 到尽头。<br />
只看一个环,环上任意一点可以朝两个方向走,距离也是从1到相遇,所以环上的距离是环大小除2,奇数有一个多走一步。</p>
<p>这些距离可以使用区间加一的数据结构来维护,例如树状数组或者线段树。<br />
当然,由于不需要求区间和,更简单的做法是维护一个加一减一标记。</p>
<p>由此,我们可以枚举每个顶点为起点,分环前、环中、环后三种情况统计,求区间分别加一即可。</p>
<h2 id="三输入单词需要的最少按键次数-ii">三、输入单词需要的最少按键次数 II</h2>
<p>与第一题一样。</p>
<h2 id="四按距离统计房屋对数目-ii">四、按距离统计房屋对数目 II</h2>
<p>与第二题一样。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛只有两道题,由于比赛电脑开机失败,浪费十几分钟,不然有可能进入前10名的。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
只有两道题。
leetcode 第 380 场算法比赛
2024-01-14T18:13:00+08:00
2024-01-14T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/01/14/leetcode-contest-380
<h2 id="零背景">零、背景</h2>
<p>这个周末有点事情,周六睡的非常晚,比赛就随便打了下。<br />
第三题使用数位DP做,公式写复杂了,比赛期间没做出来。</p>
<p>A: 统计频率,选最大频率以及出现的次数。<br />
B: 预处理 B 字符串出现的位置集合,再判断 A 出现的位置是否是答案。<br />
C: 规律题,根据规律直接推导答案或者二分查找答案。<br />
D: 与第二题一样,使用KMP或者hash计算子串出现的位置。</p>
<h2 id="一最大频率元素计数">一、最大频率元素计数</h2>
<p>题意:给一个数组,问所有具有最大频率的元素的总频率。</p>
<p>思路:预处理统计,然后扫描计算出现的最大频率以及个数,相乘就是总频率。</p>
<h2 id="二找出数组中的美丽下标-i">二、找出数组中的美丽下标 I</h2>
<p>题意:給三个字符串 S、A、B,问 S 中子串为 A 起始下标满足要求的个数。<br />
要求:A 的起始下标与某个子串为 B 的起始下标距离不大于 K。</p>
<p>思路:预处理计算所有的 B 子串下标。<br />
对于每个子串 A, 在 B 的下标集合里二分查找最近的下标,判断距离。</p>
<p>小技巧:距离有两个,可以二分查找第一个大于的,减一就是最后一个小于等于的。</p>
<h2 id="三价值和小于等于-k-的最大数字">三、价值和小于等于 K 的最大数字</h2>
<p>题意:给一个整数 k 和 x, 求一个数字 num,使得 1 到 num 所有数字的特殊1的个数之和小于等于 k。<br />
特殊1定义:一个数字的二进制bit是 x 的倍数,且那一位为1,则个数加1。</p>
<p>思路:先分析二进制的规律,发现以 x 位为整体,可以从右上角特殊矩阵在不断嵌套扩大。</p>
<p><img src="https://res2024.tiankonguse.com/images/2024/01/14/001.png" alt="" /></p>
<p>针对这个规律,有两种方案来解决这个问题。</p>
<p>方法1:按列处理。</p>
<p>如果我们可以快速一个 1到num 的特殊1个数之和,就可以通过二分查找来找到 num。</p>
<p>按列比特位来分别判断 x 位、<code class="language-plaintext highlighter-rouge">2x</code>位、<code class="language-plaintext highlighter-rouge">3x</code>位直到无穷大位出现 1 的个数,求和接口。</p>
<p>对于具体某一位 <code class="language-plaintext highlighter-rouge">X</code> 特殊1的个数,也是有规律的。<br />
即每 <code class="language-plaintext highlighter-rouge">2^X</code> 个数字,出现 <code class="language-plaintext highlighter-rouge">2^X/2</code> 个 1。<br />
解释:这些数字分为两半,前一半高位都是 0, 后一半高位都是 1,故出现个数是一半个。</p>
<p>所有对于 num, 统计有多少个 <code class="language-plaintext highlighter-rouge">2^X</code> 即可将完整的区域都计算。<br />
对于的预期,可以判断是否超过一半来处理,没超过个数就是 0 个;超过时,超过多少个,就有多少 1。</p>
<p>方案2:数位DP。</p>
<p>数位 DP 比较复杂,文字不好介绍清楚,这里就不展开介绍了。</p>
<h2 id="四找出数组中的美丽下标-ii">四、找出数组中的美丽下标 II</h2>
<p>题意:与第二题一样。</p>
<p>思路:数据范围很大,不能枚举查找子串,需要进行优化。</p>
<p>方法1:hash 加速。</p>
<p>通过 hash 快速枚举找到所有子串,之后就与第二题没区别了。</p>
<p>PS:上个月在《<a href="https://mp.weixin.qq.com/s/e5kPXWb989-Op3COSiPA5w">字符串 hash</a>》介绍过字符串 hash,套模板即可。</p>
<p>方法2:通过 kmp 快速找到子串,之后就与第二题没区别了。</p>
<h2 id="五最后">五、最后</h2>
<p>对于较复杂的算法或数据结构,使用纯文字很难介绍清楚。</p>
<p>在想要不要增加增加一个视频来辅助讲解这些算法。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
数位DP,想复杂了。
leetcode 第 379 场算法比赛,差点进前30名
2024-01-01T18:13:00+08:00
2024-01-01T18:13:00+08:00
https://github.tiankonguse.com/blog/2024/01/07/leetcode-contest-379
<h2 id="零背景">零、背景</h2>
<p>2024 年的第一场比赛,有点坑人,差点进入前 30 名。</p>
<p>A: 签到题,寻找最大值。<br />
B: 棋盘判断,情况分析、递归,枚举都可以。<br />
C: 数据结构题。<br />
D: 动态规划。</p>
<h2 id="一对角线最长的矩形的面积">一、对角线最长的矩形的面积</h2>
<p>题意:给一些矩阵,求矩阵对角线最长的矩阵,如果对角线相等,返回面积最大的矩阵。</p>
<p>思路:记录当前最大的对角线和面积,按题意循环比较即可。</p>
<p>小技巧:可以记录面积的平方,从而避免记录带有小数的对角线。</p>
<h2 id="二捕获黑皇后需要的最少移动次数">二、捕获黑皇后需要的最少移动次数</h2>
<p>题意:有一个<code class="language-plaintext highlighter-rouge">8*8</code>的国际象棋棋盘,给你三个棋子的坐标,一个白色的车,一个白色的象,一个黑色的皇后,问只移动白色棋子,最少多少步可以杀死黑色皇后。<br />
规则1:车可以上下左右走直线,但不能跳过其他棋子。<br />
规则2:象可以走45度的斜线,但不能跳过其他棋子。</p>
<p>方法1:深度优先记忆化搜索。</p>
<p>定义一个<code class="language-plaintext highlighter-rouge">map[x0][y0][x1][y1]</code> 的状态,代表两个白色棋子的位置,搜索判断两个棋子走所有可能得步骤,求出最优值。</p>
<p>关键点:如何避免死循环。<br />
一个方法:默认所有状态标记未计算,当开始计算时,先标记当前状态答案为无穷大。由此可以避免形成环。</p>
<p>时间复杂度:<code class="language-plaintext highlighter-rouge">O(4 * 8* 8^4)</code><br />
解释:状态有<code class="language-plaintext highlighter-rouge">8^4</code>个,状态转移两个棋子可移动的线有 2 条,每条最多移动 8 格。</p>
<p>方法2:分类讨论。</p>
<p>不考虑障碍物,如果一步无法杀死皇后,车至少两步就可以到达皇后的位置。<br />
有障碍物之后一个,故答案是最多两步可以杀死皇后。</p>
<p>那什么皇后可以一步杀死皇后呢?<br />
皇后恰好在车或象一步行动方向的直线上,且中间没有障碍物。</p>
<p>如果中间有障碍物,第一步障碍物先移动走,第二步就满足一步杀死皇后了。</p>
<p>综合上面的三类情况,可以判断一步可以杀死皇后时,答案是一步,否则答案是两步。</p>
<p>我们可以分情况分析三个棋子的位置情况,计算答案。</p>
<p>不过三个棋子的相对情况非常多,手动分类讨论会死人的。</p>
<p>方法3:枚举。</p>
<p>通过枚举一步可以到达的坐标,判断是否有答案即可。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(32)</code></p>
<h2 id="三移除后集合的最多元素数">三、移除后集合的最多元素数</h2>
<p>题意:给两个集合,问分别从两个集合中选 k 个数字,如何才能使得选的数字组成的新集合大小最大。</p>
<p>思路:两个集合优先选择各自的差集,最后不够了再从交集中取即可。</p>
<h2 id="四执行操作后的最大分割数量">四、执行操作后的最大分割数量</h2>
<p>题意:给一个字符串,每次需要删除一个集合大小为 k 的前缀,删除次数称为最大分割数。<br />
现在可以修改字符串的一个字符,问如何修改才能使得最大分隔数更大。</p>
<p>思路:分类讨论的动态规划。</p>
<p>假设状态是 <code class="language-plaintext highlighter-rouge">dp[N][flag]</code> 代表后 N 个字符在是否使用修改的情况下的最优值。</p>
<p>如果不使用修改次数,则状态转移方程固定为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>next_N = last_set_count(N, k);
dp[N][flag] = max(dp[N][flag], 1 + dp[next_N][flag]);
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">last_set_count</code> 的含义是查找从下标 N 开始,向后查找最大的集合,使得集合的大小为 k,返回下一个坐标。</p>
<p>如果 k 不等于 26,而且 flag 为可以修改,则修改的状态方程需要分情况讨论</p>
<p>首先找到第 k 个不同元素的首次出现的位置。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>next_N = last_set_count(N, k-1) + 1;
</code></pre></div></div>
<p>情况1:前面有重复数字,可以修改一个重复的数字,从而使得当前位置的值是第 <code class="language-plaintext highlighter-rouge">k+1</code> 个不同数字。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if(next_N - N > k){
dp[N][0] = max(dp[N][0], 1 + dp[next_N - 1][1]);
}
</code></pre></div></div>
<p>情况2:下个数字与前缀集合有重复数字,可以修改前缀的重复数字,使得当前位置满足<code class="language-plaintext highlighter-rouge">last_set_count</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dp[N][0] = max(dp[N][0], 1 + dp[next_N][1]);
</code></pre></div></div>
<p>情况3:下个数字与前缀集合有重复数字,可以修改下个数字,使得当前位置满足<code class="language-plaintext highlighter-rouge">last_set_count</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for (int i = 0; i < 26; i++) {
if (has(pre, i)) continue;
const char oldChar = s[next_N];
s[next_N] = 'a' + i; // 保存值
dp[N][0] = max(dp[N][0], 1 + dp[next_N][1]);
s[next_N] = oldChar; // 恢复值
}
</code></pre></div></div>
<p>三种情况结合起来,就是完整的动态转移方程。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛最后一题确实有难度,需要分情况写动态规划,比赛的时候只有 30 人做出来了。<br />
我第二题花费了太多时间,做第四题时时间不够,做出来后比赛已经结束十分钟了。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
第二题被坑了。
leetcode 第 378 场算法比赛
2023-12-31T18:13:00+08:00
2023-12-31T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/31/leetcode-contest-378
<h2 id="零背景">零、背景</h2>
<p>2023 年 leetcode 最后一场周赛,第四题竟然是超级大模拟题,我敲代码速度慢,最后没敲完最后一题。</p>
<p>A:判断偶数个数是否大于2个。<br />
B:预处理相同字符长度,二分求最优答案。<br />
C:与第二题一模一样。<br />
D:预处理前缀,两个线段分6种情况处理。</p>
<h2 id="一检查按位或是否存在尾随零">一、检查按位或是否存在尾随零</h2>
<p>题意:给若干数字,判断偶数的个数是否大于2个。</p>
<p>思路:统计偶数的个数即可。</p>
<h2 id="二找出出现至少三次的最长特殊子字符串-i">二、找出出现至少三次的最长特殊子字符串 I</h2>
<p>题意:求至少出现 3 次的特殊子串,如果存在多个,返回长度最长的。<br />
特殊子串:子串的字符都相等。</p>
<p>思路:特殊子串的字符都相等,所以不同字符之间就没啥关系。<br />
假设存在一个长度为 <code class="language-plaintext highlighter-rouge">A</code> 的特殊子串,则长度为<code class="language-plaintext highlighter-rouge">a</code>的特殊子串,出现次数为 <code class="language-plaintext highlighter-rouge">A-a+1</code>。</p>
<p>我们可以预处理统计每个字符连续最长特殊子串出现的次数,遍历一遍即可求出指定长度特殊子串在原字符串中出现的次数。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o"><</span><span class="n">map</span><span class="o"><</span><span class="n">len</span><span class="p">,</span> <span class="n">count</span><span class="o">>></span> <span class="n">stat</span><span class="p">;</span>
</code></pre></div></div>
<p>之后二分答案,枚举每个字符是答案时,是否满足至少出现 3 个。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bool Check(int mid) {
if (mid == 0) return true;
for (auto& m : stat) {
ll num = 0;
for (auto [k, v] : m) {
if (k < mid) continue;
num += (k - mid + 1) * v;
if (num >= 3) return true;
}
}
return false;
}
</code></pre></div></div>
<h2 id="三找出出现至少三次的最长特殊子字符串-ii">三、找出出现至少三次的最长特殊子字符串 II</h2>
<p>与第二题一模一样,相同代码提交两次即可。</p>
<h2 id="四回文串重新排列查询">四、回文串重新排列查询</h2>
<p>题意:给一个偶数长度的字符串和若干查询,每个查询包含前后一半字符串的区间,问区间内的字符重排序,是否可以使得字符串变成回文串。</p>
<p>思路:题意有点抽象,简单理解就是字符串分为前后相等长度的前后缀。</p>
<p>问前缀有个区间的字符可以随意打乱,后缀有个区间的字符可以随意打乱,前后缀组成新的字符串是否是回文串。</p>
<p>回文转两个字符串</p>
<p>做这道题时,默认思路是直接按回文串处理,这个时候前后缀的下标是对称的。<br />
其实还有另一种简洁的思路,即按两个字符串处理,这个时候两个下标就一样了。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/31/001.png" alt="" /></p>
<p>对于两个修改区间,共分为 6 种情况。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/31/002.png" alt="" /></p>
<p>稍微一分析,可以发现,如果两个区间不区分先后的话,第1种和第6种等价,第2种和第5种等价,第3种和第4种等价。</p>
<p>所以,我们可以按两个修改区间的左边界进行判断,要求第一个区间的左边界比第二个小,这样就变成 3 种情况了。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">range1</span><span class="p">.</span><span class="n">l</span> <span class="o"><=</span> <span class="n">range2</span><span class="p">.</span><span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ans</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">Solver</span><span class="p">(</span><span class="n">range1</span><span class="p">,</span> <span class="n">range2</span><span class="p">,</span> <span class="n">stat1</span><span class="p">,</span> <span class="n">stat2</span><span class="p">));</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">ans</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">Solver</span><span class="p">(</span><span class="n">range2</span><span class="p">,</span> <span class="n">range1</span><span class="p">,</span> <span class="n">stat2</span><span class="p">,</span> <span class="n">stat1</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>前后缀判断是否相等</p>
<p>首先,不管哪种情况,都需要先判断前后缀是否相等。<br />
前后缀不相等,区间内如何修改都没有答案。</p>
<p>怎么判断呢?<br />
可以预处理前后缀是否相等即可。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">minLeft</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">range1</span><span class="p">.</span><span class="n">l</span><span class="p">,</span> <span class="n">range2</span><span class="p">.</span><span class="n">l</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">maxRight</span> <span class="o">=</span> <span class="n">max</span><span class="p">(</span><span class="n">range1</span><span class="p">.</span><span class="n">r</span><span class="p">,</span> <span class="n">range2</span><span class="p">.</span><span class="n">r</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">preCmp</span><span class="p">[</span><span class="n">minLeft</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">// 左边不对称</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">subCmp</span><span class="p">[</span><span class="n">maxRight</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">// 右边不对称</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/31/003.png" alt="" /></p>
<p>剩下的区间,就需要分情况判断了。</p>
<p>分为三种情况:</p>
<p>1)区间内,一个字符串不需要修改,另一个可以随意排列。对应两个区间有重叠时的差集。<br />
2)区间内,两个字符串都可以随意排列。对应两个区间有重叠时的交集。<br />
3)区间内,两个字符串都不能排列。对应两个区间没有重叠时,中间的空白区间。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/31/004.png" alt="" /></p>
<p>不过不断那种情况,处理之前,需要先统计区间内字符的个数。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">count1</span><span class="p">(</span><span class="mi">26</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">vector</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">count2</span><span class="p">(</span><span class="mi">26</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">26</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">count1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">stat1</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">maxRight</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">stat1</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">minLeft</span> <span class="o">-</span> <span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">];</span>
<span class="n">count2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">stat2</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">maxRight</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">stat2</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">minLeft</span> <span class="o">-</span> <span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>
<p>另外,不管那种情况,都有情况1,即一个字符串要修改,另一个不需要修改。</p>
<p>以左半部为例,<code class="language-plaintext highlighter-rouge">l1<l2</code>,说明 s1 需要修改, s2 不需要修改。<br />
两个统计数据都减去不需要修改的区间字符,判断剩余的统计是否还能保持一致。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// 步骤1:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">l1</span> <span class="o"><</span> <span class="n">l2</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 左半部对齐</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">26</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [l1, l2-1]</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">num2</span> <span class="o">=</span> <span class="n">stat2</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">l2</span> <span class="o">-</span> <span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">stat2</span><span class="p">.</span><span class="n">preStat</span><span class="p">[</span><span class="n">l1</span> <span class="o">-</span> <span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">];</span>
<span class="n">count1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-=</span> <span class="n">num2</span><span class="p">;</span>
<span class="n">count2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-=</span> <span class="n">num2</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">count1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">count2</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">l1</span> <span class="o">=</span> <span class="n">l2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>左右的差集都处理后,就剩余中间的部分了。</p>
<p>一个是有重叠,一个是没重叠。</p>
<p>有重叠直接判断剩余的统计是否相等即可。</p>
<p>没有重叠时,比较复杂,需要判断区间子串是否相等</p>
<p>这个该怎么判断呢?</p>
<p>普通的预处理,需要预处理任意区间是否有答案,复杂度是<code class="language-plaintext highlighter-rouge">O(n^2)</code>。<br />
这种情况,不管是空间复杂度还是时间复杂度,都超了。</p>
<p>一种优化是使用线段树或者树状数组来优化,复杂度<code class="language-plaintext highlighter-rouge">O(n log(n))</code>。</p>
<p>线段树的话,直接储存区间内是否相等即可。<br />
树状数组的话,需要储存区间内不相等的个数或者相等的个数,前缀通过求差即可判断得到答案。</p>
<p>其实我们也可以通过预处理前缀来判断,复杂度<code class="language-plaintext highlighter-rouge">O(n)</code>。<br />
预处理前缀时,记录最近一个不相等字符的位置。<br />
区间查询时,通过最近一个不相等字符的位置和左边界比大小即可知道区间内是否有不相等的字符。</p>
<p>由此,所有情况都处理完成。</p>
<p>PS:有重叠区间和没重叠处理有差异,没有重叠的区间可以特殊处理一下。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛第三题二分不算很难,最后一题是个大的模拟题了。<br />
不仅要分情况处理,对于区间没有交集时,还需要判断区间子串是否相等,没想到前缀<code class="language-plaintext highlighter-rouge">O(n)</code>算法的话,只能上线段树或树状数组了。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
超级大模拟题。
2023 年个人总结
2023-12-29T18:13:00+08:00
2023-12-29T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/29/summary-2023
<h2 id="零背景">零、背景</h2>
<p>个人总结主要是写给自己看的,是对过去一年自己全部经历进行的汇总,并对过去进行反思,对未来进行规划,从而希望未来过得更好。</p>
<p>2023 年,疫情放开的第一年,经历了很多很多事情,简单回忆一下。</p>
<p>往年总结列表:《<a href="https://mp.weixin.qq.com/s/u8wv84UcmcpnxkxnNBc3Bw">2022年总结</a>》、《<a href="https://mp.weixin.qq.com/s/7ODQPGwO0gq1xyPf-IiiGA">2021年总结</a>》、《<a href="https://mp.weixin.qq.com/s/bk5_TqnzodlqyDOkJfLsoA">2020年总结</a>》、《<a href="https://mp.weixin.qq.com/s/_P9Rdw09YNkWj--UfojXvQ">2019年总结</a>》、《<a href="https://mp.weixin.qq.com/s/qqRlR8C0KLYl4JMCVif-Jg">2018年总结</a>》。</p>
<h2 id="一工作">一、工作</h2>
<p><strong>变动</strong></p>
<p>2022 年年中的时候,工作发生变动大变动,换了总监和组长。<br />
2023 年,变动依旧在持续,上半年离开了2个同事,年中换了总监,下半年离开了2个同事,年末组长也走了。<br />
如今团队只剩下 6 个人,我临时带领着团队继续前行。</p>
<p>由于团队由于下半年共计走了 3 个人,也找老板申请了 3 个 HC,过完年来就开始面试招人,感兴趣的可以年后投简历。</p>
<hr />
<p><strong>成长</strong></p>
<p>2023 年,作为员工,团队的风格我已经掌握了点皮毛,下面是我简单罗列几条。</p>
<p>1、做任何事情要先与领导对齐目标与预期完成时间。<br />
2、目标确定后,要快速有大致方案,然后与领导对齐大致方案。<br />
3、方案确定后,就要想办法在规定时间内完成目标。时间到了没完成,不管任何理由,没完成就是没完成。<br />
4、多用数据与逻辑说话,即摆实事,讲道理,不糊弄,不混淆。<br />
5、数据与逻辑应该是从现状出发,最终推到出怎么完成目标。<br />
6、每个任务有负责人,负责人对任务结果负责。
7、凡事有结论,结论各方达成一致后就严格执行。<br />
8、遇到重大问题,尽早向上级汇报,带着解决方案去汇报。</p>
<p>作为员工,应该如何做事,其实我总结了很多零碎的片段经验,上面只是最近我认为比较重要的几个点。<br />
另外近一个月我作为团队负责人,也总结了一些经验。<br />
不过目前我认为目前分享这些还不是时机,等后面等到恰当的时机,我再分享员工如何做事,团队负责人如何管理的经验。</p>
<h2 id="二生活">二、生活</h2>
<p><strong>驾照</strong></p>
<p>今年终于决定考驾照,目前还算顺利,科目二已经通过。<br />
全过程我记录在《<a href="https://mp.weixin.qq.com/s/S4k69Jpv5zwjOh5TZ6Y59Q">配眼镜</a>》、《<a href="https://mp.weixin.qq.com/s/gKfmciAGBRh2sn6RkQzexw">报名</a>》、《<a href="https://mp.weixin.qq.com/s/KwamgBXXRpayicYh8hmk7g">科目一</a>》、《<a href="https://mp.weixin.qq.com/s/AfcxCtq-aZdujoBA5_Lbrg">科目二</a>》。</p>
<p>科目三教练说等预约上考试后再去练车,那就意味着只有 10 天的练车时间,我有点没有信心了。</p>
<hr />
<p><strong>旅游</strong></p>
<p>2023 年旅游去了很多地方。</p>
<p>5月份和家人去了<a href="https://mp.weixin.qq.com/s/7NolCuZp1xDMWY3eE7x8fw">成都与青城山4日游</a>。<br />
7月份和家人去了<a href="https://mp.weixin.qq.com/s/cZ1zumtevWGpoG_DzVWfQg">广州4天3夜游</a>。<br />
8月上旬和家人去了<a href="https://mp.weixin.qq.com/s/uW_EPpWTzKrfKbAAp7RE0Q">长沙3天2夜游</a>。<br />
8月下旬和家人去了<a href="https://mp.weixin.qq.com/s/U_yiI164HiwU_Dg_CieB3g">武汉3天2夜游</a>。<br />
11月份公司去了<a href="https://mp.weixin.qq.com/s/tJHD8HH8TYZ_Kn19Ab8WyA">珠海2天1夜团建</a>。</p>
<hr />
<p><strong>信用卡</strong></p>
<p>2023 年我办了两张信用卡,一张是<a href="https://mp.weixin.qq.com/s/v8C3SvLYW9XWx3Cg0foXvQ">农行的精粹白</a>,一张是<a href="https://mp.weixin.qq.com/s/h7yB5bwnavWhynA_8Ax--g">工行的奋斗白</a>。</p>
<p>农行的精粹白全国的商务贵宾室免费用,而且还可以免费带一个人。<br />
工行的奋斗白,送6点龙腾和6次快速安检,每点龙腾可以兑换一次休息室或者兑换一次免费吃饭。</p>
<p>当然,2023年我只花费了1点龙腾,兑换的是免费吃饭,记录在《<a href="https://mp.weixin.qq.com/s/sMgRo1GlGiKAtsKQ7T2DvA">机场快速安检与龙腾吃饭</a>》,其他的只能白白过期了。</p>
<h2 id="三家庭">三、家庭</h2>
<p>2023 年,家里的小孩开始上学,去了最好的学校。</p>
<p>学校里的孩子学习成绩都很好,如果自己的孩子排名处于最后,家长的压力就会很大。</p>
<p>这也导致,在教育孩子的过程中,家里人对小孩的期望很高,从而对孩子产生很大的心理压力。</p>
<p>针对这个问题,目前没有发现好的解法。</p>
<p>一种可能得解法是优化家庭教育孩子的方法,这个家庭也在尝试中。</p>
<p>对于家庭,年初的时候,定了两个目标。<br />
一个是家人旅游,完成了。<br />
另一个是给家人体检,没有完成。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/007.png" alt="" /></p>
<h2 id="四健康">四、健康</h2>
<p><strong>生病</strong></p>
<p>2023 年感冒了 5 次,以至于我没有去记录每次感冒的过程,只记录了第一次和最后一次(如果最后两天不感冒的话)。</p>
<p>第一次记录在《<a href="https://mp.weixin.qq.com/s/BTfGbyP0xZSTcOKdMfUbjw">肠胃炎诺如病毒,中招会呕吐与拉肚子</a>》。<br />
最后一次记录在《<a href="https://mp.weixin.qq.com/s/RhTWAT2Wc8KV-iN2r1zvDg">往年1年感冒1次,2023年感冒无数次</a>》。</p>
<p>幸运的是,我的所有生病症状都比较轻,所以都没有吃药。</p>
<hr />
<p><strong>疫苗</strong></p>
<p>最后一次感冒好了之后,我就赶紧去打了流感疫苗,记录在文章《<a href="https://mp.weixin.qq.com/s/_B00HsRYtvUpnLzlSiizkg">第一次打流感疫苗</a>》。</p>
<p>我也做了一个决定,以后每年秋季都去打一次流感疫苗。</p>
<hr />
<p><strong>健身</strong></p>
<p>2023 年健身依旧保持着,正常的进行着,不过健身的内容发生了变化。</p>
<p>我是每天早上早起去健身房健身,持续30~40分钟,10分钟用来跑步,其他时间用来力量训练。</p>
<p>由于全年力量训练都比较随意,从来没有练到力竭,所以力量也没有什么提升。</p>
<p>11 月份曾计划每天健身后进行拉伸,但是开始考驾照后,我就没怎么拉伸了,两个好像也没啥关系,但就是暂停了。</p>
<hr />
<p><strong>攀岩</strong></p>
<p>除了早起运动外,我每周还会去攀岩一到两次。</p>
<p>2023 年最后一次攀岩是 10月24日(天呢,已经2个月多没攀岩了)。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/023.png" alt="" /></p>
<p>2022 年最后一次攀岩是 12月2日。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/024.png" alt="" /></p>
<p>两次攀岩的记录求差,就可以知道我过了多少条线。</p>
<p>V5级别:5条
V4级别:55条
V2-3级别:251条</p>
<p>当然,由于V2太简单了,一次会过很多,制作视频太消耗时间,后面V2-V3到达500条时就不录V2了。</p>
<p>攀岩时除了爬线,2024 年6月份开始,我做出了一个大胆的尝试:邀请一些攀岩比较厉害的岩友,给大家分享公开课。</p>
<p>7月份由于时间关系,我自己也尝试分享攀岩私教课。</p>
<p>不过这些在万圣节之后,都暂停了。</p>
<p>一方面是工作上比较忙,另一方面是因为我开始考驾照,周末两天都要去练车。</p>
<p>这也导致我万圣节之后,有两个月没攀岩了。</p>
<hr />
<p><strong>饮食</strong></p>
<p>我的饮食习惯已经固定,每天吃的相差不大。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/001.png" alt="" /></p>
<hr />
<p><strong>睡眠</strong></p>
<p>2023 年睡眠还是偏少,总结下分两方面。</p>
<p>一方面是自己晚上刷B站和小红书,偶尔刷的很晚,从而导致睡得比较晚,为了保证睡眠质量,只能放弃第二天的早起锻炼。<br />
另一方面是做攀岩视频比较花费时间,不过大多数时候也没那么晚,早上还是可以早起锻炼。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/002.png" alt="" /></p>
<hr />
<p><strong>体脂</strong></p>
<p>2023年,我意外了解到深圳就可以使用 DEXA 来测量体脂,我就去测量了一次。</p>
<p>可能最近一年运动量降低的缘故,体脂升高到 12.6%。</p>
<p>详细数据记录在文章《<a href="https://mp.weixin.qq.com/s/wTKry9HT4htIZagPetFd9w">2023年 DEXA 测量体脂率愿望实现</a>》,涉及到好多数据,不过我也看不懂。</p>
<hr />
<p><strong>体检</strong></p>
<p>今年去的香港大学深圳医院进行的体检,胆固醇和尿酸一直都正常。</p>
<p>香港大学深圳医院的体检集中在一个地方,转一圈就全部体检完,体验挺好的。</p>
<hr />
<p><strong>眼睛</strong></p>
<p>今年为了考驾照,去配了一副眼镜。</p>
<p>带上眼镜,整个世界都清晰多了,几十米外人脸上的痣都可以看的很清楚。</p>
<hr />
<p><strong>耳朵</strong></p>
<p>2021年和2022年的年度总结时,我都提到怀疑听力有问题,但是迟迟没有去检查。</p>
<p>今年遇到一个恰当的机会,我便去检查了下听力和耳朵边的疙瘩,记录在文章《<a href="https://mp.weixin.qq.com/s/oi9swsVWQgOSy2VruT8H2Q">2023年医院检查耳朵小记</a>》。</p>
<p>检查结果是听力没问题,耳朵的疙瘩不需要处理。</p>
<hr />
<p><strong>手腕</strong></p>
<p>我的右手腕的骨头上有一个疙瘩,在去检查耳朵的时候,也去检查了下手腕,记录在《<a href="https://mp.weixin.qq.com/s/o9NzVpOhpWmJmJPGK-3rqw">2023年医院检查手腕小记</a>》。</p>
<p>检查结果是手腕没问题,不过评论和朋友圈有人说可能是腱鞘囊肿。</p>
<hr />
<p><strong>牙齿</strong></p>
<p>2023 年,我也开始使用牙线棒,记录在《<a href="https://mp.weixin.qq.com/s/q1e-7qaE9CQF2tR67bZCXw">洗牙后决定买牙线棒了</a>》。</p>
<p>之前吃蔬菜或者吃肉,我会经常塞牙。<br />
有了牙线棒,可以轻轻松松把牙缝里的菜和肉都弄出来,方便极了。<br />
现在一日三餐我都使用牙线棒,已经离不开牙线棒了。</p>
<hr />
<p><strong>其他</strong></p>
<p>今年家里人有人突然中风,由于没有及时吃药,后遗症影响到了生活。<br />
由此,我也更加重视健康这一话题,要坚持不抽烟、坚持不喝酒、坚持健身、坚持健康饮食、坚持保障睡眠时间。</p>
<p>另外,今年我也不小心被小狗咬了,去打了狂犬疫苗。<br />
得到的教训是以后不要去随便摸小狗和小猫。</p>
<hr />
<p><strong>目标完成情况</strong></p>
<p>年初的时候,健康定的目标比较简单,我都完成了。</p>
<p>关于牙齿矫正,初步查阅资料,发现矫正后复发的概率很高,便放弃矫正了。
如果有矫正过的朋友,可以说下自己的经验以及态度,是否建议校准,我也会参考你们的意见的。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/006.png" alt="" /></p>
<h2 id="五理财">五、理财</h2>
<p>我现在理财不买股票,只买指数。</p>
<p>2023 年,我维护了两个策略,一个是A股实操,一个是定了一个模拟基金。</p>
<p>模拟基金上半年选择了 10 个ETF,国内股票相关基金的权重过大,导致亏得比较多。<br />
下半年进行调整,降低到 5 个ETF,提高了标普500的权重。<br />
最终年度收益率 -8.31%。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/003.png" alt="" /></p>
<p>A股实操是跟着模拟基金来买卖的,不过由于有时候忘记买入,有时候资金不够,两边有一定的 gap。<br />
最终年度收益率 -7.24%。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/004.png" alt="" /></p>
<hr />
<p><strong>目标完成情况</strong></p>
<p>年初定的是不做啥操作,读两本理财书,读两个公司的财报,调研一个公司的产业链。</p>
<p>第一个不做啥操作确实没有啥操作,算是达标了。<br />
第二个读两边理财书,最终只读完了一本。<br />
第三个读财报和第四个调研产业链都没有启动。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/008.png" alt="" /></p>
<h2 id="五读书">五、读书</h2>
<p>2023 年阅读时长继续下降,降到了 66.5小时。</p>
<p>读到书也降到 10 本以下,只读了 8 本书。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/005.png" alt="" /></p>
<p>分析原因,2023 年做的事情太多,分散了精力,也导致读书时长严重下滑。</p>
<p>年初定的目标是读 100 小时的书,实际只读了 66.5 小时,没有完成目标。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/009.png" alt="" /></p>
<h2 id="六文字">六、文字</h2>
<p>2023 年共写了 168 篇文章,读书笔记 6 篇,算法 59篇,项目与技术 21 篇,生活 41 篇,AI 41 篇。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/010.png" alt="" /></p>
<p>写了一个脚本把自己公众号的文章数据都抓下来,可以发现最受欢迎额文章还是机场贵宾厅和chatGPT的话题。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/013.png" alt="" /></p>
<p>年初定的目标是写 30 篇技术文章,完成了 70%。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/011.png" alt="" /></p>
<h2 id="七算法">七、算法</h2>
<p>算法方面,全年 leetcode 周赛都尽力参加了,有事没参加时,事后也补回来,并且都写题解了。</p>
<p>其中两次比赛进去前50名,第 339 场比赛排名39,第362场比赛排名33.</p>
<p>算法有三个目标。<br />
第一个是周赛,完成度 100%。<br />
第二个是每日打卡,后面放弃了,因为发现打卡的题没有意义。<br />
第三个是补充往日比赛30场,实际只补充了1场,完成度3%。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/012.png" alt="" /></p>
<h2 id="八视频">八、视频</h2>
<p>2023 年继续分享攀岩视频。</p>
<p>其中爬线视频共 89 个,公开课视频 22 个,私教课视频 25 个,攀岩周边视频 13 个,1个无关视频。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/014.png" alt="" /></p>
<p>在B站里面,攀岩视频,最受欢迎的是公开课和私教课。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/015.png" alt="" /></p>
<p>相同的视频,在小红书里,数据比B站好很多。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/016.png" alt="" /></p>
<h2 id="九短视频">九、短视频</h2>
<p>2023 年,刷了很多小红书和B站的视频。</p>
<p>可惜小红书没有年度报告,只有旅游报告和搜索报告。</p>
<p>B 站的报告如下:</p>
<p>B站打开次数 353 次,观看视频时长 262 小时(读书只有 66 小时,惭愧)。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/017.png" alt="" /></p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/018.png" alt="" /></p>
<p>观看视频分类为动画、电影、知识、体育、游戏。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/019.png" alt="" /></p>
<p>动漫主要是一拳超人和海贼王。<br />
电影的话主要是看电影解说。<br />
游戏的话是看英雄联盟比赛。<br />
运动的话是攀岩。<br />
知识的话就比较杂乱无章了。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/020.png" alt="" /></p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/021.png" alt="" /></p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/29/022.png" alt="" /></p>
<h2 id="十最后">十、最后</h2>
<p>回顾 2023 年,涉及的主题有工作、家庭、生病、健身、攀岩、睡眠、理财、读书、文章、创作视频、刷短视频。</p>
<p>其中完成了几个的目标:</p>
<p>1)使用 DEXA 检查体脂<br />
2)配眼镜<br />
3)检查听力<br />
4)检查耳朵后面的疙瘩<br />
5)检查手腕上的疙瘩<br />
6)报名考驾照<br />
7)旅游打卡几个城市</p>
<p>那对于 2024 年,我的目标如下</p>
<p>工作方面,学习项目管理与管对管理,争取带领团队做出一些成绩。</p>
<p>家庭方面,优化孩子教育方法,带着父母去做全面的身体检查。</p>
<p>生活方面,争取春节之前考到驾照,办一张更优权益的信用卡,去几个城市旅游。</p>
<p>健康方面,继续打流感疫苗。</p>
<p>饮食方面,继续保持即可。</p>
<p>睡眠方面,现在只有7小时,争取提高到8小时。</p>
<p>健身方面,继续拉伸柔韧性,以及重启力量训练。</p>
<p>读书方面,与去年的目标保持一致,至少读书 100 小时。</p>
<p>理财方面,不做过多操作,继续简化指数个数,保持不变即可。</p>
<p>文字方面,技术文章至少 30 篇。</p>
<p>算法方面,周赛继续维持,新增 20 篇算法讲解或者非 leetcode 算法讲解。</p>
<p>攀岩方面,重启公开课与私教课,提高柔韧性,爬 NC 攀岩馆的 10 条 V5。</p>
<p>其他方面,还有一些其他的想法,但是现在还不方便公开,未来做到的时候,再来公开吧。</p>
<p>不知不觉写了 8 千多字,感谢你看到这里,祝你新年快乐。</p>
<p>最后问大家两个问题:</p>
<p>2023 年你过得怎么样呢?<br />
2024 年你的规划又是什么呢?</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
家庭、工作、健康(运动/饮食/睡眠)、理财,这些是永恒的主题。
读《潜沙记》
2023-12-28T18:13:00+08:00
2023-12-28T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/28/read-sand-hugh-howey
<h2 id="一背景">一、背景</h2>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/000.png" alt="" /></p>
<p>8月份读了一本小说,名字叫做《潜沙记》,简单写下书评。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/001.png" alt="" /></p>
<h2 id="二书评">二、书评</h2>
<p>《潜沙记》读了一半,第一个想到的是《进击的巨人》。</p>
<p>《进击的巨人》里有个装备叫做立体机动装置,可以飞行。<br />
《潜沙记》里有个潜沙服,可以把沙漠的沙子变成水一样软,从而可以像潜水一样潜沙。</p>
<p>《进击的巨人》主角村庄之外存在科技发达的城市。<br />
《潜沙记》一样,在外面存在科技发达的城市。</p>
<p>同样,不知什么原因,科技发达的城市要毁灭主角的村庄。</p>
<p>文中说为了防止村庄的人不断去发达的城市,但缺少更多的说明,所以逻辑上很牵强。</p>
<p>故事以发达城市毁灭结束,以城市之间的仇恨报复依旧在持续。</p>
<p>总结:一般,不推荐这本书。</p>
<h2 id="三剧透">三、剧透</h2>
<p>《潜沙记》是一部依靠主角光环发展的一部小说。</p>
<p>第一部分通过主角帕尔默,让我们认识到书中的世界。</p>
<p>这是一个充满沙子的世界,古老的高科技文明全部被沙子掩盖,人类现在唯一的科技就是潜沙动力装置。<br />
通过潜沙动力装置,可以做出一个潜沙服,从而可以在沙子里自由的潜水。<br />
也可以做出一个轮船,从而可以在沙子上自由穿行。</p>
<p>主角帕尔默作为一个潜沙者,被一群神秘的陌生人雇佣,求挑战潜沙的深度,寻找传说中的失落的城市“丹佛”。<br />
最终,帕尔默和同伴找到了“丹佛”,地下城市里面有可以呼吸的空气。</p>
<p>但是同伴的潜沙服坏了,并抢走了主角帕尔默的潜沙装备,留下主角帕尔默独自一人在深深的地下等死。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/002.png" alt="" /></p>
<p>第二部分介绍主角的家庭。</p>
<p>帕尔默住在一个绿洲附近。<br />
帕尔默的妈妈是一个妓女,开了一个妓院养活家庭。<br />
帕尔默的父亲曾经是绿洲的领主,后来不知什么原因,丢下全家人独自去了神秘的东方城市。<br />
帕尔默的姐姐是一个潜沙高手,但已经嫁人,很多年没回家了。<br />
帕尔默还有两个弟弟,都没有成年,依靠妓女妈妈每周给钱来养活自己。</p>
<p>帕尔默的家庭有一个传统,每年的指定日期,都需要去一个叫做无人之地的地方露营。<br />
帕尔默的父亲就是在露营那晚,穿过那个无人之地,去远方的神秘地方。</p>
<p>之后的每一年,这个家庭还会去无人之地露营,只为能等待爸爸回来。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/003.png" alt="" /></p>
<p>第三部分介绍帕尔默与姐姐的相遇。</p>
<p>帕尔默在“丹佛”这个几千年的地下城市寻找出路,真的遇到了一个怪物。</p>
<p>原来帕尔默在潜沙之前,雇佣者曾说过之前有两个人下去了,但是没有上来。</p>
<p>既然有人下来,那就意味者这两个人有潜沙装备,如果拼凑起来,有机会回到地面。</p>
<p>结果自然是主角帕尔默把对方打倒,拼齐了装备,然后在极限状态下回到了地面。</p>
<p>回到地面后,帕尔默并没有马上找吃的与逃跑,而是在偷听雇佣者的谈话。</p>
<p>原来这群雇佣者要从古老的城市里寻找原子弹,然后要把主角的家乡炸了。</p>
<p>不出意外的话,肯定会出意外。</p>
<p>把所有话都听完后,主角被对方发现了。</p>
<p>于是主角在没有吃喝补给的情况下,继续逃亡。</p>
<p>帕尔默的姐姐突然被人追杀,回到家乡寻找原因,发现有人在追杀弟弟帕尔默以及相关的人。<br />
由此,姐姐推断出帕尔默在沙漠里,赶紧去沙漠寻找帕尔默。</p>
<p>这么大的沙漠,姐姐找到了无数天没有吃喝的帕尔默。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/006.png" alt="" /></p>
<p>第四部分介绍帕尔默的妹妹。</p>
<p>帕尔默的两个弟弟按照每年的约定,去无人之地露营。</p>
<p>其中的大弟弟本来规划这天也像爸爸那样去无人之地的东方,不再回来。</p>
<p>结果刚走不远,就遇到一个女孩子,竟然喊自己哥哥,并准确的说出了自己的名字。</p>
<p>后面大家了解到,这个女孩子确实是妹妹,不过是同父异母的妹妹。</p>
<p>爸爸去到东方后,就被抓起来当做工人去做苦工了,期间有了一个女儿。</p>
<p>爸爸为了告诉家乡的人不要来东方,日积月累手搓了一个潜沙服,并计算好时间,让女儿在这个约定的日期,寻找家人。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/004.png" alt="" /></p>
<p>第五部分介绍姐姐的复仇。</p>
<p>雇佣兵拿着原子弹来到了主角的家乡,先使用炸弹把城墙炸毁,让沙子淹没了一半的城市,然后拿着原子弹去炸另一半城市。</p>
<p>结果不用想都能猜到,雇佣兵被姐姐全部反杀,原子弹没有炸。</p>
<p>考虑到较发达的城市可能还会派人来炸这个地方,姐姐带着原子弹去到发达城市,把发达城市炸了。</p>
<p>家人在无人之地看着东方,期望看到姐姐回来,但是等了很多天,迟迟没有看到姐姐的回来。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/28/005.png" alt="" /></p>
<h2 id="四最后">四、最后</h2>
<p>这本小说看着心中会有一堆问题,可能是因为这个小说是一个系列。</p>
<p>等后面把其他关联的小说都看了,整个故事才能串起来吧。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
一个类似于《进击的巨人》的小说。
【算法讲解】字符串 hash 之逆元
2023-12-27T18:13:00+08:00
2023-12-27T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/27/string-hash-two
<h2 id="零背景">零、背景</h2>
<p>Leetcode 算法比赛的题解中,我经常提到一个字符串 hash 这个算法。</p>
<p>上面文章《<a href="https://mp.weixin.qq.com/s/e5kPXWb989-Op3COSiPA5w">【算法讲解】字符串 hash</a>》介绍了最初级的 hash 算法。</p>
<p>这篇文章稍微介绍一个高级的字符串 hash, 逆元。</p>
<h2 id="一模运算">一、模运算</h2>
<p>介绍 hash 算法之前,需要先介绍两个模运算的规则。</p>
<p>逆元:如果存在一个 x, 使得 <code class="language-plaintext highlighter-rouge">(a * x) % p = 1</code>,则 x 称为 <code class="language-plaintext highlighter-rouge">a % p</code> 的逆元,记作<code class="language-plaintext highlighter-rouge">a^-1</code></p>
<p>大整数除法:假设 <code class="language-plaintext highlighter-rouge">a/b</code> 可以整除,但是 <code class="language-plaintext highlighter-rouge">a</code> 和 <code class="language-plaintext highlighter-rouge">b</code> 都是大整数,如何求 <code class="language-plaintext highlighter-rouge">(a/b)%p</code>的答案呢?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> (a / b) % p
= (a / b) % p * 1
= (a / b) % p * (b * b^-1) % p
= ((a / b) *( b * b^-1)) % p
= (a * b^-1) % p
</code></pre></div></div>
<p>由此可以推导出下面的公式。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(a / b) % p = (a * b^-1)%p
</code></pre></div></div>
<h2 id="一前缀-hash">一、前缀 hash</h2>
<p>正常的 hash 左边是高位,右边是低位,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int pre = 0;
for(int i=1;i<=n;i++){
int vi = Val[i] - '0;
pre = (pre * 10 + vi) % mod;
preHash[i] = pre;
}
</code></pre></div></div>
<p>如果左边是低位,右边是高危,则写法如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int pre = 0;
for(int i=0;i<n;i++){
int vi = Val[i] - '0;
pre = (pre + vi * pow(10, i, mod)) % mod;
preHash[i] = pre;
}
</code></pre></div></div>
<h2 id="二区间-hash">二、区间 hash</h2>
<p>如果我们想要计算字符串区间<code class="language-plaintext highlighter-rouge">val[3,5]</code>的 hash 值,根据上面的算法,可以推论出</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pre[5] = v0*10^0 +v1*10^1 + v2*10^2 + v3*10^3+ v4*10^4 + v5*10^5
preHash[5] = pre[5] % mod
pre[2] = v0*10^0 +v1*10^1 + v2*10^2
preHash[2] = pre[2] % mod
目标 = (v3*10^0+ v4*10^1 + v5*10^2) % mod
= (pre[5] - pre[2]) / 10^3 % mod
= ((pre[5] % mod) - (pre[2] % mod)) / 10^3 % mod
= (preHash[5] - prehash[2]) / 10^3 % mod
</code></pre></div></div>
<p>从上面的公式中可以看到,只需要两个 hash 前缀可以直接相减,之后还需要进行左移若干次。</p>
<p>PS:上面公式正确性很容易证明,先把 <code class="language-plaintext highlighter-rouge">10^3</code> 转化为逆元即可证明。</p>
<p>假设一个数字的逆元记为<code class="language-plaintext highlighter-rouge">inv(a)</code>,则区间子串的 hash 算法如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll RangeHash(int l, int r) { // [l, r]
l--; //(l, r]
const int lr = r - l;
const ll R = preHash[r];
const ll L = preHash[l];
return (R - L ) * inv(10^lr) % mod;
}
</code></pre></div></div>
<h2 id="三逆元计算">三、逆元计算</h2>
<p>根据费马小定理,可以知道 <code class="language-plaintext highlighter-rouge">a^(p-1) % p = 1</code>。<br />
对 <code class="language-plaintext highlighter-rouge">a^(p-1)</code> 拆出一个 <code class="language-plaintext highlighter-rouge">a</code>,即可得出结论 <code class="language-plaintext highlighter-rouge">a * a^(p-2) % p = 1</code>。<br />
即 <code class="language-plaintext highlighter-rouge">a</code> 的逆元是 <code class="language-plaintext highlighter-rouge">a^(p-2)</code>。</p>
<p>故我们可以使用快速幂来计算逆元。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll qpow(ll x, ll v, ll mod) {
x = x % mod;
ll y = 1;
while (v) {
if (v & 1) y = y * x % mod;
x = x * x % mod;
v >>= 1;
}
return y;
}
ll inv(ll x, ll mod) { return qpow(x, mod - 2, mod); }
</code></pre></div></div>
<h2 id="四最后">四、最后</h2>
<p>好了,字符串的两种区间 hash 都介绍完了。</p>
<p>两种区间 hash 优化后时间复杂度其实是等价的,大家可以根据比赛的实际情况来选择。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
算法比赛中一个基础的知识点。
【算法讲解】字符串 hash
2023-12-26T18:13:00+08:00
2023-12-26T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/26/string-hash
<h2 id="零背景">零、背景</h2>
<p>Leetcode 算法比赛的题解中,我经常提到一个字符串 hash 这个算法。</p>
<p>那这个算法到底是什么意思呢?</p>
<p>今天详细讲解一下。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/26/001.png" alt="" /></p>
<h2 id="一模运算">一、模运算</h2>
<p>介绍 hash 算法之前,需要先介绍一下模运算的规则。</p>
<p>可重入: <code class="language-plaintext highlighter-rouge">(a % p) % p = a % p</code><br />
分配率: <code class="language-plaintext highlighter-rouge">(a + b) % p = (a % p + b % p) % p</code><br />
逆运算: <code class="language-plaintext highlighter-rouge">(a * a^-1) % p = 1</code><br />
除法: <code class="language-plaintext highlighter-rouge">(a / b) % p = (a * b^-1)%p</code></p>
<p>当然,这篇文章暂时不展开介绍逆元。</p>
<h2 id="一基础场景">一、基础场景</h2>
<p>字符串 hash 基础的场景是大整数字符串,前缀 hash 值伪代码如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int pre = 0;
for(int i=1;i<=n;i++){
int vi = Val[i] - '0;
pre = (pre * 10 + vi) % mod;
preHash[i] = pre;
}
</code></pre></div></div>
<p>如果我们想要计算字符串区间<code class="language-plaintext highlighter-rouge">val[3,5]</code>的 hash 值,根据上面的算法,可以推论出</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pre[5] = v1*10^4 + v2*10^3 + v3*10^2+ v4*10^1 + v5*10^0
preHash[5] = pre[5] % mod
pre[2] = v1*10^1 + v2*10^0
preHash[2] = pre[2] % mod
目标 = (v3*10^2 + v4*10^1 + v5*10^0) % mod
= (pre[5] - pre[2] * 10^3) % mod
= ((pre[5] % mod) - (pre[2] * 10^3) % mod) % mod
= (preHash[5] - prehash[2] * 10^3) % mod
</code></pre></div></div>
<p>从上面的公式中可以看到,把整个前缀当做一个大整数,较小的前缀需要进行左移,对齐最高位,从而可以求差,即可求出一个子串的 hash 值了。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/26/002.png" alt="" /></p>
<p>由此就可以写出区间子串的 hash 算法了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll RangeHash(int l, int r) { // [l, r]
l--; //(l, r]
const int lr = r - l;
const ll R = preHash[r];
const ll L = preHash[l] * pow10[lr] % mod;
return (R - L + mod) % mod;
}
</code></pre></div></div>
<h2 id="二通用场景">二、通用场景</h2>
<p>大整数的hash算法有一个缺陷,例如<code class="language-plaintext highlighter-rouge">1</code>和<code class="language-plaintext highlighter-rouge">01</code>的 hash 值相同。<br />
所以大整数 hash 算法仅适用于题意明确说明是数字字符串的类型上。</p>
<p>对于英文字母字符串也一样,如果第一个字母 <code class="language-plaintext highlighter-rouge">a</code> 当做 0, <code class="language-plaintext highlighter-rouge">a</code> 和 <code class="language-plaintext highlighter-rouge">aa</code>的哈希值就会一样。</p>
<p>所以这个 hash 算法需要稍微进行调整,所有数字需要从 1 开始计数,这样就不会有重复值了。</p>
<p>对于纯数字,需要改成大于 10 的数字为幂底,例如选择 11。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int pre = 0;
for(int i=1;i<=n;i++){
int vi = Val[i] - '0' + 1;
pre = (pre * 11 + vi) % mod;
preHash[i] = pre;
}
</code></pre></div></div>
<p>而对于纯小写字母,可以改成 27 为幂底,不过我习惯上选择质数为幂底,比例 29。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int pre = 0;
for(int i=1;i<=n;i++){
int vi = Val[i] - 'a' + 1;
pre = (pre * 29 + vi) % mod;
preHash[i] = pre;
}
</code></pre></div></div>
<p>字符串的区间 hash 也可以轻松写出来了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ll RangeHash(int l, int r) { // [l, r]
l--; //(l, r]
const int lr = r - l;
const ll R = preHash[r];
const ll L = preHash[l] * pow29[lr] % mod;
return (R - L + mod) % mod;
}
</code></pre></div></div>
<h2 id="四最后">四、最后</h2>
<p>好了,字符串 hash 和区间字符串 hash 算法已经介绍完了。</p>
<p>这里暂时没有介绍 hash 的逆元,因为当前的 hash 只需要使用减法和乘法即可得到对应的 hash 值。</p>
<p>后面有机会,在介绍带逆元的 hash 算法。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
算法比赛中一个基础的知识点。
leetcode 第 377 场比赛第四题
2023-12-25T18:13:00+08:00
2023-12-25T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/25/leetcode-minimum-cost-to-convert-string-ii
<h2 id="零背景">零、背景</h2>
<p>上篇文章《<a href="https://mp.weixin.qq.com/s/6Lp0XZ4FQfTDVT0OfuXl2Q">leetcode 第 377 场算法比赛</a>》提到,最后一题我使用<code class="language-plaintext highlighter-rouge">O(1000^2)</code>的复杂度超时了。</p>
<p>于是我参考了下其他人的代码,继续优化了下算法,分享下优化思路。</p>
<p>比赛代码:<br />
https://github.com/tiankonguse/leetcode-solutions/tree/master/contest</p>
<h2 id="一题意">一、题意</h2>
<p>题意:给一个字符串映射到另外一个字符串的代价,问原始字符串是否可以替换为目标字符串,求最小代价。<br />
规则:原始字符串到目标字符串的映射过程中,映射的子串之间不能有交集(相等除外)。</p>
<p>基本思路:</p>
<p>第一步:对映射的字符串进行hash处理,转化为数字之间的映射,并求全源最短路。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(200^3)</code></p>
<p>第二步:对原始字符串和目标字符串的所有子串进行预处理,求出对应的hash值。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(1000^2)</code></p>
<p>第三步:动态规划,枚举判断原始字符串后缀是否可以替换到目标字符串后缀。<br />
方程:<code class="language-plaintext highlighter-rouge">dp[i] = min(dp[j-1] + cost[fromHash][toHash] )</code><br />
复杂度:<code class="language-plaintext highlighter-rouge">O(1000^2)</code></p>
<h2 id="二全源最短路优化">二、全源最短路优化</h2>
<p>全源最短路的复杂度最高。</p>
<p>由于点数最多200,边数最多只有 100 个,换其他最短路算法可以更优。</p>
<p>例如使用 SPFA+堆 单源最短路优化,复杂度只与边的个数有关。<br />
复杂度降为:<code class="language-plaintext highlighter-rouge">O(200^2)</code></p>
<h2 id="三hash-优化">三、hash 优化</h2>
<p>我们计算了任意子串之间的 hash 值,空间复杂度和时间复杂度都是<code class="language-plaintext highlighter-rouge">O(1000^2)</code>。</p>
<p>其实可以只计算前缀的 hash,子串的 hash 可以通过两个前缀的差值来计算。</p>
<p>差值计算公式</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>f(l,r) = (f(0,r) - f(0,l-1) * base^(r-l+1)) % mod
</code></pre></div></div>
<p>前缀和base的幂都可以预处理,复杂度:<code class="language-plaintext highlighter-rouge">O(1000)</code><br />
计算子串hash的复杂度:<code class="language-plaintext highlighter-rouge">O(1)</code></p>
<h2 id="四动态规划优化">四、动态规划优化</h2>
<p>默认动态规划是枚举所有长度,复杂度是 <code class="language-plaintext highlighter-rouge">O(1000^2)</code></p>
<p>实际上,替换的点只有 100 个,说明可以替换的字符串长度最多最有 100 个。</p>
<p>故可以只枚举字符串长度集合。</p>
<p>复杂度:<code class="language-plaintext highlighter-rouge">O(1000*100)</code></p>
<h2 id="五最后">五、最后</h2>
<p>优化后,各步骤的优化效果如下</p>
<p>第一步:求全源最短路
优化前:<code class="language-plaintext highlighter-rouge">O(200^3)</code>
优化后:<code class="language-plaintext highlighter-rouge">O(200^2)</code></p>
<p>第二步:计算hash值<br />
优化前:<code class="language-plaintext highlighter-rouge">O(1000^2)</code><br />
优化后:<code class="language-plaintext highlighter-rouge">O(1000)</code></p>
<p>第三步:动态规划<br />
优化前:<code class="language-plaintext highlighter-rouge">O(1000^2)</code><br />
优化后:<code class="language-plaintext highlighter-rouge">O(1000*100)</code></p>
<p>最终优化完,使用 176ms 通过了这道题。<br />
优化前耗时至少 176ms,稍微加点常数,耗时就超过 2 秒了。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/25/001.png" alt="" /></p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
继续优化,降低常数。
leetcode 第 377 场算法比赛
2023-12-23T18:13:00+08:00
2023-12-23T18:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/24/leetcode-contest-377
<h2 id="零背景">零、背景</h2>
<p>周六科目二成功考试通过,周日科目三预约的是下午练车,所以上午有时间参加比赛。</p>
<p>这次最后一题是和综合题,但是总体复杂度不高,但是不知为啥总是超时。</p>
<p>A: 排序<br />
B: 预处理垂直线,枚举水平线<br />
C: 全源最短路。<br />
D: hash+全源最短路。</p>
<p>比赛代码:<br />
https://github.com/tiankonguse/leetcode-solutions/tree/master/contest</p>
<h2 id="一最小数字游戏">一、最小数字游戏</h2>
<p>题意:两个人轮流做游戏,每一轮A和B依次移除最小数字,然后B和A依次把数字追加到数组中。</p>
<p>思路:排序,交换相邻数字即可。</p>
<h2 id="二移除栅栏得到的正方形田地的最大面积">二、移除栅栏得到的正方形田地的最大面积</h2>
<p>题意:给一个矩阵,现在告诉你若干水平线和垂直线,问是否可以删除一些水平线和垂直线,让剩下的线构成至少一个正方形。<br />
如果可以,返回最大的正方形面积。</p>
<p>思路:如果可以构成正方形,肯定需要两根水平线和两根垂直线。<br />
所以需要枚举两根水平线和两根垂直线。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n^4)</code></p>
<p>优化1:上面枚举没有利用正方形的性质。<br />
两根水平线确定后,正方形的边长就确定了。<br />
所以只需要再枚举一根垂直线,另一根垂直线的位置就确定了(上下两个可能)。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(n^3)</code></p>
<p>优化2:确定正方形边长时,能不能直接判断是否有垂直线答案呢?<br />
预处理垂直线,记录所有可能的边长,即可直接判断。<br />
预处理复杂度:<code class="language-plaintext highlighter-rouge">O(n^2)</code><br />
枚举复杂度:<code class="language-plaintext highlighter-rouge">O(n^2)</code></p>
<h2 id="三转换字符串的最小成本-i">三、转换字符串的最小成本 I</h2>
<p>题意:给一个字符映射到另外一个字符的代价,问原始字符串是否可以替换为目标字符串,求最小代价。</p>
<p>思路:字符只有26个,合并输入映射,可以得到26个字母之间的输入映射代价。<br />
然后通过 floyd 求全源最短路,即可以得到任意两个字母之间的最小映射代价。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(26^3)</code></p>
<p>之后判断输入字符串是否可以转化为目标字符串即可。</p>
<h2 id="四转换字符串的最小成本-ii">四、转换字符串的最小成本 II</h2>
<p>题意:给一个字符串映射到另外一个字符串的代价,问原始字符串是否可以替换为目标字符串,求最小代价。<br />
规则:原始字符串到目标字符串的映射过程中,映射的子串之间不能有交集(相等除外)。</p>
<p>思路:第三题的升级版本。</p>
<p>第一步:对映射的字符串进行hash处理,转化为数字之间的映射,并求全源最短路。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(200^3)</code></p>
<p>第二步:对原始字符串和目标字符串的所有子串进行预处理,求出对于的hash值。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(1000^2)</code></p>
<p>第三步:动态规划,枚举判断原始字符串后缀是否可以替换到目标字符串后缀。<br />
方程:<code class="language-plaintext highlighter-rouge">dp[i] = min(dp[j-1] + cost[fromHash][toHash] )</code><br />
复杂度:<code class="language-plaintext highlighter-rouge">O(1000^2)</code></p>
<p>就是这样一个算法,竟然超时了。</p>
<p>全源最短路的复杂度最高。<br />
由于边数最多只有 200 个,于是我不适用 floyd, 改成求每个顶点的最短路。<br />
但源最短路使用 SPFA 堆优化。<br />
复杂度:<code class="language-plaintext highlighter-rouge">O(200^2)</code></p>
<p>最终复杂度:<code class="language-plaintext highlighter-rouge">O(1000^2)</code><br />
但还是超时了。</p>
<p>我把所有 vector 由临时变量调整为全局数组,结果还是超时了。</p>
<h2 id="五最后">五、最后</h2>
<p>这次比赛最后一题被卡了,看其他人没有使用 hash 字符串,使用二分查找或者 tire 树来做的。<br />
其实 hash 的速度更快,只开了两个<code class="language-plaintext highlighter-rouge">O(1000^2)</code>的内存,其他的都是<code class="language-plaintext highlighter-rouge">O(200^2)</code>的内存,最终却超时了,莫名其妙。</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
O(1000^2) 的复杂度,莫名其妙超时。
考驾照(四):科目二
2023-12-23T19:13:00+08:00
2023-12-23T19:13:00+08:00
https://github.tiankonguse.com/blog/2023/12/23/driver-license-sign-class-two
<h2 id="一背景">一、背景</h2>
<p>在《<a href="https://mp.weixin.qq.com/s/S4k69Jpv5zwjOh5TZ6Y59Q">考驾照(一):配眼镜</a>》中提到,我打算考驾照,去配了眼镜。<br />
在《<a href="https://mp.weixin.qq.com/s/gKfmciAGBRh2sn6RkQzexw">考驾照(二):报名</a>》提到,我报名了一个驾校。<br />
在《<a href="https://mp.weixin.qq.com/s/KwamgBXXRpayicYh8hmk7g">考驾照(三):科目一</a>》提到,我科目一通过了,</p>
<p>今天,12月23日,我考试了科目二,顺利通过,简单记录一下期间的故事。</p>
<h2 id="二规则">二、规则</h2>
<p>手动挡(C1)考试内容为倒车入库、侧方停车、曲线行驶(S路)、直角转弯、坡道定点停车和起步(上坡)。</p>
<p>自动挡(C2)与手动挡相比,少一个坡道定点停车与起步(上坡)。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/003.png" alt="" /></p>
<p>满分为100分,C1和C2 都是 80 分合格。</p>
<p>考试结束后,需要确认考试成绩(签字),才能离开。</p>
<p>本次预约考试,第一次考试不合格时可以当场补考一次,不参加当场补考或补考仍不合格的,本次预约考试算不合格。<br />
申请人10日后可以再次预约考试。</p>
<p>科目二预约考试次数能超过5次,5次考试都不合格时,需要重新进行全流程考试(重新交报名费了)。</p>
<h2 id="三扣分标准">三、扣分标准</h2>
<h3 id="考试开始">考试开始</h3>
<p>未系安全带,扣100分<br />
启动发动机时非空挡,扣100分<br />
熄火,扣10分</p>
<h3 id="倒车入库">倒车入库</h3>
<p>不按规定路线行驶(开错道),扣100分<br />
车身出线,扣100分<br />
没有完全入库,扣100分<br />
前轮未过控制线,扣100分<br />
超过210秒,扣100分<br />
中途停车,扣10分</p>
<h3 id="侧方停车">侧方停车</h3>
<p>入库压线,扣100分<br />
完成时间超过100秒,扣100分<br />
行驶压线,扣10分<br />
出库没打灯或者打错,扣10分<br />
中途停车,扣5分</p>
<h3 id="曲线行驶">曲线行驶</h3>
<p>压线,扣100分<br />
中途停车,扣5分</p>
<h3 id="直角转弯">直角转弯</h3>
<p>压线,扣100分<br />
中途停车,扣5分<br />
出库没打灯或者打错,扣10分</p>
<h3 id="波动定点停车与起步">波动定点停车与起步</h3>
<p>没有停车,扣100分<br />
停车后,车前保险杠超过控制线50cm,扣100分<br />
停车后,车身与变现距离超出50cm,扣100分<br />
停车超过30秒,扣100分<br />
停车没拉手刹,扣10分<br />
停车后,车前保险杠超过控制线30cm,扣10分<br />
停车后,车身与控制线距离超出30cm,扣10分</p>
<h3 id="总结">总结</h3>
<p>扣5分:停车</p>
<p>扣10分:熄火,非S线行驶压线,灯使错误,上坡停车没拉手刹,上坡车前或车轮控制线30~50cm,车后溜30cm内</p>
<p>扣100分:其他</p>
<h2 id="四练车要预约">四、练车要预约</h2>
<p>报名后,下一个周六我就与教练说去练车,练科目二。</p>
<p>去之前,我与同事说起练车的事情,同事说一般都是大家排队练,每个人练半个小时下来休息。<br />
于是我以为直接去排队就可以了。</p>
<p>结果我一大早跑去后,教练在考场,说练车前先预约。<br />
我恍然大悟,确实是我不对,凡事要有预约商量时间。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/001.png" alt="" /></p>
<h2 id="五练车">五、练车</h2>
<p>11月4日教练教了我倒车入库后,就说让我回去好好学科目一,把科目一过了再说。</p>
<p>11月15日科目一考试通过后,那个周末我离开深圳回家了一趟,所以没有练车。</p>
<p>之后的每个周六和周日都会去练车,工作日去了3个晚上练车。</p>
<p>回顾整个过程。<br />
倒车入库我连续练了4天。<br />
侧方停车练了1天。<br />
之后的1天教练把直角转弯、曲线行驶、上坡一次全教了。 <br />
再之后的所有时间全部是转圈。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/002.png" alt="" /></p>
<h2 id="六考前分析">六、考前分析</h2>
<p>考前我分析过科目二的几个考试内容。</p>
<p>所有的考试内容都需要方向盘对准指定位置,这个我练了这么久的车,已经轻松掌握了。</p>
<p>而倒车入库和侧方停车需要看左右倒车镜,且需要在倒车镜中对准某些指定坐标点来操作方向盘。</p>
<p>那座椅和倒车镜是否调好就最重要了。<br />
没调好必然压线,那就必然挂科了。</p>
<p>座椅,教练说头上留一个拳头的距离,这个很好调整,靠在座位上去调整即可。</p>
<p>左右倒车镜,教练说的是可以刚好看到后车门的把手,但是头会上下左右动的,不同角度后把手看到的位置也不一样。</p>
<p>这就导致倒车镜很难有一个标准的流程来调整到正确的位置。</p>
<p>这个是我最担心的,模拟考试和考试的时候,都遇到倒车镜的问题了。</p>
<p>看下公告的内容,挂科率挺高的。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/012.png" alt="" /></p>
<h2 id="六模拟考试">六、模拟考试</h2>
<p>12月8日的时候,我的学时够了,可以预约考试了,可以选择的是12月183日到23日,我预约了周六12月23日。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/004.png" alt="" /></p>
<p>12月21日,教练让我科目二付费。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/005.png" alt="" /></p>
<p>12月22日,我提前问下教练考试的安排。<br />
由于我是下午考试,我最终选择上午先去驾校练车,之后去考场模拟练车,最后考试。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/006.png" alt="" /></p>
<p>另外,我也咨询了下模拟练车的费用和规则。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/007.png" alt="" /></p>
<p>12月23日,我7点半起床,去驾校练车。</p>
<p>10点半的时候,到达考场,发现这天模拟练车的人特别多,上午模拟练车已经全部预约满了。</p>
<p>之前的周六周末都是没啥人模拟练车,所以教练没预约模拟练车。</p>
<p>10点半到11点半,教练让我一个人在模拟考场转。<br />
我转了很多圈,分析了各个车道的规律,除了 S 弯入口不一样,其他的差别都不大。</p>
<p>于是我便去公告处看考场的示意图,发S弯入口比较远,这样就是说考场所有赛道区别不是很大了。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/013.png" alt="" /></p>
<p>11点半到12点半,由于一直没人让出模拟车,教练让我坐在另外一个教练的手动挡学生的车上,感受一下考试流程。<br />
我坐在车里跟着转了几圈,发现那个学生倒车入库时各种压线,等待时各种熄火。<br />
那个教练也没有去分析学员为啥压线,而是说车肯定没问题,肯定是你人的问题。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/008.png" alt="" /></p>
<p>12点半,教练发现有个人预约没来且超过半个小时了,赶紧去找工作人员,于是我便可以练车了。</p>
<p>上车后,第一个是倒车入库,我一下就压线了,语音提示考试不合格,我蒙圈了。</p>
<p>我分析了一下,对教练说,原因在于这个车的离合与驾校车的离合差异很大。 、
教练说不同车的离合和刹车差别很大的。</p>
<p>驾校的车离合很紧,松开很多才会跑,离合稍微踩下去一点,车就快速减速了。<br />
而模拟车的离合很轻,稍微一松,车就跑的很快。<br />
车跑起来后,离合踩下去没有任何减速,需要轻踩刹车才能减速。</p>
<p>接下来侧方停车,我又压角了,语音又提示考试不合格。</p>
<p>教练分析了一下,说我倒车镜有问题,需要调整下角度。 <br />
于是教练让我不要看倒车镜,直接看感应线的柱子,那个是最准的。</p>
<p>直角转弯,外侧又压线了。</p>
<p>教练问我什么时候转的方向盘?<br />
我说线与方向盘对齐时,在驾校一直这样练的。<br />
教练说需要稍微向前一点,对齐门把手。<br />
我分析道,这个车的离合与练的车不一样,应该主要原因还是我车看的太快了。</p>
<p>上坡不记得有没有问题了。</p>
<p>下坡后,语音一直在重复着播报考试不合格。</p>
<p>一圈下来,我算是摸熟了这个车的离合松紧程度。</p>
<p>不过第二圈,我侧方停车又压线了。</p>
<p>教练说让你看柱子你不看,侧方停车后面不要在看倒车镜了,直接看柱子就行了。</p>
<p>我问教练,这个倒车镜除了侧方停车需要,倒车入库也需要倒车镜,会不会影响倒车入库。<br />
教练告诉我,两边的倒车镜角度不一样的,左边需要看侧方的角,需要高一点,右边需要看底下,需要低一些。<br />
于是我把这个记下来。</p>
<p>这次下坡后,语音又一直在重复着播报考试不合格。</p>
<p>第三圈我练得好多了,不记得是否合格。</p>
<p>第四圈的时候,我确定全部合格了。</p>
<p>由于大部分车都在倒车入库外面排队,我的倒车入库没问题,教练就让我直接从前面练侧方停车。<br />
练了两小圈都没问题,教练就让我去考试去了。</p>
<h2 id="七考试">七、考试</h2>
<p>我预约的是 14:00~15:00 考试,13:30 我进入大厅签到的。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/009.png" alt="" /></p>
<p>领取签到号后,还是先免费寄存东西,之后进去大厅等待叫号。</p>
<p>我等了半个小时,约 14:00的时候,肚子饿的有点疼。<br />
看了下,,我是一百多号,现在排到90号,还有20多号。<br />
问工作人员预计多久可以排到,工作人员说差不多半个小时。<br />
于是我赶紧出去找便利店买点吃的,安全起见,我买的是没有任何味道的蛋糕面包。</p>
<p>之后就是漫长的等待,看着号离自己越来越近时,心中就会越来越紧张。<br />
实际上我等到 15:30,也就是等了 2 个小时才排到。</p>
<p>上车后,发现车已经启动了,不需要自己启动。<br />
先是系安全带,然后调整座椅和倒车镜。<br />
最后等待前面考试的人离开倒车入库考场。</p>
<p>前面的人已经走了,我放下手刹,松开离合,发现车不会前进。<br />
摸索一番,发现没挂挡。<br />
赶紧挂挡,稍微一松离合,车熄火了。</p>
<p>没听到喇叭说不合格,我赶紧挂挡,进入倒车入库继续考试。</p>
<p>车开过控制线后,我发现怎么都没办法挂上倒挡。<br />
试了好几次,车都是向前开。</p>
<p>不知道停了几分钟,终于挂上了。<br />
结果我忘记打死方向盘了,车直接入库压线了。</p>
<p>车上的喇叭马上就说车身压线,考试不合格。</p>
<p>我想着继续把后面的流程走完,喇叭又响了,说停止操作,等待操作员来开车。</p>
<p>就这样,第一次尝试,刚倒车入库就挂了,后面的流程没有体验。</p>
<p>操作员来之后,让我坐在车第二排。<br />
我向操作员反馈,这个车倒车挡有问题,挂不上去。<br />
操作员向我演示了几次,说没问题的。<br />
我便记下操作员的手势。</p>
<p>操作员说不要下车,你一会还是使用这辆车第二次考试。<br />
之后操作员就下车走了。</p>
<p>在等待的期间,车的喇叭一直在重复考试不合格。<br />
每一次都是对自己内心的刺激。</p>
<p>后面第二次考试终于开始了。<br />
上车后,要等前面的人倒车入库完成后我才能进去。<br />
这个时间段我没有按指纹,先一直在模仿操作员的手势挂倒挡,感觉用他的手势确实可以了。</p>
<p>由于这是最后一次机会,我内心一直告诫自己,开慢点。</p>
<p>由于倒车镜没调好,倒车入库车尾还是歪了。<br />
我便放弃定点点位操作了,直接自己看倒车镜与边线的距离,凭感觉去调方向盘,就这样顺利通过倒车入库。</p>
<p>侧方停车也一样,按教练说的,不看倒车镜,直接看柱子,顺利通过。</p>
<p>S弯没啥难度,保持着车盖与路线的交点,以及两边车轮与边线的距离,看着调即可,顺利通过。</p>
<p>直角转弯车速慢下来后,也是顺利通过。</p>
<p>上坡对我没啥难度,方向盘对齐点位,顺便通过左边倒车镜去掉距离没问题,保持着前进即可。</p>
<p>下坡时,我发现车速很慢很慢。<br />
我想抬起脚让车跑的快点,发现脚已经僵硬了,抬不起来了。</p>
<p>就这样,我用蜗牛的速度,把车开向黄线范围之外。</p>
<p>结果我还没开出黄线,喇叭就说我考试通过了。</p>
<p>考试完,需要去签字,之后会打印一个成绩单,我的成绩竟然是 100 分。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/010.png" alt="" /></p>
<h2 id="八最后">八、最后</h2>
<p>考完科目二,还是感觉倒车镜太重要了。</p>
<p>座位的前后、高低、后背的角度都会影响倒车镜的角度,从而影响倒车入库的角度。</p>
<p>如果上个人把倒车镜都调好,大家身高差不多,那就不需要调倒车镜了。</p>
<p>如果身高有差异,那就需要调整倒车镜到适合自己的位置。</p>
<p>另外一个比较重要的就是车本身,离合的差异相差很大,挂挡的开关查一下也很大,刚开始建议开慢点感受一下离合的灵敏度。</p>
<p>科目二我也记了每个考试内容的笔记,不过不同教练教的差异很大,这里就不分享出来了,大家以自己的教练说的为主。</p>
<p><img src="https://res2023.tiankonguse.com/images/2023/12/23/011.png" alt="" /></p>
<p>接下来就是学习科目三了。<br />
科目三我还没开始学,预约的明天去第一次学习。<br />
大家对科目三有啥建议呢?</p>
<p>《完》</p>
<p>-EOF-</p>
<p>本文公众号:天空的代码世界<br />
个人微信号:tiankonguse<br />
公众号ID:tiankonguse-code</p>
不同车离合差异很大,后车镜和座椅是关键。