上一篇的反饋還算不錯,原來拜完年後真有一批人開始學習啦,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…