目录
广度优先算法思想
广度优先搜索使用队列(queue,先进先出)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
题目
素数行李箱密码
广度优先算法(BFS)、递归、哈希算法
题目描述
某行李箱支持4位数字密码,每位数字在0和9之间,开锁方式:
每次操作改变其中1位数字。(注意,是改变,比如0023改变第4位后,变成0029)每次操作后的数字必须始终是素数(如23和29是素数)。
现给定行李箱的初始密码与解锁密码(都是素数,包含前导0),请找出最快的开锁方式(改变次数最少),输出改变次数,如果无法解锁,输出-1。
解答要求
时间限制:1000ms,内存限制:256MB
输入
两个4位数字字符,分别表示初始密码与解锁密码,以单个空格分隔。
输出
一个整数,表示开锁的最少改变次数;无法解锁则输出-1。
样例
输入样例1
0023 0059
输出样例1
2
提示样例1
0023->0059,存在两种开锁方式:0023->0029->0059,或0023->0053->0059,操作次数都是2
输入样例2
1373 8017,存在一种开锁方式:1373->0373->0313->0317->8317->8017,需要5次操作。
提示
素数,又称质数,指在大于1的自然数中,除了1和该数自身外,无法被其它自然数整除的数。
答案
//定义队列,先进先出,用于广度优先算法
function Queue() {
this.q = new Array()
this.enQueue = (val) => {
this.q.push(val)
}
this.deQueue = () => {
return this.q.shift()
}
this.isEmpty = () => {
return this.q.length === 0
}
this.size = () => {
return this.q.length
}
}
//判断是否为质数
const isPrime = (val) => {
var val = Number(val)
if (val === 0 || val === 1) {
return false
} else if (val === 2) {
return true
} else {
let t = Math.sqrt(val)
for (let i = 2; i <= t; i++) {
if (val % i === 0) {
return false
}
}
}
return true
}
//求只改变一位数字能产生多少个素数
const adjacent = (cur) => {
let nodes = []
let charators = cur.split('')
for (let i = 0; i < 4; i++) {
let iValue = Number(charators[i])
let copy = [...charators]
for (let j = 0; j <= 9; j++) {
copy[i] = j
if (j != iValue && isPrime(copy.join(''))) {
nodes.push(copy.join(''))
}
}
}
return nodes
}
const unlock = (initState, dstState) => {
let queue = new Queue()
//用于标记该密码是否已经使用过
let visited = {}
let sum = 0
queue.enQueue(initState)
visited[initState] = true
//队列不为空就继续循环
while (!queue.isEmpty()) {
let t = queue.size()
let xlNodes = []
//将现在队列中的每个数字拿出队列进行一次计算后再将符合条件的相邻素数放入队列
for (let i = 0; i < t; i++) {
let cur = queue.deQueue()
//出口,找到了密码
if (cur === dstState) {
return sum
}
//当前数字的相邻素数
xlNodes = adjacent(cur)
//将没有访问过的素数存入队列中,并标记为访问过
for (let j = 0; j < xlNodes.length; j++) {
if (!visited[xlNodes[j]]) {
queue.enQueue(xlNodes[j])
visited[xlNodes[j]] = true
}
}
}
sum++
}
console.log(
unlock('1373', '8017')
)
解析
注意用到数组时,要使用浅拷贝。
核心思想
创建一个队列,每次从队列中拿出当前初始密码,计算相邻的素数密码后全部放入队列,并将放入过队列的密码标记。每次放入队列前要进行判断,未做标记的才能放入。循环直到队列为空(找不到密码)或找到密码。
二叉树的最大深度
求给定二叉树的最大深度,
深度是指树的根节点到任一叶子节点路径上节点的数量。
最大深度是所有叶子节点的深度的最大值。
(注:叶子节点是指没有子节点的节点。)
function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
}
function maxDepth(root) {
}
题链接
解析
注意flat函数是用于拍平数组的层级。
JavaScript中常用函数方法(数组篇)_javascript 函数数组_YF-SOD的博客-CSDN博客
核心思想
广度优先:利用数组存储二叉树第一层的节点,然后每次循环将第二层的元素返回,递归,直到数组中没有节点,返回循环的次数即为二叉树的最大深度
递归、深度优先:首先找到上下层级直接的关系即f(n)和f(n.left)、f(n.right)的关系,f(n)=Math.max(f(n.left),f(n.right))+1.然后确定出口,f(0)=0.
答案
方法一(广度优先)
function maxDepth(root) {
let arr = [root], count = 0
while (arr.length && root) {
if (arr.length) {
count++
arr = arr.map(v => {
if (v.left && v.right) {
return [v.left, v.right]
} else if (v.left) {
return v.left
} else {
return v.right
}
}).flat().filter(v => v)
}
}
return count
}
方法二(深度优先、递归)
function maxDepth( root ) {
if(root === null){
return 0
}
return Math.max(maxDepth(root.left),maxDepth(root.right))+1
}
对称二叉树
给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)
例如: 下面这棵二叉树是对称的
下面这棵二叉树不对称。
function isSymmetrical(pRoot) {
}
题链接
解析
注意广度优先时需要从数组尾部加入,从头部取出,保证遍历是一层一层的遍历。
核心思想
广度优先:
1.利用数组存储二叉树第二层的2个节点
2.取出2个节点比较,不相等返回false。相等,将4个子节点分2组从尾部加入数组,每组2个子节点为需要比较的。
3.每次循环从数组头部取出2个节点重复2的操作,直到数组为空返回true
递归、深度优先:
1、确定入参是需要比较的2个节点。
2.找到上下层级的关系f(left,right)=f(left.left,right.right)&&f(left.right,right.left)
3.找出口比较的2个节点为空时返回true,2个节点不相等时直接返回false。
答案
方法一(广度优先)
function isSymmetrical(pRoot) {
if (!pRoot) {
return true
}
function check(node1, node2) {
let queue = [];
queue.push(node1);
queue.push(node2);
while (queue.length) {
let p = queue.pop(), q = queue.pop();
if (!p && !q) {
continue;
} else if (!p || !q || p.val !== q.val) {
return false;
}
queue.push(p.left);
queue.push(q.right);
queue.push(p.right);
queue.push(q.left);
}
return true;
}
return check(pRoot.left, pRoot.right)
}
方法二(递归、深度优先)
function isSymmetrical(pRoot) {
if (!pRoot) {
return true
}
function compare(l, r) {
if (l === null && r === null) {
return true
}
if (l === null || r === null) {
return false
}
if (l.val !== r.val) {
return false
}
return compare(l.left, r.right) && compare(l.right, r.left)
}
return compare(pRoot.left, pRoot.right)
}
合并二叉树
已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。例如:
两颗二叉树是:
Tree 1
Tree 2
合并后的树为
function mergeTrees(root1, root2) {
}
题链接
解析
注意递归时不是在最后return中递归了,而是在函数体中。
核心思想
广度优先:
1.将2个节点存入数组中;
2.每次循环从数组头部取出2个节点合并,并将子节点分2组,每组为需要合并的2个节点存入数组尾部。
3.循环2的操作,直到数组为空,返回合并的节点。
递归、深度优先:首先找到上下级的关系f(x,y)=f(x.left,y.left)+f(x.right,y.right),然后找到出口当有一个不为null就进行合并。
答案
方法一(广度优先)
function mergeTrees(t1, t2) {
if (!t1) return t2;
if (!t2) return t1;
if (!t1 && !t2) return null;
let queue = [t1, t2];
while (queue.length) {
let root1 = queue.shift();
let root2 = queue.shift();
root1.val += root2.val;
if (root1.left && root2.left) {
queue.push(root1.left, root2.left)
}
if (root1.right && root2.right) {
queue.push(root1.right, root2.right)
}
if (!root1.left && root2.left) root1.left = root2.left
if (!root1.right && root2.right) root1.right = root2.right
}
return t1
}
方法二(递归、深度优先)
function mergeTrees(t1, t2) {
if (t1 !== null && t2 !== null) {
t1.val += t2.val
t1.left = mergeTrees(t1.left, t2.left)
t1.right = mergeTrees(t1.right, t2.right)
} else if (t2 !== null && t1 === null) {
t1 = t2
}
return t1
}