上一篇的反馈还算不错,原来拜完年后真有一批人开始学习啦,2021春招陆续开始啦,我能给你们帮助的就是尽可能在2月份给你们更新完算法入门专栏,能帮助到你们很开心👍~javascript
如今和你们分享一下咱们是如何准备算法这一块的,春招即将开启,还能最后准备一下,但愿对你们有所帮助。前端
本来打算经过一篇文章介绍一下,推荐一下本身的刷题方式和刷题路线,获得一些伙伴的反馈:最好仍是更加详细,面向零基础,小白这些,还有github
访问速度也是一方面问题,可能图片都加载不出来。java
所以,我打算分模块出几期文章,这样你只用经过首发在掘金的文章便可了解 Chocolate
同窗总体刷题汇总啦。立刻就要过年了,但愿可以帮助你的春招。打算出的内容计划安排以下:git
欢迎访问 GitHub仓库,目前已经有 552 道大厂真题了,涵盖各种前端的真题,祝你春秋招牛气冲天~github
首先,我来简单介绍一下本身,在校打过ACM(若是没听过,当我没说,由于没有很大价值的牌牌,铁牌,参赛证以及证书却是一堆)面试
若是你知道acm,而且参与过,对于国内前端(注意是说前端)面试的话,应该不须要花费很长的刷题时间,若是你们有想法了解个人acm经历的话,这个后续我会考虑在 B站发布一期视频。算法
那么对于零基础的小白来讲,可能须要花10-20天左右时间来准备算法,而对于非科班来讲这个周期可能会更长一点。那么,如今我准备来分享我是如何带着女朋友零基础刷题的。api
leetcode
一些简单以及中等难度的居多,而这些算法对于科班来讲的话,应该在学校都学习过,好比算法分析与设计,数据结构与算法这一类课程,那么有这个基础,你的刷题时间又能够进行缩短了开门见山地说,首先提供一份思惟导图,让知识由繁到简。数组
获取高清PDF,请在微信公众号【小狮子前端】回复【LeetCode】,一块儿刷题或者交流学习能够加企鹅群【666151691】微信
本仓库刷题路线参考 ssh (给大佬点赞) 仓库地址:github.com/sl1673495/l…
感谢大佬的概括总结,本来打算在大佬那里打卡学习,后面考虑不太友好,仍是本身新建了一个仓库打卡学习。
其次,本仓库解题代码大部分是本身的代码风格,题量也进行了拓展,将会持续更新下去,何不star收藏一下?
本仓库将全程使用的语言是 JavaScript
,是一个纯前端刷题路线,对于前端刷题没有方向的小伙伴简直是福音。解题代码会记录在本仓库的 Issues
中,会按照 label
进行分类。好比想查看 「递归与回溯」 分类下的问题,那么选择标签进行筛选便可。
同时,小伙伴们能够在 Issues
中提交本身的解题代码,🤝 欢迎 Contributing
,可打卡刷题,坚持下来的人最酷!Give a ⭐️ if this project helped you !
下面正式开始咱们的刷题之路,给本篇文章点个赞,拿出本身心仪的键盘,开始!
如下专题顺序仅我的以及面试高频点来总结的刷题方式,你们能够根据本身的想法来组合。更多题集请参考本仓库哈~
题目描述
给你一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出全部知足条件且不重复的三元组。
注意:答案中不能够包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
知足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
复制代码
解题思路
由于不能有重复的解,为了简化操做,咱们先对数组排序,因而判断一个元素是否重复,只需看它和它前面的元素是否相等便可
双指针的移动时,避免出现重复解
获得一个解后,须要左右指针向 “内” 收缩,为了不指向重复的元素
优化点,若是当前元素值大于0了,因为咱们事先排好序了,不存在三个数相加为0了,此时直接break就行了。
/** * @param {number[]} nums * @return {number[][]} */
var threeSum = function (nums) {
let len = nums.length;
if (len < 2) return [];
let res = [];
nums.sort((a, b) => a - b); // 从小到大进行排序
for (let i = 0; i < len - 2; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] === nums[i - 1]) continue; // 去掉重复项
let L = i + 1;
let R = len - 1;
while (L < R) {
let sum = nums[i] + nums[L] + nums[R]; // 三数之和
if (sum === 0) {
res.push([nums[i], nums[L], nums[R]]);
while (L < R && nums[L] == nums[L + 1]) L++; // 去重,直到指向不同的数
while (L < R && nums[R] == nums[R - 1]) R--;
L++;
R--;
} else if (sum < 0) {
L++; // 和小于0,就是左边值过小了,往右移
} else if (sum > 0) {
R--; // 和大于0,就是右边值太大了,往左移
}
}
}
return res;
};
复制代码
题目描述
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在惟一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
复制代码
提示:
解题思路
这道题和15有一点区别,咱们只要求最接近 target
的三树之和,那么咱们就须要每次更新一下,最接近的和,简单来讲就是比较一下,而后本题也没有去重操做,相对来讲考虑状况会更少一点。
/** * @param {number[]} nums * @param {number} target * @return {number} */
var threeSumClosest = function (nums, target) {
let len = nums.length;
nums.sort((a, b) => a - b); // 从小到大进行排序
let res = nums[0] + nums[1] + nums[len - 1]; // 初始化随机一个res
for (let i = 0; i < len - 2; i++) {
let L = i + 1;
let R = len - 1;
while (L < R) {
let sum = nums[i] + nums[L] + nums[R]; // 三数之和
sum > target ? R-- : L++; // 比目标值大,就往左内缩,小的话,就往右内缩
if (Math.abs(sum - target) < Math.abs(res - target)) {
res = sum; // 迭代更新res
}
}
}
return res;
};
复制代码
题目描述
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,咱们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意: 不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
复制代码
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,而后按照0、一、2的排序,重写当前数组。 你能想出一个仅使用常数空间的一趟扫描算法吗?
解题思路
双指针,当前值为2,那么就和右边指针进行交换,反之当前值为0,那么就和左边指针进行交换,为1就不动。
/** * @param {number[]} nums * @return {void} Do not return anything, modify nums in-place instead. */
var sortColors = function (nums) {
let len = nums.length;
let L = 0;
let R = len - 1;
let i = 0;
while (i <= R) {
while (nums[i] == 2 && i < R) { // 当前值为2,那么就和右边指针进行交换
[nums[i], nums[R]] = [nums[R], nums[i]];
R--;
}
while (nums[i] == 0 && i > L) { // 当前值为0,那么就和左边指针进行交换
[nums[i], nums[L]] = [nums[L], nums[i]];
L++;
}
i++;
}
return nums;
};
复制代码
我想下面这份代码应该会更好理解一点:
/** * @param {number[]} nums * @return {void} Do not return anything, modify nums in-place instead. */
var sortColors = function (nums) {
let len = nums.length;
let L = 0;
let R = len - 1;
let i = 0;
while (i <= R) {
if (nums[i] == 0) { // 当前值为0,那么就和左边指针进行交换
[nums[i], nums[L]] = [nums[L], nums[i]];
L++;
i++;
} else if (nums[i] == 2) { // 当前值为2,那么就和右边指针进行交换
[nums[i], nums[R]] = [nums[R], nums[i]];
R--;
} else {
i++;
}
}
return nums;
};
复制代码
题目描述
编写一个函数,其做用是将输入的字符串反转过来。输入字符串以字符数组 char[]
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你能够假设数组中的全部字符都是 ASCII
码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
复制代码
示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
复制代码
解题思路
方法一:利用JS原生api
/** * @param {character[]} s * @return {void} Do not return anything, modify s in-place instead. */
var reverseString = function (s) {
return s.reverse();
};
复制代码
方法二:双指针,头尾交换
/** * @param {character[]} s * @return {void} Do not return anything, modify s in-place instead. */
var reverseString = function (s) {
let i = 0, j = s.length - 1;
while (i < j) {
[s[i], s[j]] = [s[j], s[i]]; // 双指针,交换
i++ , j--;
}
return s;
};
复制代码
题目描述 给你 n 个非负整数 a1,a2,...,an,每一个数表明坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器能够容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线表明输入数组 [1,8,6,2,5,4,8,3,7]。在此状况下,容器可以容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
复制代码
解题思路
双指针作法,咱们须要枚举全部状况,有一点贪心的思想,每次咱们得看短的板子让咱们容纳的面积。每次都选择左右指针最短的那个板子,计算出当前容纳的最多的水,而后从短的板子指针出发向内缩,这样不断求,最终咱们能够枚举全部状况,天然能够枚举出最大容器面积。
/** * @param {number[]} height * @return {number} */
var maxArea = function (height) {
let len = height.length;
let L = 0;
let R = len - 1;
let res = 0;
while (L < R) {
if (height[L] < height[R]) { // 选择短板效应
let ans = height[L] * (R - L);
L++;
res = Math.max(res, ans); // 求当前容纳最多的水
} else {
let ans = height[R] * (R - L);
res = Math.max(res, ans);
R--;
}
}
return res;
};
复制代码
题目描述
给定 n 个非负整数表示每一个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨以后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1]
表示的高度图,在这种状况下,能够接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
复制代码
解题思路
这个存放水,咱们就须要看左边两边指针的柱子看谁的高度小了,当前是看高度小的了。
以左边为例:当前柱子存水量 = 最近最高柱子高度(只看左边到当前柱子) - 当前柱子高度
右边同理。
/** * @param {number[]} height * @return {number} */
var trap = function (height) {
let len = height.length;
let L = 0, R = len - 1;
let leftHeight = 0, rightHeight = 0;
let res = 0;
while (L < R) {
if (height[L] < height[R]) { // 左边高度小,固然看左边
leftHeight = Math.max(leftHeight, height[L]);
res += leftHeight - height[L]; // 当前柱子能存放的水
L++;
} else { // 右边高度小,看右边
rightHeight = Math.max(rightHeight, height[R]);
res += rightHeight - height[R]; // 当前柱子能存放的水
R--;
}
}
return res;
};
复制代码
题目描述
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中知足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。若是不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
复制代码
进阶:
解题思路
滑动窗口,利用双指针实现,从左到右看,知足条件就把左指针左移,找到最小的长度,而后每次窗口右指针都往右滑动,直到数组末尾。
/** * @param {number} s * @param {number[]} nums * @return {number} */
var minSubArrayLen = function (s, nums) {
let len = nums.length;
let L = 0, R = 0;
let res = Infinity, sum = 0;
while (R < len) {
sum += nums[R];
while (sum >= s) { // 滑动窗口
res = Math.min(res, R - L + 1);
sum -= nums[L];
L++;
}
R++;
}
return res == Infinity ? 0 : res; // 判断合法性
};
复制代码
题目描述
你的朋友正在使用键盘输入他的名字 name
。偶尔,在键入字符 c
时,按键可能会被长按,而字符可能被输入 1 次或屡次。
你将会检查键盘输入的字符 typed
。若是它对应的多是你的朋友的名字(其中一些字符可能被长按),那么就返回 True
。
示例 1:
输入:name = "alex", typed = "aaleex"
输出:true
解释:'alex' 中的 'a' 和 'e' 被长按。
复制代码
示例 2:
输入:name = "saeed", typed = "ssaaedd"
输出:false
解释:'e' 必定须要被键入两次,但在 typed 的输出中不是这样。
复制代码
示例 3:
输入:name = "leelee", typed = "lleeelee"
输出:true
复制代码
示例 4:
输入:name = "laiden", typed = "laiden"
输出:true
解释:长按名字中的字符并非必要的。
复制代码
提示:
name.length
<= 1000typed.length
<= 1000name
和 typed
的字符都是小写字母。解题思路
显而易见,采用双指针作法,经过 cnt
计数统计字符匹配成功个数,而后经过双指针进行比较匹配,其中有几个地方注意一下:
typed
和 name
的当前索引前一位都不相等的话,那么名字就不对应,直接跳出去,这里算是小小的优化了一下。typed
走完才能跳出去,若是是 i == n
就跳出去的话,这种状况:name:abc | typed:abcd 就会判断出错/** * @param {string} name * @param {string} typed * @return {boolean} */
var isLongPressedName = function (name, typed) {
let n = name.length; // 求出字符串长度
let m = typed.length;
let cnt = 0; // 统计匹配成功个数
let i = 0, j = 0; // 双指针
let flag = false; // 判断是否中途遇到不匹配阶段
while (1) {
if (name[i] == typed[j]) { // 匹配成功
i++ , cnt++ , j++;
} else {
if (typed[j] == name[i - 1]) {
j++;
} else {
// 若是 typed 和 name 当前索引前一位都不相等的话,那么名字就不对应,直接跳出去
flag = true;
}
}
if (flag) break;
if (j == m) break; // 当 typed走完才能跳出去,若是是 i == n 就跳出去的话,这种状况:abc | abcd 就会判断出错
}
if (cnt === n && j === m) return true;
else return false;
};
复制代码
题目描述
字符串 S
由小写字母组成。咱们要把这个字符串划分为尽量多的片断,同一个字母只会出如今其中的一个片断。返回一个表示每一个字符串片断的长度的列表。
示例 1:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每一个字母最多出如今一个片断中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,由于划分的片断数较少。
复制代码
提示:
S
的长度在 [1, 500]
之间。S
只包含小写字母 'a'
到 'z'
。解题思路
此题是一个挺有意思的题,既有贪心的味道,又有双指针的味道,下面说一下解题思路:
首先维护一个 map
,它用来统计字当前字母的位置,而咱们经过遍历就能够记录获得每一个字母的最远位置。
而后,再次遍历字符串时,咱们既能够获得当前字母的最远位置,根据贪心思想,为了让同一个字母只会出如今其中的一个片断,那么对于这个字母必定要是最远位置,咱们就能够获得一个范围区间,即 maxLen
。
获得了 maxLen
后,咱们还须要让 i
指针,即尾指针走到这个地方才算咱们能够切分的片断。
(想一想,若是不走到 maxLen
的话,这个范围区间内的字母可能会有更远的位置,那么就没法知足让同一个字母只会出如今其中的一个片断这个条件了)
参考 笨猪爆破组 大佬图解。
/** * @param {string} S * @return {number[]} */
var partitionLabels = function (S) {
let map = {}; // 用来统计当前字母最远位置
for (let i = 0; i < S.length; i++) {
map[S[i]] = i; // 存储当前字母当前位置
}
let start = 0; // 头指针
let res = [];
let maxLen = 0;
for (let i = 0; i < S.length; i++) {
let curMaxLen = map[S[i]];
maxLen = Math.max(maxLen, curMaxLen); // 计算出当前区间范围是否还能够继续扩大区间
if (i === maxLen) {
let tmp = i - start + 1;
start = i + 1;
res.push(tmp); // 划分片断
}
}
return res;
};
复制代码
题目描述
给定一个非空的字符串,判断它是否能够由它的一个子串重复屡次构成。给定的字符串只含有小写英文字母,而且长度不超过10000。
示例 1:
输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。
复制代码
示例 2:
输入: "aba"
输出: False
复制代码
示例 3:
输入: "abcabcabcabc"
输出: True
解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)
复制代码
解题思路
对于样例字符串,看是否由字符串的其中一个子串重复构成,咱们能够将原字符串与本身拼接一次,而后从原字符串第1位(从0开始)找,看是否会找到拼接以后的字符串起始位,即 s.length
处,那么就不存在重复构成这样状况,不然,就存在,返回 True
。
/** * @param {string} s * @return {boolean} */
var repeatedSubstringPattern = function(s) {
return (s+s).indexOf(s,1) !== s.length
};
复制代码
说明:
我想会有小伙伴疑惑,诶,为啥字符串这一块就这一道题呢?
首先,整理这一系列文章,主要是给你们提供一些刷题路线思路,题是变幻无穷的,而且一道题可能有不少种解题方式,我列举的题目大多数是我遇到过的原题,一份入门指南。其次,对于字符串而言,大部分都是与其它算法扯上关系,好比:
另外,给你们说起一下,字符串这块考察范围多一点的就是回文,以及相关牵扯到的一系列问题,好比:马拉车算法、最长回文子串问题、如何判断一个回文、最长公共前缀等等,这些在
leetcode
上都是有原题的,而 马拉车 算法在笔试以及面试的时候我常常遇到,犹记得当时是面字节跳动公司遇到的,先从回文考察,最后牵扯到Manacher
算法,若是你尚未据说过这个算法,挺好的,至少这篇文章帮助到你了,赶快去了解一下吧~
至于电话号码的字母组合这道题在上一篇遗漏掉了,这是我2020年春招腾讯面试的真题,当时就被这道题给卡住了,后面发现其实也不是很难,如今来补充一下:
17. 电话号码的字母组合原题传送门(回溯、dfs)
题目描述
给定一个仅包含数字 2-9 的字符串,返回全部它能表示的字母组合。
给出数字到字母的映射以下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
复制代码
说明:
尽管上面的答案是按字典序排列的,可是你能够任意选择答案输出的顺序。
复制代码
解题思路
采用回溯作法,对于当前选项,咱们能够重复选择,因此 for
循环那里从 0 开始,对于字母组合咱们作一个 map
映射便可。
参考 xiao_ben_zhu 大佬的图解
var letterCombinations = function (digits) {
if(!digits.length) return [];
// 直接映射
const map = { '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz' };
let res = [];
let dfs = (cur, start) => {
if (start >= digits.length) {
res.push(cur);
return;
}
// 取当前可选的字母组合
let str = map[digits[start]];
for (let i = 0; i < str.length; i++) {
dfs(cur + str[i], start + 1);
}
}
dfs('', 0);
return res;
};
复制代码
解法2
这个是没用回溯以前写的一份代码,简单来讲就是利用了层次遍历的特性,反正每次取字母都是能够重复的,直接遍历便可,而后进队列。
var letterCombinations = function(digits) {
if(!digits.length) return []
const map = { '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz' };
let queue = []
queue.push('')
for(let i=0;i<digits.length;i++){
let size = queue.length
while(size--){
let cur = queue.shift()
let str = map[digits[i]]
for(let j=0;j<str.length;j++){
queue.push(cur+str[j])
}
}
}
return queue
};
复制代码
❤️关注+点赞+收藏+评论+转发❤️,原创不易,您的支持将会是我最大的动力~
访问超逸の博客,方便小伙伴阅读玩耍~
最后,祝各位新年快乐,牛年大吉,好运++,在准备春招の你,可以早点结束春招,offer拿到手软,但愿个人文章可以帮助到你,咱们很快会在下期相遇~
快来关注我吧,学习前端虽然很“苦”,但有 一百个Chocolate 的文章会更“甜”~
【做者:一百个Chocolate】juejin.cn/user/298153…