把這些基礎算法題掌握好,基礎不牢地動山搖,後面中級題不少都是在這些基礎題的基礎上的。javascript
前序遍歷題目以下:
root節點是A節點(下圖的A節點),而後讓你按照下圖數字的順序依次打印出節點。
咱們能夠看到這其中的規律,就是深度優先遍歷,先遍歷左子樹,再遍歷右子樹,這裏咱們不用遞歸,由於一些大廠嚴格要求二叉樹遍歷不用遞歸,遞歸太簡單了。css
重點思路就是:深度優先遍歷,先遍歷左子樹,再遍歷右子樹,前端
因此,咱們須要一套如何遍歷一顆二叉樹,而且是先左子樹,再右子樹的通用模板,以下vue
var Traversal = function(root) { const stack = []; while (root || stack.length){ while(root){ stack.push(root); root = root.left; } root = stack.pop(); root = root.right; } return res; };
咱們結合圖片發現這個遍歷產生的總體壓棧的順序是java
咱們把上面入棧的元素按順序排列一下就是,A、B、D、E、C、F,而這就是前序遍歷的順序!解答完畢!git
是否是頗有意思,下面的中序遍歷,咱們看看出棧順序是否是中序遍歷的要求:D、B、E、A、C、F(這就是中序遍歷的要求,好了,兩個題解決)面試
放具體前序遍歷代碼:算法
var preorderTraversal = function(root) { // 初始化數據 const res =[]; const stack = []; while (root || stack.length){ while(root){ res.push(root.val); stack.push(root); root = root.left; } root = stack.pop(); root = root.right; } return res; };
中序遍歷是一個意思,在前序遍歷的基礎上改造一下小程序
var preorderTraversal = function(root) { // 初始化數據 const res =[]; const stack = []; while (root || stack.length){ while(root){ stack.push(root); root = root.left; } root = stack.pop(); res.push(root.val); root = root.right; } return res; };
後序遍歷有點不太同樣,可是套路是同樣的,咱們須要先遍歷右子樹,再遍歷左子樹,反着來,就能夠了,代碼以下:segmentfault
var postorderTraversal = function(root) { // 初始化數據 const res =[]; const stack = []; while (root || stack.length){ while(root){ stack.push(root); res.unshift(root.val); root = root.right; } root = stack.pop(); root = root.left; } return res; };
這個題簡而言之就是判斷一個二叉樹是對稱的,好比說:
二叉樹 [1,2,2,3,4,4,3] 是對稱的。
1 / \ 2 2 / \ / \ 3 4 4 3
可是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的:
1 / \ 2 2 \ \ 3 3
思路:
遞歸解決:
判斷 A 的左子樹與 B 的右子樹是否對稱
function isSame(leftNode, rightNode){ if(leftNode === null && rightNode === null) return true; if(leftNode === null || rightNode === null) return false; return leftNode.val === rightNode.val && isSame(leftNode.left, rightNode.right) && isSame(leftNode.right, rightNode.left) } var isSymmetric = function(root) { if(!root) return root; return isSame(root.left, root.right); };
這個題在面試滴滴的時候遇到過,主要是掌握二叉樹遍歷的套路
而後以此類推,一直比較到深度最大的
var maxDepth = function(root) { if(!root) return root; let ret = 1; function dfs(root, depth){ if(!root.left && !root.right) ret = Math.max(ret, depth); if(root.left) dfs(root.left, depth+1); if(root.right) dfs(root.right, depth+1); } dfs(root, ret); return ret };
咱們先看題:
給你一個整數數組 nums ,其中元素已經按 升序 排列,請你將其轉換爲一棵 高度平衡 二叉搜索樹。
高度平衡 二叉樹是一棵知足「每一個節點的左右兩個子樹的高度差的絕對值不超過 1 」的二叉樹。
示例 1:
輸入:nums = [-10,-3,0,5,9] 輸出:[0,-3,9,-10,null,5] 解釋:[0,-10,5,null,-3,null,9] 也將被視爲正確答案:
示例 2:
輸入:nums = [1,3] 輸出:[3,1] 解釋:[1,3] 和 [3,1] 都是高度平衡二叉搜索樹。 提示:
<= nums.length <= 104
-104 <= nums[i] <= 104
nums 按 嚴格遞增 順序排列
遞歸函數能夠傳遞數組,也能夠傳遞指針,選擇傳遞指針的時候:l r 分別表明參與構建BST的數組的首尾索引。
var sortedArrayToBST = function(nums) { return toBST(nums, 0, nums.length - 1) }; const toBST = function(nums, l, r){ if( l > r){ return null; } const mid = l + r >> 1; const root = new TreeNode(nums[mid]); root.left = toBST(nums, l, mid - 1); root.right = toBST(nums, mid + 1, r); return root; }
棧是一種先進先出的數據結構,因此涉及到你須要先進先出這個想法後,就可使用棧。
其次我以爲棧跟遞歸很類似,遞歸是否是先壓棧,而後先進來的先出去,就跟函數調用棧同樣。
這是一道很典型的用棧解決的問題, 給定一個只包括 '(',')','{','}','[',']' 的字符串 s ,判斷字符串是否有效。
有效字符串需知足:
左括號必須用相同類型的右括號閉合。左括號必須以正確的順序閉合。
示例 1: 輸入:s = "()" 輸出:true 示例 2: 輸入:s = "()[]{}" 輸出:true 示例 3: 輸入:s = "(]" 輸出:false 示例 4: 輸入:s = "([)]" 輸出:false
思路:這道題有一規律:
1.右括號前面,必須是相對應的左括號,才能抵消!
2.右括號前面,不是對應的左括號,那麼該字符串,必定不是有效的括號!
也就是說左括號咱們直接放入棧中便可,發現是右括號就要對比是否跟棧頂元素相匹配,不匹配就返回false
var isValid = function(s) { const map = { '{': '}', '(': ')', '[': ']' }; const stack = []; for(let i of s){ if(map[i]){ stack.push(i); } else { if(map[stack[stack.length - 1]] === i){ stack.pop() }else{ return false; } } } return stack.length === 0; };
### 最小棧
先看題目:
設計一個支持 push ,pop ,top 操做,並能在常數時間內檢索到最小元素的棧。
getMin() —— 檢索棧中的最小元素。
示例: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2. 提示: pop、top 和 getMin 操做老是在 非空棧 上調用。
咱們先不寫getMin方法,知足其餘方法實現就很是簡單,咱們來看一下:
var MinStack = function() { this.stack = []; }; MinStack.prototype.push = function(x) { this.stack.push(x); }; MinStack.prototype.pop = function() { this.stack.pop(); }; MinStack.prototype.top = function() { return this.stack[this.stack.length - 1]; };
如何保證每次取最小呢,咱們舉一個例子:
如上圖,咱們須要一個輔助棧來記錄最小值,
因此咱們取最小的時候,總能在minStack中取到最小值,因此解法就出來了:
var MinStack = function() { this.stack = []; // 輔助棧 this.minStack = []; }; MinStack.prototype.push = function(x) { this.stack.push(x); // 若是是第一次或者當前x比最小棧裏的最小值還小才push x if(this.minStack.length === 0 || x < this.minStack[this.minStack.length - 1]){ this.minStack.push(x) } else { this.minStack.push( this.minStack[this.minStack.length - 1]) } }; MinStack.prototype.pop = function() { this.stack.pop(); this.minStack.pop(); }; MinStack.prototype.top = function() { return this.stack[this.stack.length - 1]; }; MinStack.prototype.getMin = function() { return this.minStack[this.stack.length - 1]; };
動態規劃,必定要知道動態轉移方程,有了這個,就至關於解題的鑰匙,咱們從題目中體會一下
題目以下:
給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例 1: 輸入:nums = [-2,1,-3,4,-1,2,1,-5,4] 輸出:6 解釋:連續子數組 [4,-1,2,1] 的和最大,爲 6 。 示例 2: 輸入:nums = [1] 輸出:1 示例 3: 輸入:nums = [0] 輸出:0
思路:
肯定轉義方程的公示:
dp[i]只有兩個方向能夠推出來:
這樣代碼就出來了,其實更多的就是求dp,遍歷nums每個下標都會產生最大子序和,咱們記錄下來便可
var maxSubArray = function(nums) { let res = nums[0]; const dp = [nums[0]]; for(let i=1;i < nums.length;i++){ if(dp[i-1]>0){ dp[i]=nums[i]+dp[i-1] }else{ dp[i]=nums[i] } res=Math.max(dp[i],res) } return res };
先看題目:
假設你正在爬樓梯。須要 n 階你才能到達樓頂。
每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1: 輸入: 2 輸出: 2 解釋: 有兩種方法能夠爬到樓頂。 1. 1 階 + 1 階 2. 2 階 示例 2: 輸入: 3 輸出: 3 解釋: 有三種方法能夠爬到樓頂。 1. 1 階 + 1 階 + 1 階 2. 1 階 + 2 階 3. 2 階 + 1 階
涉及到動態規劃,必定要知道動態轉移方程,有了這個,就至關於解題的鑰匙,
這道題咱們假設dp[10]表示爬到是你爬到10階就到達樓頂的方法數,
那麼,dp[10] 是否是就是你爬到8階,而後再走2步就到了,還有你走到9階,再走1步就到了,
因此 dp[10] 是否是等於 dp[9]+dp[8]
延伸一下 dp[n] 是否是等於 dp[n - 1] + dp[n - 2]
代碼以下:
var climbStairs = function(n) { const dp = {}; dp[1] = 1; dp[2] = 2; for(let i = 3; i <= n; i++){ dp[i] = dp[i-1] + dp[i-2] } return dp[n] };
題目以下:
給定一個由 整數 組成的 非空 數組所表示的非負整數,在該數的基礎上加一。
最高位數字存放在數組的首位, 數組中每一個元素只存儲單個數字。
你能夠假設除了整數 0 以外,這個整數不會以零開頭。
示例 1: 輸入:digits = [1,2,3] 輸出:[1,2,4] 解釋:輸入數組表示數字 123。 示例 2: 輸入:digits = [4,3,2,1] 輸出:[4,3,2,2] 解釋:輸入數組表示數字 4321。 示例 3: 輸入:digits = [0] 輸出:[1]
這個題的關鍵有兩點:
還須要一個每次迭代都重置和的變量sum來幫咱們算是否進位,以及進位後的數字
記住這個題,這是兩數字相加的套路,此次是+1,其實就是兩數相加的題(騰訊面試遇到過兩數相加)
var plusOne = function(digits) { let carry = 1; // 進位(由於咱們肯定+1,初始化進位就是1) for(let i = digits.length - 1; i >= 0; i--){ let sum = 0; // 這個變量是用來每次循環計算進位和digits[i]的值的 sum = digits[i] + carry; digits[i] = sum % 10; // 模運算取個位數 carry = (sum / 10) | 0; // 除以10是取百位數,而且|0表示捨棄小數位 } if(digits[0] === 0) digits.unshift(carry); return digits };
題目以下:實現 int sqrt(int x) 函數。
計算並返回 x 的平方根,其中 x 是非負整數。
因爲返回類型是整數,結果只保留整數的部分,小數部分將被捨去。
示例 1:
輸入: 4 輸出: 2
示例 2:
輸入: 8 輸出: 2 說明: 8 的平方根是 2.82842..., 因爲返回類型是整數,小數部分將被捨去。
這道題是典型的二分法解題,因此咱們須要熟悉二分法的通用模板,咱們出一個題:
在 [1, 2, 3, 4, 5, 6] 中找到 4,若存在則返回下標,不存在返回-1
const arr = [1, 2, 3, 4, 5, 6]; function getIndex1(arr, key) { let low = 0; const high = arr.length - 1; while (low <= high) { const mid = Math.floor((low + high) / 2); if (key === arr[mid]) { return mid; } if (key > arr[mid]) { low = mid + 1; } else { height = mid - 1; } } return -1; } console.log(getIndex1(arr, 5)); // 4
因此這道題的意思就是,咱們找一個數平方跟x最相近的數,二分法的用法中也有找相近數的功能
因此代碼以下:
var mySqrt = function(x) { let [l , r] = [0, x]; let ans = -1; while(l <= r) { const mid = (l + r) >> 1; if(mid * mid > x){ r = mid - 1 } else if(mid * mid < x){ ans = mid; // 防止越界 l = mid + 1; } else { ans = mid; return ans; } } return ans; }; };
這個題比較重要,也比較基礎,簡而言之就是進制轉換,必須緊緊掌握
題目以下:
給你一個整數 columnNumber ,返回它在 Excel 表中相對應的列名稱。
例如:
A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ...
示例 1: 輸入:columnNumber = 1 輸出:"A" 示例 2: 輸入:columnNumber = 28 輸出:"AB" 示例 3: 輸入:columnNumber = 701 輸出:"ZY" 示例 4: 輸入:columnNumber = 2147483647 輸出:"FXSHRXW"
說白了,這就是一道26進制的問題,之前咱們知道10進制轉2進制就是不停的除2,把餘數加起來,26進制也是同樣,不停的除26
思路:
以 ZY 爲例,Z 的值爲 26,Y 的值爲 25,則結果爲 26 * 26 + 25=701
var titleToNumber = function(columnTitle) { let ans = 0; for(let i = 0; i < columnTitle.length; i++){ ans = ans * 26 + (columnTitle[i].charCodeAt() - 'A'.charCodeAt() + 1) } return ans; };
題目:
給定一個整數 n,返回 n! 結果尾數中零的數量。
示例 1: 輸入: 3 輸出: 0 解釋: 3! = 6, 尾數中沒有零。 示例 2: 輸入: 5 輸出: 1 解釋: 5! = 120, 尾數中有 1 個零.
這道題很簡單,有多少個5就有多少個0,爲何這麼說呢,咱們分析一下題目
好比說 5!,
10! = 10 9 8 7 6 5 4 3 2 1 其中,除了10 = 2 5和自己有一對2 * 5,因此有兩個0,這樣這道題的規律就出來了,咱們再精進一步
如上圖,每四個數字都會出現一個或者多個2的因子,可是隻有每 5 個數字才能找到一個或多個5的因子。因此整體上看來,2的因子是遠遠多於5的因子的,因此咱們只須要找5的倍數就能夠了。
咱們再進一步,按照上面的說法,咱們須要計算好比10的階乘有多少個0,要把10的階乘算出來,其實咱們只須要算10有幾個5就行了,爲何呢
咱們再進一步,按照上面的說法,咱們須要計算好比10的階乘有多少個0,要把10的階乘算出來,其實咱們只須要算10有幾個5就行了,爲何呢
咱們發現只有5的倍數的階乘,纔會產生5, 因此咱們須要看看階層數有多少個5,代碼以下:
var trailingZeroes = function (n) { let r = 0; while (n > 1) { n = Math.floor(n / 5); r += n; } return r; };
題目以下:
顛倒給定的 32 位無符號整數的二進制位。
示例 1:
輸入: 00000010100101000001111010011100 輸出: 00111001011110000010100101000000 解釋: 輸入的二進制串 00000010100101000001111010011100 表示無符號整數 43261596, 所以返回 964176192,其二進制表示形式爲 00111001011110000010100101000000。
示例 2:
輸入:11111111111111111111111111111101 輸出:10111111111111111111111111111111 解釋:輸入的二進制串 11111111111111111111111111111101 表示無符號整數 4294967293, 所以返回 3221225471 其二進制表示形式爲 10111111111111111111111111111111 。
這類題,就是翻轉字符串,咱們能夠把其轉爲字符串,再轉成數組,再reverse一下,這裏咱們選用數學的方式去解答,不用這種轉字符串的方式。
解答這道題以前,咱們須要瞭解的前置知識:
1.與預算 &
1 & 1 // 1的2進制最後一位是1,獲得1 2 & 0 // 2的2進制最後一位是0,獲得0 3 & 1 // 3的2進制最後一位是1,獲得1 4 & 0 // 4的2進制最後一位是0,獲得0
因此咱們知道了怎麼取10進制最後1位的2進制是幾。
2.JavaScript 使用 32 位按位運算數(意思是咱們的按位運算都會轉成32位,你的數字不能超過32位,會出問題)
3.'<< 1' 運算
這個運算實際上就是把10進制乘以2,這個乘2在2進制上表現出右邊填了一個0,咱們距舉例來講,
思路:循環取最後一位拼接起來便可
var reverseBits = function (n) { let result = 0 for (let i = 0; i < 32; i++) { result = (result << 1) + (n & 1) n = n >> 1 } // 爲何要 >>> 0 呢,一位javascript沒有無符號整數,全是有符號的 // 不>>>0的話,得出來的值是負數,可是無符號整數是沒有符號的 // javascript 有符號轉化爲無符號的方法就是>>>0 return result >>> 0 }
給定一個包含 [0, n] 中 n 個數的數組 nums ,找出 [0, n] 這個範圍內沒有出如今數組中的那個數。
進階:
你可否實現線性時間複雜度、僅使用額外常數空間的算法解決此問題?
示例 1: 輸入:nums = [3,0,1] 輸出:2 解釋:n = 3,由於有 3 個數字,因此全部的數字都在範圍 [0,3] 內。2 是丟失的數字,由於它沒有出如今 nums 中。 示例 2: 輸入:nums = [0,1] 輸出:2 解釋:n = 2,由於有 2 個數字,因此全部的數字都在範圍 [0,2] 內。2 是丟失的數字,由於它沒有出如今 nums 中。
這題很簡單,就是用0-n的總和減去數組總和
0 - n 的總和用等差數列:(首數+尾數)* 項數 / 2 來求
var missingNumber = function(nums) { const len = nums.length let sum = ((1 + len) * len) / 2 for (let i = 0; i < len; i++) { sum -= nums[i] } return sum }
題目以下:
給定一個整數,寫一個函數來判斷它是不是 3 的冪次方。若是是,返回 true ;不然,返回 false 。
整數 n 是 3 的冪次方需知足:存在整數 x 使得 n == 3的x次方
示例 1: 輸入:n = 27 輸出:true 示例 2: 輸入:n = 0 輸出:false 示例 3: 輸入:n = 9 輸出:true
思路
也就是說,若是是3的冪次方,一直除以3,除到最後就等於1好比27/3/3/3等於1 若是不是3的冪次方,除到最後就是3點幾/3 等於1點幾
代碼就出來了判斷是否是等於1便可
var isPowerOfThree = function(n) { while(n >= 3){ n /= 3; } return n === 1; };
這個題沒啥好說的,就按照題目說的寫代碼就行,先看題目:
寫一個程序,輸出從 1 到 n 數字的字符串表示。
1.若是 n 是3的倍數,輸出「Fizz」;
2.若是 n 是5的倍數,輸出「Buzz」;
3.若是 n 同時是3和5的倍數,輸出 「FizzBuzz」。
示例: n = 15, 返回: [ "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz" ]
var fizzBuzz = function (n) { const list = []; for (let i = 1; i <= n; i++) { const is3Times = i % 3 === 0; // 是不是3的倍數 const is5Times = i % 5 === 0; // 是不是5的倍數 const is15Times = is3Times && is5Times; // 是不是15的倍數 if (is15Times) { list.push('FizzBuzz'); continue; } if (is3Times) { list.push('Fizz'); continue; } if (is5Times) { list.push('Buzz'); continue; } list.push(`${i}`); } return list; };
這類問題的特色就是,你要循環尋找,到底怎麼循環尋找,看題便知。
題目以下:
給定一個鏈表,判斷鏈表中是否有環。
若是鏈表中有某個節點,能夠經過連續跟蹤 next 指針再次到達,則鏈表中存在環。爲了表示給定鏈表中的環,咱們使用整數 pos 來表示鏈表尾鏈接到鏈表中的位置(索引從 0 開始)。若是 pos 是 -1,則在該鏈表中沒有環。注意:pos 不做爲參數進行傳遞,僅僅是爲了標識鏈表的實際狀況。
若是鏈表中存在環,則返回 true 。不然,返回 false 。
輸入:head = [3,2,0,-4], pos = 1 輸出: true 解釋: 鏈表中有一個環,其尾部鏈接到第二個節點。
示例 2:
輸入:head = [1,2], pos = 0 輸出: true 解釋: 鏈表中有一個環,其尾部鏈接到第一個節點。
咱們採用標記法:
給遍歷過的節點打記號,若是遍歷過程當中遇到有記號的說明已環
var hasCycle = function(head) { let traversingNode = head; while(traversingNode){ if(traversingNode.isVistitd) return true traversingNode.isVistitd = true traversingNode = traversingNode.next } return false; };
題目以下:編寫一個算法來判斷一個數 n 是否是快樂數。
「快樂數」定義爲:
因此代碼只要判斷這兩種就好了,代碼以下:
// 封裝獲取快樂數的方法 function getNext(n){ n = String(n); let sum = 0; for(let num of n){ sum = sum + Math.pow(+num, 2); } return sum; } var isHappy = function(n) { // 哈希表來看是否循環 const map = {}; while( n !== 1 ){ map[n] = true; n = getNext(n) if(map[n]) return false } return true };
我還整理了一份前端面試題,包括人事、項目、小程序、HTML5/CSS三、JS、HTTP、ES六、Vue、REACT 等面試題,咱們先一塊兒看看題目。
這都是主觀問題,列舉出來的是能夠參考下,人事面試也不能掉以輕心,有的公司人事是有一票否決權
完整版的2021前端面試題精編PDF文檔點擊這裏獲取,還有面試題沒有列舉出來,小夥伴們到時能夠好好看面試題。