[路飞]_前端算法第二弹

524 阅读4分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

继上次的五道排序题之后,今天又做了五道题,来记录一下我做题和思考的过程,下次再记录经过整理优化之后的方法。这次记录主要是记录我看到这几道题的第一反应,由于做题做的少,所以第一反应往往不是那么准确,熟能生巧吧。

括号有效性判断

这道题是LeetCode第20题,难度简单。

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。

  2. 左括号必须以正确的顺序闭合。

    示例1: 输入:s = "()" 输出:true 示例2: 输入:s = "()[]{}" 输出:true 示例3: 输入:s = "(]" 输出:false 示例4: 输入:s = "([)]" 输出:false

这是一道经典的栈算法,我们这么看,比如一个桶里面,先进后出,后进先出,如果第一个遇到的是括号的右侧,那一定是错的,因为这个括号100%不能成对。如果是左边呢,我们就放进去,如果是右边呢,我们就看最后一个是不是它对应的左侧,如果是,就拿出来,成对抵消,再继续往里放,如果前一个不是它对应的左侧,那就返回FALSE,因为这两个不匹配的,永远都不能匹配了。就是这个道理。

所以我的代码是这样的。

function symbol(str) {
  let arr = str.split(",");
  let res = []; // 这是那个桶
  let len = 0; // 这是桶里面符号的个数

  for (let i = 0; i < arr.length; i++) {
    switch (arr[i]) {
			// 如果是右侧,查找上一个
      case "}":
        if (res[len - 1] == '{') {
          res.pop()
          len--
        } else {
          return false
        }
        break;
      case "]":
        if (res[len - 1] == '[') {
          res.pop()
          len--
        } else {
          return false
        }
        break;
      case ")":
        if (res[len - 1] == '(') {
          res.pop()
          len--
        } else {
          return false
        }
        break;
      default:
				// 如果是左侧,填入
        res.push(arr[i])
        len++
        break;
    }
  }
	// 全部查完,如果桶里没有了,说明全部配对
  if (res.length == 0) {
    return true
  } else {
    return false
  }
}

合并排序链表

给定一个链表数组,每个链表都已经按升序排列。

请将所有链表合并到一个升序链表中,返回合并后的链表。

示例1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例2:
输入:lists = []
输出:[]
示例3:
输入:lists = [[]]
输出:[]

这道题,也不用过多的解释,就是把多个链表合成一个,既然每个链表都是有序的,那我们对比每个链表的val,选择最小的那个,把它拿出来,填到我们新的链表里,直到所有的值都填完为止。

但是这里面有一个问题,我们JS的数据类型里没有链表,这也是我刚开始做这道题非常困惑的,那个时候我还不知道这个要自己写,现在我们就先来实现一下,如何从array→link的转变

// 这是生成一个链表的构造函数
ListNode = function (val) {
  this.val = val;
  this.next = null;
}

// array->link
generateLink = function (array) {
  const fakeHead = new ListNode(0); // 随便设一个头值
  let current = fakeHead;
	// 循环数组,把每一项都添加到链表里
  for (let i = 0; i < array.length; i++) {
    current.next = {
      val: array[i],
      next: null
    };
		// 把链表的指针指向当前链表的next,这样下次赋值赋到的就是当前链表的next上,形成了链
		// 可能是我基础不好,最开始这里我思考了很久这是什么意思
    current = current.next
  }
  return fakeHead.next
}
// link->array
exports.generateArray = function (link) {
  let arr = [];
  while (link) {
    arr.push(link.val);
    link = link.next;
  }
  return arr;
}

我们已经自己实现了,把数组转化成链表的过程,那么接下来就是实现代码了

let lists = [
  [1, 4, 5],
  [1, 3, 4],
  [2, 6]
]
// 把数组里每一项都变成一个链表
lists.map((v, i) => {
  lists[i] = generateLink(v)
})
// 设置一个初始链表
let list = {
  val: null,
  next: null
}
let link = list
contrast(lists);
// 将链表变回数组
let arr = generateArray(list.next)
console.log(arr);

function contrast(lists) {
  if (lists.length == 0) return;
  let len = lists.length;
  let minVal = Infinity;
  let index = 0;
	// 循环找到第一次的最小值
  for (let i = 0; i < len; i++) {
    if (lists[i] && lists[i].val < minVal) {
      minVal = lists[i].val;
      index = i;
    }
  }
	// 如果找到了,赋值
  if (minVal != Infinity) {
    link.next = {
      val: minVal,
      next: null
    }
    link = link.next;
  }
	// 如果找到的选项为null,则在数组中删除这一项,不为空则next
	// 这里如果下标为index的链表全部都赋值给了link,则这里变被置空
  if (lists[index] == null) {
    lists.splice(index)
  } else {
    lists[index] = lists[index].next;
  }
	// 循环下一次查找
  contrast(lists)
}

计数排序

通俗的讲就是找到数组arr中最大的数,把它当做新数组arr1的最大的下标。

然后一次遍历,把数组arr中所有的数都对应到arr1的下标,而arr1下标对应的值就是下标在arr中出现

的次数。我们这样表示arr = [5, 7, 4, 7, 8, 2, 1, 3], arr1[0, 1, 1, 1, 1, 1, 0, 2, 1]。

arr1表示为0个0, 1 个1, 1 个2, 1 个3, 1 个4, 1 个5, 0 个6, 2 个7, 1 个8

然后再新建一个数组,长度和arr相同,一次把arr1下标代表次数的数,填入到新数组中去

那么要实现一个计数排序,又拢共需要几步呢

  1. 新建两个数组,res和 list,res用来放返回值,list用来计数

  2. 找到原数组arr中最大的数,表示list的最大下标

  3. 循环原数组,填入list

  4. 根据list和arr,填入res

    function countSort(arr) { let len = arr.length; let res = []; // 用来放返回值 let list = []; // 用来给下标计数 let max = 0;

    for (let i = 0; i < len; i++) { // 确定list的最大下标 if (arr[i] > max) max = arr[i] } // 统计每个下标包含的数量 for (let i = 0; i < len; i++) { list[arr[i]] = list[arr[i]] ? list[arr[i]] + 1 : 1 } // 按大小顺序和数量,填入返回数组中 for (let i = 0; i < list.length; i++) { while (list[i] > 0) { res.push(i); list[i]-- } } console.log(res); }

有效的回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。

示例1:
输入:x = 121
输出:true
示例2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。

这个网上很多方法是将数字置反,然后对比,而我理解的就是头尾配对,对上了就成了。

function palindrome(arr) {
  let arr1 = arr;
  let len = arr.length;
  for (let i = 0; i < Math.floor(len / 2); i++) {
		// 依次查找,只要前后位置相同数不同,那就不是回文,只需要循环数字长度的一半就可以
    if (arr[i] != arr1[len - 1 - i]) return false
  }
  return true;
}

前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

输入:root = [1,null,2,3]
输出:[1,2,3]

输入:root = [1,2]
输出:[1,2]

输入:root = [1,null,2]
输出:[1,2]

前序遍历就是遍历二叉树时按照,根→左→右的顺序遍历,而且,整体和子树都是这个顺序。

比如说上面这个图,它的前序遍历就是[F,C,A,D,B,E,H,G,M]

以F为根的时候[根F,左[C,A,D,B],右[E,H,G,M]];

以C为根的时候[F,根C,左A,右[D,B],E,H,G,M];

以E为根的时候[F,C,A,D,B,根E,左H,右[G,M]]

所以我们可以理解,每次我们都要先找到根,然后找左树,找到左树的根之后,如果左树还有左树,那就继续,然后再找右树。

再实现前序遍历之前,我们还是首先要根据数组自己实现一个二叉树。

实现二叉树之前我们还要知道什么是二叉树,二叉树简单来说就是,每一个根的左边都要比根小,根的右边都要比根大

let str = "1,,2,3"
let arr = str.split(',').map(Number)
const tree = convertBinaryTree(arr);

function convertBinaryTree(arr) {
  let root;

  let insertNode = function (parentNode, childNode) {
    if (!childNode || childNode.val == '') return;
		// 小的放左边
    if (childNode.val < parentNode.val) {
      if (parentNode.left === null) parentNode.left = childNode;
      else insertNode(parentNode.left, childNode)
    } else {
			// 大的放右边
      if (parentNode.right === null) parentNode.right = childNode;
      else insertNode(parentNode.right, childNode)
    }
  }

  arr.forEach(val => {
    let node = {
      val: val,
      left: null,
      right: null
    }
    if (root) insertNode(root, node);
    else root = node
  })
  return root
}

接下来我们就要实现我们的代码了

let res = [] // 接收返回值

function preOrderTraversal(tree) {
  res.push(tree.val) // 先将根放入数组中
  if (tree.left) preOrderTraversal(tree.left); // 如果有左边,继续遍历左边
  if (tree.right) preOrderTraversal(tree.right);// 左边遍历结束,再遍历右边
}

我的理解可能有偏差,也可能有不足的地方,希望大家批评指正,这就是我对上面我道题的全部理解。