做研发多年,对算法理解一直不够成体系,基本是每次在面试的时候才会去重点看算法,刷一些题,因此在这里,把我多年的总结发出来,希望晚辈站在一个高的位置学习。
最新链接:有道云笔记
--------------------------------------------
高频算法题——每次跳槽刷一遍
结合之前的面试经历和刷leetcode的经历,总计了113道高频算法题,建议在准备面试前的1-2周过一遍,这里尽量用简短的几个词,记录每道题的核心解法,帮助记忆。(文末附上之前求职时刷算法题的经历)
怎么用这个文档:
1)如果你还有1-2个月开始面试,那就跟着这个文档一道一道的写吧,看看里面说的解法是否符合你的预期,不符合,总结自己的思路即可,怎么简单,怎么有助于你记忆,就怎么总结。
2)如果你还有1-2周开始面试,那我相信你已经刷了不少题了,那就跟着这个文档一道一道过,思考自己能否写出来,感觉能写出来的,直接过下一道,感觉把握不大的,再写一遍吧。
同志们,加油冲啊,拿下高薪offer,就差最后这一步“强化记忆”了!
TOP 113
解法一:暴力算法,时间复杂度:n^2 ;解法2:定一个hashmap ,降低复杂度为N
两数相加:注意最后的进位不为0,
1:迭代,相同位的值可以想加,定一个进位值;
2:递归:定义一个递归方法,传入两个链表和进位值;
解法:定一个左指针和最大值,然后进行遍历值是否在hashmap里
滑动窗口题目:
3. 无重复字符的最长子串
30. 串联所有单词的子串
76. 最小覆盖子串
159. 至多包含两个不同字符的最长子串
209. 长度最小的子数组
239. 滑动窗口最大值
567. 字符串的排列
632. 最小区间
727. 最小窗口子序列
解法:hard级别难在怎么把代码写优雅
解法一:两个数组归并排序之后,取中间值;
解法二:通过切换加二分法查找,但是我没看懂
dp[i] [j] = dp[i + 1] [j - 1]. if s[i + 1] [j - 1] 注意这个循环的遍历,外层是len from 0 to n,内层是i from 0 to n,j = i + len
解法1:中心扩展法
解法2:动态规划
解法 dp[i] [j]:s的前i个和p的前j个是否匹配,dp[i] [0] = false, dp[0] [j] = dp[0] [j - 2] if p[j - 1] == '*' dp[0] [0] = true
时间复杂度:横纵查找 mn m 为数据里字符串平均长度,n为数量 优化方向是增加2分查找
解法:要关注数据重复的情况,时间复杂度:O(N^2)
解法:回溯解法
回溯相关文章汇总
题目 |
题解 |
题解 |
难度等级 |
全排列 |
两种实现+图解 |
中等 |
|
括号生成 |
两种实现+图解 |
中等 |
|
电话号码的字母组合 |
两种实现+图解 |
中等 |
|
复原IP地址 |
两种实现+图解 |
中等 |
|
N 皇后 |
两种实现+图解 |
困难 |
解法1: 遍历链表,然后遍历全部,找到倒数的几位;解法2:采用压栈的方式,先进后出,弹出的第几位删除; 解法3:双指针 时间复杂度都是O(N)
三种解题:1动态规划: 没太懂,
2.栈、解题:利用栈,(入栈,)出栈,然后判断栈是否为空,并统计最大长度;
3.额外的空间 : 注意栈存储下标,开始预先存储一个-1. 或者,利用两个计数器,left和right分别统计左括号和右括号的个数。左往右边找一遍、右往左找一遍;
不重复的元素挪到左边:循环对右边的元素往右移动,右迈两步,左边迈一步
解法:倍增运算,原理要了解到矩阵幂运算、快速乘;将复杂度从O(n) 降低到logn
解法1:需要辅助数组:matrix_new[j][n - i - 1] = matrix[i][j];
解法2:原地替换
解法:二分查找,在一个旋转后的数组里查找是否有目标值
解法:定义三个数组,通过双层for 循环;将数据映射到不同的下标,最后判断是否有重复数据;
解法:遍历生成,题目没太懂; 也可以走枚举查询;
hash表的思维:
就是找左边最大值(大于该元素)和右边最大值,两个取较小那个,再减去当前高度。 双指针。
有四种解决方案:1.按行取(超时) 2.按列取 3.动态规划 4.双指针 5.栈,和取有效括号一样
多种组合的动态规划: *符号:dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
Collections.swap 不断交换元素
解法:动态规划,和斐波那契数列题相似;还有其他解法没太懂,通项公式、矩阵快速幂
其他动规问题:
1. 746. 使用最小花费爬楼梯:消费版斐波那契数列:dp[i] 下标未i的最小花费; dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
2.62.不同路径 : 转移方程: dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; dp[i][j] 走到当前有多少种方法;
3.63.不同路径2:存在障碍物。 dp[i][j] = (obstacleGrid[i][j] == 0) ? dp[i - 1][j] + dp[i][j - 1] : 0;
4.64.最小路径和:不同路径加一起做为0 的判断
5.343.整数拆分: dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
6.不同的二叉搜索树:96:dp[i] += dp[j - 1] * dp[i - j];
7.不同的二叉搜索树II:95:这个就是想着怎么生成左子树,右子树。然后组合成新的树。
解:第一种方法,对字符串进行遍历,然后死循环匹配,完全匹配上返回下标,时间复杂度:O((n-m)*m)。
第二种解法:KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」。
上述的朴素解法,不考虑剪枝的话复杂度是 O(m∗n) 的,而 KMP 算法的复杂度为 O(m+n)。
KMP 之所以能够在 O(m+n) 复杂度内完成查找,是因为其能在「非完全匹配」的过程中提取到有效信息进行复用,以减少「重复匹配」的消耗。
8. 01背包和完全背包、多重背包(leetcode 没有这道题)
01背包:有N种物品,每种物品只有1个,
完全背包:有N种物品,每种物品有无限个;
多重背包:有N种物品,每种物品的个数
c++ dp[i] = dp[i - 1] if s[i - 1] // 是合法字符 dp[i] = dp[i] + dp[i - 2] if s[i - 2, i - 1] // 是合法字符
c++ // dp[i][j]: 表示word1的前i字母,word2的前j个字母,编辑距离 if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1] else dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
c++ isValidBSTCore(TreeNode* root, TreeNode* min_tree, TreeNode* max_tree)
c++ bool isSymmetricCore(TreeNode* left, TreeNode* right);
c++ // 看下回溯的模板 void dfs(vector> & res, vector &temp, string s, int index) { if (index == s.length()) { res.push_back(temp); return; } for (int i = index; i < s.length(); i++) { if (dp[index][i]) { temp.push_back(s.substr(index, i - index + 1)); dfs(res, temp, s, i + 1); temp.pop_back(); } } }
c++ for (int i = 0; i < gas.size(); i++) { total += gas[i] - cost[i]; cur += gas[i] - cost[i]; if (cur < 0) { res = (i + 1) % gas.size(); cur = 0; } }
可以通过二分查找,时间复杂度logn 或者通过异或;元星总结:二分查找 扩展题,三个元素一样,只出现过一次;
c++ // d[i] : 表示前i个字符是否可以被拆分都在单词表里 dp[i] = dp[i - k] && s[i-k+1...i] in wordDict
c++ max_dp[i] = max(min_dp[i - 1] * nums[i - 1], max_dp[i - 1] * nums[i- 1], nums[i - 1]); min_dp[i] = min(min_dp[i - 1] * nums[i - 1], max_dp[i - 1] * nums[i- 1], nums[i - 1]);
c++ return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
dp[i]表示抢劫到第i家,累计最大收益 dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]); 这种都可以进行状态压缩
c++ 正确的理解: 自底向上的 rob(root)就是以root为根,所能偷到的最高金额,这里涉及到可能偷root,也可能不偷root的 由于不能偷相邻的,如果偷root,则不能偷其左右孩子,但是可以偷其左右孩子的孩子 max(root->val + rob(root->left->left) + rob(root->left->right) + rob(root->right->left) + rob(root->right->right), rob(root->left) + rob(root->right)) 使用map存储,减少遍历
一种是BFS:先构建邻接矩阵,并统计每个课程(节点)的入度。把入度为0的都加到队列中,然后遍历队列,弹出元素(入度为0的节点),以从邻接矩阵中找到以该节点为入度的其他节点,并分别将它们的入度都减去一。最后判断所有节点的入度是否为0。LC,课程表II:LC
解法:转移方程:dp[n] = min(dp[n - i * i]) . for i = 0...sqrt(n) init dp[n] = i.
for (int i = 0; i < n; i++) { if (nums[i] == 0) { continue; } else { swap(nums[p_0], nums[i]); p_0++; } }
115.找出字符串中第一个匹配项的下标:28,for 循环,时间复杂度m*n,KMP算法