千题只是目前的一个目标,希望能磨炼基本功并一直保持熟练,原创代码更新在github,在这里会进行简单总结:
---------------------------------------------------------------------------------------------------------------------------------
代码维护
GitHub - huqinwei/leetcode_practice: C++
文件说明:
vs2015工程
100题一个文件
Solution_1xx.h代表100-199题
Solution_11xx.h代表1100-1199题
TODO:考虑做一个检索,甚至只需要输入题号就能执行函数(但是有些函数甚至需要依赖leetcode提供接口去验证某些数字,所以再考虑)
---------------------------------------------------------------------------------------------------------------------------------
经验与思路整理:
时间复杂度:
其实刷题提速的关键就是明确哪些过程是必须要走的,哪些过程是可以省略的,并且有办法把它省略。
学习优先级大于造轮子:
刷题覆盖类型还不全面,就去参加了双周赛,结果大费周章设计各种容器各种参数,甚至优化容器的检索速度,最后发现速度瓶颈不在于此,方法也有比这代码更少更快的。所以强行做题还是偶尔,优先学一些现成思路和方案,效率会更高。
---------------------------------------------------------------------------------------------------------------------------------
题目索引:
1.
2.
leetcode 4 寻找两个正序数组的中位数
看到大多数人是用的sort,并且还在炫耀打败了python下9x%的人,感觉不太对,这题最好的预期给的是O(log(m+n)),即使笨一点,双指针,复杂度也就只有O(m+n)啊(取个巧,到一半就结束O((m+n)/2),而且我也不用真的插入那个容器内),但是把两个数组放一起sort,额。。。
随便几百个数据:
sort方法:cost time: 0.000389
双指针法:cost time: 1.3e-05
leetcode 48 . 旋转图像
其实很简单,但是有一次被问到,状态不好翻车了,首先排除cosx+siny的坐标轴转换法,下标没有负的,不好处理,很繁琐,还要处理一次小数,虽然90度的cos和sin还好。
试图直接用new_i=j,new_j=w-i-1之类的直接不对。
其实就是一个对flip操作的烂熟过程就搞定了,和翻转字符串类似,翻转矩阵即可。
翻转矩阵也有坑,左右flip和上下flip达不到“90度”的要求,试想手拿A4纸,左右flip,上下flip,长高颠倒了吗?!
想顺时针90度旋转矩阵,可以对角线翻折一下,再上下(左右)翻转一下,对角线翻转还有很多下标细节要推,总之是一个吃熟练度的题,或者至少要熟练思路。
.......
leetcode 213.打家劫舍2(解析见下方)
leetcode 567 字符串的排列 字母统计表对比+动态低复杂度更新
leetcode 707 设计链表:单链表即可,繁琐活,一致性,如果维护一个tail,速度会进一步提升。
leetcode 733 图像渲染:我从一开始就设计的完备逻辑,所以一次过了,后来试了取巧提速,失败了,简单说,数据集的一个因素要考虑进来,就是,oldColor==newColor,如此,你需要自己去维护visited,然后再单独去写一个结果。
.....
999.
5836(1976)到达目的地的方案数:
自己设计数据结构和冗长的参数列表,颇费了一番心思,最后超时了,改进后也只能卡在数据集29,DFS加动态设定最短路径阈值的方式还是不够快,我想到了一些BFS+DP的思路,但是还没实践,
这题我的核心思路是:利用dp给每个点设立淘汰机制,到达这个点的value取min,其余的过程都可以排除了,因为更远,题目只求最近的,如果利用这个性质,就考虑BFS更好了,另外就是当一条路径已经走过了某一点dp[i],到达了dp[i+1],这时候别的路线突然更新了dp[i],让它也无效了,是否有办法有必要把它也中断(与此同时,是否能拿到题目要求的信息)。
看题解是dijkstra+dp,说明做题还不够多,光费脑筋也不行,还是要先学会现有方法。
类型索引:
双指针:
可以变相当计数器用,例如19. 删除链表的倒数第 N 个结点
链表:
设计链表:单链表即可,繁琐活,一致性,如果维护一个tail,速度会进一步提升。
DFS&BFS:
我典型的DFS就是岛屿数量,需要注意逻辑的完备性,是否需要保持原始信息不动。
动态规划:
其实入门不难,就三点:知道状态是怎么迭代的,知道初始怎么设置,最重要的是,要知道到底要求的是什么东西。
DP的误区:无脑堆迭代公式,忘了求的目标是什么,只有时刻明确目标,才能设计好题解。
爬楼梯:
滑窗法直接算,pqr的滑窗概念,相似的还能用到fibonacci和tribonacci。
dp数组法,和滑窗本质一样,就是形式不同。设好初始值,每一层的可能性就是前两层的可能性的叠加。
求的是可能性。
最小花费爬楼梯:你的“当前状态”其实应该是走过所有楼梯的下一个位置,n节楼梯的结束位置是[n](起始0)。
求的是花费。
爬楼梯递归为什么能成立?有时候我就反思,dp[i-1]已经包含了dp[i-2],为什么dp[i]还能是dp[i-1]+dp[i-2]?因为dp[i-1]只包含了dp[i-2]走一步的可能性,现在dp[i]是从dp[i-2]走两步,所以爬楼梯的迭代过程本质是,每一个dp[i-1]只储存了能确定走到这个台阶i-1的所有可能性,而dp[i]可能由两个位置“一步”走过来,所以dp[i]是求和前两步的可能性是没错的。
P(i)=p(i-1)p(i-1走一步)+p(i-2)p(i-2走二步)
=P(i-2)p(i-2走一步)p(i-1走一步)+p(i-2)p(i-2走二步)
打家劫舍:
其实可以看做最大花费爬楼梯。和爬楼梯不太一样的是,爬楼梯没有互斥条件,互斥条件是打家劫舍难的一点,其实想明白也就简单了,每条状态i都是衡量如下条件得出来的:
max(dp[i-2]+dp[i],dp[i-1])
打家劫舍2
这里边有两个提升过程,是分两步完成的。第一个是难度提升,需要思考一下DP的本质,DP的本质是状态存储,是牺牲细节信息的,无论你找dp[i-1]还是dp[i],他都包含了nums[0],nums[0]都已经作为输入信息累积到这个dp数组内了,是无论如何也不能从DP中分离出来的。这时候,无脑套公式是没用的,解题需要求助于dp之外的trick——分两次dp,一次彻底不带nums[0],也就是dp1范围[0,n-2],dp2范围[1,n-1](最后一个数字下标是n-1)
第二个是速度提升,规划完了两个dp,最后max一下,就完成了最优解,但是我发现排名不靠前,4ms,因为我两个dp分别走了两个循环,我的复杂度是O(2n),虽然两个循环的区间不一样,但是也就首尾分别差1,其余全是重复。这时候把两个循环合并为一个循环,只是需要稍微给两个dp加不同的上下限控制if语句,就能把C++的速度提升到0ms,这是一个基于时间复杂度思考的过程。
已分享至github:int rob_II(vector<int>& nums);