「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
继上次的五道排序题之后,今天又做了五道题,来记录一下我做题和思考的过程,下次再记录经过整理优化之后的方法。这次记录主要是记录我看到这几道题的第一反应,由于做题做的少,所以第一反应往往不是那么准确,熟能生巧吧。
括号有效性判断
这道题是LeetCode第20题,难度简单。
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
-
左括号必须用相同类型的右括号闭合。
-
左括号必须以正确的顺序闭合。
示例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下标代表次数的数,填入到新数组中去
那么要实现一个计数排序,又拢共需要几步呢
-
新建两个数组,res和 list,res用来放返回值,list用来计数
-
找到原数组arr中最大的数,表示list的最大下标
-
循环原数组,填入list
-
根据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);// 左边遍历结束,再遍历右边
}
我的理解可能有偏差,也可能有不足的地方,希望大家批评指正,这就是我对上面我道题的全部理解。