微信前端社招算法面经

1,284 阅读6分钟

楼主于数个月前面了wxg(经验2.5年),前前后后3、4面,几乎每一面都有算法,哈哈哈,不愧是微信事业群,狠重视算法,结果蛮遗憾的,没有能走到GM面[哭的很大声]。 但整个过程下来,还是蛮有收获的。一方面自己对于数据结构有了更深的理解,一方面也对认识了自己的不足。在此也感谢给予我帮助的大佬。再接再厉,下次再战。

以下是一些记录。

一、有印象的几道算法题如下:

1、实现二叉树镜像(翻转)

function treeFun(root) {
  if (root === null) return null;
  function treeList(node) {
    let left = node.left;
    let right = node.right;
    node.left = right;
    node.right = left;
  }
  // 前序遍历 || 后序遍历
  treeFun(root);
  treeList(root.left);
  treeList(root.right);
  return root;
}

2、使用非递归的方式实现二叉树前序遍历

// 前序遍历
const preorderTraversal = (root) => {
    const list = [];
    const stack = [];
    if(root) stack.push(root)
    while(stack.length > 0) {
        const curNode = stack.pop()
        list.push(curNode.val)
        if(curNode.right !== null) {
            stack.push(curNode.right)
        }
        if(curNode.left !== null) {
            stack.push(curNode.left)
        }
    }
    return list
}

3、字符串转整形

let strToNumberFun = function (str) {
  let res = parseInt(str); // 如果遇到不能转数字的,则为NAN
  let max = Math.pow(2, 31);
  let min = Math.pow(-2, 31);
  if (isNaN(res)) {
    return 0;
  } else if (res > max) {
    return max;
  } else if (res < min) {
    return min;
  } else {
    return res;
  }
};

还有没有其他的解法? 另外一种解法是使用正则,有兴趣的同学可以试试。

4、找出数组中的质数

// 省略

5、写一个快排

function quickArrFun(arr) {
  let leng = arr.length;
  if (leng === 1) return arr;
  let quick = arr[0];
  let left = [],
    right = [],
    res = [];
  for (let i = 1; i < leng; i++) {
    if (quick < arr[i]) right.push(arr[i]);
    if (quick > arr[i]) left.push(arr[i]);
  }
  res = [...quickArrFun(left), quick, ...quickArrFun(right)];
  return res;
}

6、实现一个apply

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

其他的就不一一列举了...

二、以下是自己复习到的部分算法:

1、二叉树
/*二叉树 start*/
function treeFun(root) {
  if (root === null) return null;
  let temp = root.left;
  root.left = root.right;
  root.right = temp;
  treeFun(root.left);
  treeFun(root.right);
}

// 二叉树中序遍历
function treeFunMid(root) {
  let result = [];
  var inorderTraversal = (node) => {
    if (node) {
      // 先遍历左子树
      inorderTraversal(node.left);
      // 再根节点
      result.push(node.val);
      // 最后遍历右子树
      inorderTraversal(node.right);
    }
  };
  inorderTraversal(root);
  return result;
}

const inorderTraversal = (root) => {
  let list = [];
  let stack = [];
  let node = root;

  while (node || stack.length) {
    // 遍历左子树
    while (node) {
      stack.push(node);
      node = node.left;
    }

    node = stack.pop();
    list.push(node.val);
    node = node.right;
  }
  return list;
};

var inorderTraversal = function (root) {
  if (root === null) return [];
  let stack = [root]; // 利用栈来遍历树
  let res = [];
  while (stack.length) {
    let current = stack.pop();
    if (current === null) continue;
    if (!current.visited) {
      // 节点未被访问
      current.visited = true; // 设置了一个变量,标记该节点是否被访问了
      stack.push(current.right, current, current.left); // 不管三七二十一,按照右根左的顺序全部入栈,即使有null的也会在上面continue的时候跳过
    } else {
      // 节点已经被访问了,输出值,遍历栈里的下一个节点
      res.push(current.val);
    }
  }
  return res;
};

// 二叉树的第k大节点
function treeKFun(root, k) {
  if (root === null) return null;
  let res = [];
  function treeNode(node) {
    treeNode(node.left);
    res.push(node.val);
    treeNode(node.right);
  }
  treeNode(root);
  return res[res.length - k];
}

/**
leetcode 112. 路径之和
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    if(root === null) return false;
    if(root.left === null && root.right === null) return targetSum === root.val;
    targetSum = targetSum - root.val;
    return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
};

/*二叉树 end*/
2、排序
/*排序 start*/

// 冒泡排序
function arrFun(arr) {
  let leng = arr.length;
  if (leng === 1) return arr;
  for (let i = 0; i < leng; i++) {
    for (let j = i + 1; j < leng; j++) {
      if (arr[i] > arr[j]) {
        let temp = arr[j];
        arr[i] = temp;
        arr[j] = arr[i];
      }
    }
  }
  return arr;
}

// 选择排序
function arrSelectFun(arr) {
  let leng = arr.length;
  if (leng === 1) return arr;
  for (let i = 0; i < leng; i++) {
    let mix = i;
    for (let j = i + 1; j < leng; j++) {
      if (arr[mix] > arr[j]) {
        mix = j;
      }
      let temp = arr[i];
      arr[i] = arr[mix];
      arr[mix] = temp;
    }
  }
  return arr;
}

// 快速排序
function quickArrFun(arr) {
  let leng = arr.length;
  if (leng === 1) return arr;
  let quick = arr[0];
  let left = [],
    right = [],
    res = [];
  for (let i = 1; i < leng; i++) {
    if (quick < arr[i]) right.push(arr[i]);
    if (quick > arr[i]) left.push(arr[i]);
  }
  res = [...quickArrFun(left), quick, ...quickArrFun(right)];
  return res;
}

/*排序 end*/
3、链表
/*链表 start*/
/* leetcode 19. 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
        进阶:你能尝试使用一趟扫描实现吗?
        示例 1:
        输入:head = [1,2,3,4,5], n = 2
        输出:[1,2,3,5]
*/
/**
  * Definition for singly-linked list.
  * function ListNode(val, next) {
  *     this.val = (val===undefined ? 0 : val)
  *     this.next = (next===undefined ? null : next)
  * }
  */
/**
  * @param {ListNode} head
  * @param {number} n
  * @return {ListNode}
  */
var removeNthFromEnd = function (head, n) {
  var ret = new ListNode(0, head);
  var slow = (fast = ret);
  while (n--) {
    fast = fast.next;
  }
  if (!fast) return ret.next;
  while (fast.next) {
    fast = fast.next;
    slow = slow.next;
  }
  slow.next = slow.next.next;
  return ret;
};

// leetcode024. `反转链表`
/*
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
*/
/**
  * Definition for singly-linked list.
  * function ListNode(val, next) {
  *     this.val = (val===undefined ? 0 : val)
  *     this.next = (next===undefined ? null : next)
  * }
  */
/**
  * @param {ListNode} head
  * @return {ListNode}
  */
var reverseList = function (head) {
  if (!head || !head.next) return head;
  let temp = null,
    pre = null,
    cur = head;
  while (cur) {
    temp = cur.next;
    cur.next = pre;
    pre = cur;
    cur = temp;
  }
  // temp = cur = null;
  return pre;
};

/*
 * @lc app=leetcode.cn id=19 lang=javascript
 *
 * [19] 删除链表的倒数第 N 个结点
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function (head, n) {
  let fast = head,
    slow = head;
  for (let i = 0; i < n; i++) {
    fast = fast.next;
  }
  while (!fast) {
    return head.next;
  }
  while (fast.next) {
    fast = fast.next;
    slow = slow.next;
  }
  slow.next = slow.next.next;
  return head;
};
// @lc code=end


/*
 * @lc app=leetcode.cn id=21 lang=javascript
 *
 * [21] 合并两个有序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists = function (list1, list2) {
  // 如果list1或者list2一开始就是空链表,那么没有任何操作需要合并,返回对方即可
  if (list1 === null) return list2;
  if (list2 === null) return list1;
  if (list1.val < list2.val) {
    list1.next = mergeTwoLists(list1.next, list2);
    return list1;
  }
  if (list1.val > list2.val) {
    list2.next = mergeTwoLists(list1, list2.next);
    return list2;
  }
};
// @lc code=end

/*链表 end*/
4、LeetCode 相关
/*LeetCode start*/

// leetcode 88、合并两个有序数组
/*输入:
  nums1 = [1,2,3,0,0,0], m = 3
  nums2 = [2,5,6],       n = 3
  输出: [1,2,2,3,5,6]
*/
function concactArrFun(nums1, m, nums2, n) {
  nums1.splice(m, n, ...nums2);
  nums1.sort((a, b) => {
    a - b;
  });
  return nums1;
}

// leetcode415 字符串相加
/*给定两个字符串形式的非负整数 num1 和 num2 ,计算它们的和。例如:"111" + ”2222“ = ”2333“*/

function addStringFun(num1, num2) {
  let result = '';
  let temp = 0;
  let arr1 = num1.splice('');
  let arr2 = num2.splice('');
  while (arr1.length || arr2.length || temp) {
    temp += ~~arr1.pop() + ~~arr2.pop();
    result = (temp % 10) + result;
    temp = ~~(result / 10);
  }
  return result.replace(/^0+/, '');
}

// 腾讯&leetcode43:字符串相乘
/* 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
  示例 1:
  输入: num1 = "2", num2 = "3"
  输出: "6"
  示例 2:
  输入: num1 = "123", num2 = "456"
  输出: "56088" 
  */
function stringMultiply(num1, num2) {
  let result = 0;
  let temp = 0;
  let arr1 = num2.splice('');
  while (arr1.length) {
    result += ~~num1 * ~~arr1.pop() * factorial(temp);
    temp += 1;
  }
  function factorial(n) {
    if (n === 0) return 1;
    return 10 * factorial(n - 1);
  }
  return result;
}

// 5. 最长回文子串 leetcode
var longestPalindrome = function (s) {
  if (s.length < 2) {
    return s;
  }
  let res = '';
  for (let i = 0; i < s.length; i++) {
    // 回文子串长度是奇数
    helper(i, i);
    // 回文子串长度是偶数
    helper(i, i + 1);
  }

  function helper(m, n) {
    while (m >= 0 && n < s.length && s[m] == s[n]) {
      m--;
      n++;
    }
    // 注意此处m,n的值循环完后  是恰好不满足循环条件的时刻
    // 此时m到n的距离为n-m+1,但是mn两个边界不能取 所以应该取m+1到n-1的区间  长度是n-m-1
    if (n - m - 1 > res.length) {
      // slice也要取[m+1,n-1]这个区间
      res = s.slice(m + 1, n);
    }
  }
  return res;
};
console.log(longestPalindrome('asdasddsadfg'));

/* 腾讯&leetcode647:回文子串 #107
      给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
      具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
      示例 1:
      输入:"abc"
      输出:3
      解释:三个回文子串: "a", "b", "c"
      示例 2:
      输入:"aaa"
      输出:6
      解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
*/

// 暴力拆解
function backStringFun(str) {
  let count = 0;
  let leng = str.length;
  for (let i = 0; i < leng; i++) {
    for (let j = i; j < leng; j++) {
      if (addFun(str.substring(i, j + 1))) {
        count++;
      }
    }
  }
  function addFun(res) {
    let i = 0,
      j = res.length - 1;
    while (i < j) {
      if (res[i] != s[j]) return false;
      i++;
      j--;
    }
    return true;
  }
}

/*寻找两个正序数组的中位数
      https://github.com/sisterAn/JavaScript-Algorithms/issues/162
      */
const arr1 = [1, 2, 10];
const arr2 = [2, 5, 7, 8];

function findMiddleNumber(arr1, arr2) {
  // 容错处理
  if (!arr1.length && !arr2.length) return null;
  // 合并并排序
  const total = [...arr1, ...arr2].sort((a, b) => a - b);
  // 中位数索引
  let midIndex = (total.length - 1) / 2;

  // 两位
  if (String(midIndex).includes('.')) {
    const left = parseInt(midIndex);
    const right = parseInt(midIndex) + 1;
    const midNumber = (total[left] + total[right]) / 2;
    return midNumber.toFixed(5);
  } else {
    // 一位
    return total[midIndex].toFixed(5);
  }
}

console.log(findMiddleNumber(arr1, arr2));

/*
      const arr = [101,19,12,51,32,7,103,8];
      问题一: 找出连续最大升序的数量
      问题二: 找出不连续最大升序的数量
      */

const findLengthOfLCIS = (nums) => {
  if (nums.length <= 1) return nums.length;
  let max = 1,
    count = 1;
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] > nums[i - 1]) {
      count += 1;
    } else {
      count = 1;
    }
    max = Math.max(max, count);
  }
  return max;
};

function queryFn(arr) {
  if (!arr.length) return 0;
  let res = 1;
  let num = 0;
  let lastVal = 0;
  for (let i = 0, len = arr.length - 1; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[j] > lastVal) {
        num++;
        lastVal = arr[j];
      }
    }
    res = Math.max(res, num);
  }
  return res;
}

/*接雨水 https://github.com/sisterAn/JavaScript-Algorithms/issues/122
      给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
      示例 1:
      输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
      输出:6
      解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)
。*/
// 双指针
function trap(height) {
  let total = 0;
  let left = 0,
    right = height.length - 1,
    leftMax = 0,
    rightMax = 0;
  while (left <= right) {
    leftMax = Math.max(leftMax, height[left]);
    rightMax = Math.max(rightMax, height[right]);
    if (leftMax < rightMax) {
      total += leftMax - height[left];
      left++;
    } else {
      total += rightMax - height[right];
      right--;
    }
  }
  return total;
}

/*
 * @lc app=leetcode.cn id=1 lang=javascript
 *
 * [1] 两数之和
 */

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    /* 暴力拆解
    for(let i=0; i<nums.length; i++) {
        for(let j=i+1; j<nums.length; j++) {
            if(nums[i] + nums[j] === target) {
                return [i, j]
            }
        }
    }
    */
   // 哈希表
    let map = new Map();
    for(let i=0, leng = nums.length; i< leng; i++) {
        if(map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i]
        } else {
           map.set(nums[i], i)
        }
    }
};

/*
 * @lc app=leetcode.cn id=3 lang=javascript
 *
 * [3] 无重复字符的最长子串
 */

// @lc code=start
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
  let len = s.length;
  let arr = [];
  let max = 0;
  for (let i = 0; i < len; i++) {
    if (arr.indexOf(s[i]) === -1) {
      arr.push(s[i]);
    } else {
      arr.shift();
      i--;
      continue;
    }
    max = Math.max(max, arr.length);
  }
  return max;
};
// @lc code=end


/*
 * @lc app=leetcode.cn id=5 lang=javascript
 *
 * [5] 最长回文子串
 */

// @lc code=start
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  let start = -1;
  let len = s.length;
  let max = 0;
  for (let i = 0; i < len; i++) {
    let num = 1;
    let l = i - 1;
    while (s[i + 1] === s[i]) {
      num++;
      i++;
    }
    let r = i + 1;
    while (s[l] === s[r] && s[l]) {
      l--;
      r++;
      num += 2;
    }
    if (max < num) {
      max = num;
      start = l + 1;
    }
  }
  return s.slice(start, start + max);
};
// @lc code=end

/*
 * @lc app=leetcode.cn id=9 lang=javascript
 *
 * [9] 回文数
 */

// @lc code=start
/**
 * @param {number} x
 * @return {boolean}
 */
var isPalindrome = function (x) {
  if (x < 0) return false;
  let s = x.toString();
  let len = s.length;
  let max = 0;
  for (let i = 0; i < len; i++) {
    let num = 1;
    let l = i - 1;
    while (s[i + 1] === s[i]) {
      i++;
      num++;
    }
    let r = i + 1;
    while (s[l] === s[r] && s[l]) {
      l--;
      r++;
      num += 2;
    }
    if (max < num) {
      max = num;
    }
  }
  return max === len ? true : false;
};
// @lc code=end

/*
 * @lc app=leetcode.cn id=22 lang=javascript
 *
 * [22] 括号生成
 */

// @lc code=start
/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {
    let set = new Set(['()']);
    for(let i = 2; i <= n; i++) {
    let setChild = new Set();
        for(let item of set) {
            for(let sitem = 0; sitem < item.length; sitem++) {
                setChild.add(item.slice(0, sitem) + '()' + item.slice(sitem))
            }
        }
        
    set = setChild
    }
    return [...set];
};
// @lc code=end

/*
 * @lc app=leetcode.cn id=169 lang=javascript
 *
 * [169] 多数元素
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function (nums) {
  let leng = nums.length;
  let map = new Map();
  for (let i = 0; i < leng; i++) {
    let num = 1;
    if (map.has(nums[i])) {
      map.set(nums[i], map.get(nums[i]) + 1);
    } else {
      map.set(nums[i], num);
    }
  }
  for (let item of map.entries()) {
    // console.log(item[0], item[1]);
    if (item[1] > leng / 2) {
      return item[0];
    }
  }
};
// @lc code=end


/*LeetCode end*/