把这些基础算法题掌握好,基础不牢地动山摇,后面中级题不少都是在这些基础题的基础上的。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文档点击这里获取,还有面试题没有列举出来,小伙伴们到时能够好好看面试题。