前端经典算法题

314 阅读4分钟

1.判断一个字符串是否回文

回文是指类似于“上海自来水来自海上”或者“madam”,从前往后和从后往前读,字符串的内容是一样的,称为回文。判断一个字符串是否是回文有很多种思路:

1.创建一个与原字符串前后倒过来的新字符串,比较二者是否相等,如果相等则是回文

1.1 利用中介Array.reverse()的反转数组的特性

function isPalindRome(str) {
  return str.split('').reverse().join('') === str;
}

console.log(isPalindRome('madam')); //true
console.log(isPalindRome('mada')); //false

1.2 不利用任何方法,手动创建新字符串

function isPalindRome(str) {
  let newStr = '';
  for(let i = str.length - 1; i >= 0; i --){
    newStr = newStr + str[i];
  }
  return newStr === str;
}

2.从字符串的头和尾开始,依次比较字符串组是否相等,逐渐往中间收,如果全部相等,则是回文

function isPalindRome(str) {
  let length = str.length;
  for(let i = 0; i <= Math.floor(str.length / 2); i ++){
      if(str[i] !== str[length - 1 - i]){
          return false;
      }
  }
  return true;
}

2.数组去重

2.1 利用ES6新增的Set,因为Set的元素是非重复的

function deduplicate(arr) {
  return [...new Set(arr)]

  // return Array.from(new Set(arr))
}

2.2 创建一个新数组,只包含源数组非重复的元素

function deduplicate(arr) {
  let newArray = [];
  for (let i of arr) {
    if (newArray.indexOf(i) === -1) {
      newArray.push(i);
    }
  }
  return newArray;
}

3: 统计字符串中出现最多次数的字符及其次数

function getMaxCount(str) {
  let resultMap = new Map();
  for (let letter of str) {
    if (resultMap.has(letter)) {
      resultMap.set(letter, resultMap.get(letter) + 1);
    } else {
      resultMap.set(letter, 1);
    }
  }

  let maxCount = Math.max(...resultMap.values())
  let maxCountLetters = []; //可能几个字符同时都是出现次数最多的,所以用一个Array去装这些字符
  resultMap.forEach((value, key, mapSelf) => {
    if (value === maxCount) {
      maxCountLetters.push(key);
    }
  })

  return {maxCountLetters, maxCount}
}

4.滑动窗口算法

什么是滑动窗口?

滑动窗口算法是在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这就降低了问题的复杂度,从而也降低了循环的嵌套深度。滑动窗口主要应用在数组和字符串的场景。

简单示例

先通过一个简单的示例来看一下滑动窗口的运作,比如有一个数组[1,3,5,6,2,2],设定滑动窗口(window)大小为3,那么当窗口从数组开始位置滑动到最终位置时依次计算每个窗口内3个元素的和,表示为sum。

滑动窗口

上图我们可以看出,随着窗口在数组上向右移动,窗口内的数据也在不断变化,我们只用对窗口内连续区间内的数据进行处理即可。由于区间是连续的,因此当窗口移动时只用对旧窗口的数据进行裁剪处理,这样便减少了重复计算,降低了时间复杂度。

以上图为例,当窗口位于[1,3,5]时,处理完该窗口的数据之后,将窗口向右移动一格,等于是将原有窗口左边的1裁剪掉,然后将窗口右边的6添加上,而整个过程看起来就像窗口在向右移动一样。

对于类似“请找到满足 xx 的最 x 的区间(子串、子数组)的 xx ”这类问题都可以使用该方法进行解决。

滑动窗口的基本步骤

需要注意的是:窗口的移动是按照移动的顺序来进行的;窗口的大小不一定是固定的,可以不断缩小或变大的。

对于滑动窗口算法的基本解题思路,以字符串S示例如下:

(1)采用双指针来指定窗口的范围,初始化left=right=0,而索引闭区间[left,right]便是一个窗口。
(2)不断增大窗口的right指针,直到窗口中的字符串满足条件。
(3)此时,停止right的增加,转而不断增加left指针,用于缩小窗口[left,right],直到窗口中的字符串不再符合要求。每增加一次left,需要更新一轮结果。
(4)重复第2和第3步,直到right到达字符串的尽头。
其中,第2步相当于在寻找一个「可行解」,然后第3步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

案例

题目: 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

function calLength(str) {
  let l = str.length;
  let max = 0, right = 0;
  let map = new Map();

  for (let i = 0; i < l; i++) {
    if (i !== 0) {
      map.delete(str.charAt(i - 1))
    }

    while(right < l && !map.has(str.charAt(right))) {
      map.set(str.charAt(right), str.charAt(right))
      right++;
    }

    max = Math.max(max, map.size)
  }

  return max
}

遍历二叉树所有节点

1.构造节点

function Node(value) {
  this.value = value
  this.left = left
  this.right = right
}

2.构造二叉树

function createNode() {
  let root = new Node('A')
  root.left = new Node('B')
  root.left.left = new Node('D')
  root.left.right = new Node('E')
  root.left.left.left = new Node('F')
  root.right = new Node('C')
  root.right.left = new Node('G')
  root.right.right = new Node('H')
  root.right.right.right = new Node('M')

  return root
}

3.遍历二叉树

function getAllNode(node) {
  let nodeList = []

  nodeList.push(node.value)

  if (node.left) {
    nodeList.push(...getAllNode(node.left))
  }
  if (node.right) {
    nodeList.push(...getAllNode(node.right))
  }

  return nodeList
}