前端面试题-算法篇

51 阅读5分钟

哈喽,各位小伙伴们大家好,不知道你们的面试准备的怎么样呢,最近很多朋友和我反映,前端面试其实一直忽视了一点--算法,很多朋友都在这上面栽跟头了,于是我立即和阿里的一个大佬朋友要到了一份最新的前端算法面试题,希望对各位秋招面试的朋友有所帮助。

有需要完整版的朋友可以看置顶文章评论区

即可免费领取

  1. 合并两个有序链表

题⽬描述 

将两个升序链表合并为⼀个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

输⼊:1->2->4, 1->3->4

输出:1->1->2->3->4->4

前置知识 

递归

链表

思路 

本题可以使⽤递归来解,将两个链表头部较⼩的⼀个与剩下的元素合并,并返回排好序的链表头,当两条链表中的⼀条为空时终⽌递归。

关键点 

掌握链表数据结构

考虑边界情况

代码 

JS Code:

/**

 * Definition for singly-linked list.

 * function ListNode(val) {

 * this.val = val;

 * this.next = null;

 * }

 */

/**

 * @param {ListNode} l1

 * @param {ListNode} l2

 * @return {ListNode}

 */

const mergeTwoLists = function (l1, l2) {

 if (l1 === null) {

 return l2;

 }

 if (l2 === null) {

 return l1;

 }

 if (l1.val < l2.val) {

 l1.next = mergeTwoLists(l1.next, l2);

 return l1;

 } else {

 l2.next = mergeTwoLists(l1, l2.next);

 return l2;

 }

};

复杂度分析

M、N 是两条链表 l1、l2 的⻓度

时间复杂度:

空间复杂度:

扩展 

你可以使⽤迭代的⽅式求解么?

迭代的 CPP 代码如下:

class Solution {

public:

 ListNode* mergeTwoLists(ListNode* a, ListNode* b) {

 ListNode head, *tail = &head;

 while (a && b) {

 if (a->val <= b->val) {

 tail->next = a;

 a = a->next;

 } else {

 tail->next = b;

b = b->next;

 }

 tail = tail->next;

 }

 tail->next = a ? a : b;

 return head.next;

 }

};

迭代的 JS 代码如下:

var mergeTwoLists = function (l1, l2) {

 const prehead = new ListNode(-1);

 let prev = prehead;

 while (l1 != null && l2 != null) {

 if (l1.val <= l2.val) {

 prev.next = l1;

 l1 = l1.next;

 } else {

 prev.next = l2;

 l2 = l2.next;

 }

 prev = prev.next;

 }

 prev.next = l1 === null ? l2 : l1;

 return prehead.next;

};

  1. 括号⽣成 

题⽬描述 

数字 n 代表⽣成括号的对数,请你设计⼀个函数,⽤于能够⽣成所有可能的并且 有效的 括号组合。

示例:

输⼊:n = 3

输出:[

 "((()))",

 "(()())",

 "(())()",

"()(())",

 "()()()"

 ]

前置知识 

DFS

回溯法

思路 

本题是 20. 有效括号 的升级版。

由于我们需要求解所有的可能, 因此回溯就不难想到。回溯的思路和写法相对⽐较固定,并且回溯的优化⼿段⼤多是剪枝。

不难想到, 如果左括号的数⽬⼩于右括号,我们可以提前退出,这就是这道题的剪枝。⽐如 ()).... ,后⾯就不⽤看了,直接退出即可。回溯的退出条件也不难想到,那就是:

左括号数⽬等于右括号数⽬

左括号数⽬ + 右括号数⽬ = 2 * n

由于我们需要剪枝, 因此必须从左开始遍历。(WHY?)

因此这道题我们可以使⽤深度优先搜索(回溯思想),从空字符串开始构造,做加法, 即 dfs(左括号数, 右括号数⽬, 路径), 我们从 dfs(0, 0, '') 开始。

伪代码:

res = []

def dfs(l, r, s):

 if l > n or r > n: return

 if (l == r == n): res.append(s)

 # 剪枝,提⾼算法效率

 if l < r: return

 # 加⼀个左括号

 dfs(l + 1, r, s + '(')

 # 加⼀个右括号

 dfs(l, r + 1, s + ')')

dfs(0, 0, '')

return res

由于字符串的不可变性, 因此我们⽆需 撤销 s 的选择 。但是当你使⽤ C++ 等语⾔的时候, 就需要注意撤销 s 的选择了。类似:

s.push_back(')');

dfs(l, r + 1, s);

s.pop_back();

关键点 

当 l < r 时记得剪枝

代码 

JS Code:

/**

 * @param {number} n

 * @return {string[]}

 * @param l 左括号已经⽤了⼏个

 * @param r 右括号已经⽤了⼏个

 * @param str 当前递归得到的拼接字符串结果

 * @param res 结果集

 */

const generateParenthesis = function (n) {

 const res = [];

 function dfs(l, r, str) {

 if (l == n && r == n) {

 return res.push(str);

 }

 // l ⼩于 r 时不满⾜条件 剪枝

 if (l < r) {

 return;

 }

 // l ⼩于 n 时可以插⼊左括号,最多可以插⼊ n 个

 if (l < n) {

 dfs(l + 1, r, str + "(");

 }

 // r < l 时 可以插⼊右括号

 if (r < l) {

 dfs(l, r + 1, str + ")");

 }

 }

dfs(0, 0, "");

 return res;

};

复杂度分析

时间复杂度:O(2^N)

空间复杂度:O(2^N)

  1. 合并 K 个排序链表 

题⽬描述 

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输⼊:

[

 1->4->5,

 1->3->4,

 2->6

]

输出: 1->1->2->3->4->4->5->6

前置知识 

链表

归并排序

思路 

这道题⽬是合并 k 个已排序的链表,号称 leetcode ⽬前 最难 的链表题。和之前我们解决的88.merge-sorted-array很像。

他们有两点区别:

  1. 这道题的数据结构是链表,那道是数组。这个其实不复杂,毕竟都是线性的数据结构。

  2. 这道题需要合并 k 个元素,那道则只需要合并两个。这个是两题的关键差别,也是这道题难度为 hard 的原因。

图片

因此我们可以看出,这道题⽬是 88.merge-sorted-array 的进阶版本。其实思路也有点像,我们来具体分析下第⼆条。

如果你熟悉合并排序的话,你会发现它就是 合并排序的⼀部分 。

具体我们可以来看⼀个动画

(动画来⾃ zhuanlan.zhihu.com/p/61796021

关键点解析 

分治

归并排序(merge sort)

代码 

JavaScript Code:

/*

 * @lc app=leetcode id=23 lang=javascript

 *

 * [23] Merge k Sorted Lists

 *

 * leetcode.com/problems/me…

*/

function mergeTwoLists(l1, l2) {

 const dummyHead = {};

 let current = dummyHead;

 // l1: 1 -> 3 -> 5

 // l2: 2 -> 4 -> 6

 while (l1 !== null && l2 !== null) {

 if (l1.val < l2.val) {

 current.next = l1; // 把⼩的添加到结果链表

 current = current.next; // 移动结果链表的指针

 l1 = l1.next; // 移动⼩的那个链表的指针

 } else {

 current.next = l2;

 current = current.next;

 l2 = l2.next;

 }

 }

 if (l1 === null) {

 current.next = l2;

 } else {

 current.next = l1;

 }

 return dummyHead.next;

}

/**

 * Definition for singly-linked list.

 * function ListNode(val) {

 * this.val = val;

 * this.next = null;

 * }

 */

/**

 * @param {ListNode[]} lists

 * @return {ListNode}

 */

var mergeKLists = function (lists) {

 // 图参考:zhuanlan.zhihu.com/p/61796021

 if (lists.length === 0) return null;

 if (lists.length === 1) return lists[0];

 if (lists.length === 2) {

 return mergeTwoLists(lists[0], lists[1]);

 }

 const mid = lists.length >> 1;

 const l1 = [];

 for (let i = 0; i < mid; i++) {

l1[i] = lists[i];

 }

 const l2 = [];

 for (let i = mid, j = 0; i < lists.length; i++, j++) {

 l2[j] = lists[i];

 }

 return mergeTwoLists(mergeKLists(l1), mergeKLists(l2));

};

复杂度分析

时间复杂度:

空间复杂度:

相关题⽬ 

88.merge-sorted-array

扩展 

这道题其实可以⽤堆来做,感兴趣的同学尝试⼀下吧。

由于篇幅原因就先到这里了

有需要完整版的朋友可以看置顶文章评论区

即可免费领取