算法题一、用js实现二分查找
1、递归的方式
function binarySearch(arr, target, startIndex, endIndex) {
const length = arr.length
if (length === 0) return -1
// 开始和结束的范围
if (startIndex === null) startIndex = 0
if (endIndex === null) endIndex = length - 1
// 如果start和end相遇,则结束
if (startIndex > endIndex) return -1
// 中间位置
const midIndex = Math.floor((endIndex + startIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧进行查找
return binarySearch(arr, target, startIndex, midIndex - 1)
} else if (target > midValue) {
// 目标值较大,则继续在右侧进行查找
return binarySearch(arr, target, midIndex + 1, endIndex)
} else {
return midIndex
}
}
2、循环的方式
function binarySearch(arr, target) {
const length = arr.length
if (length === 0) return -1
let startIndex = 0//开始位置
let endIndex = length - 1//结束位置
while (startIndex <= endIndex) {
const midIndex = Math.floor((endIndex + startIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧进行查找
endIndex = midIndex - 1
} else if (target > midValue) {
// 目标值较大,则继续在右侧进行查找
startIndex = midIndex + 1
} else {
return midIndex
}
}
return -1
}
总结:
递归会重复调用函数,造成消耗
- 递归-代码逻辑更加清晰
- 非递归性能好
- 时间复杂度O(logn)-非常快
- 凡事有二分,时间复杂度必包含O(logn)
算法题二、给一个数组,找出其中和为n的两个元素
有一个递增的数组[1,2,3,4,7,11,15]和一个n为15
数组中有两个数,和是n。即4+11 ===15
常规思路:
嵌套循环,找到一个数,然后去遍历下一个数,求和,判断
时间复杂度是O(n^2),不可用
function findTowNumbers1(arr, n) {
const length = arr.length
const res = []
if (length === 0) return res
for (var i=0;i<length-1;i++){
const n1 = arr[i]
let flag = false
for(var j = i+1;j<length;j++){
const n2 = arr[j]
if(n1+n2===n){
res.push(n1)
res.push(n2)
flag = true
break
}
}
if(flag) break
}
return res
}
利用递增 ( 有序 )的特性
随便找两个数如果和大于n,则需要向前寻找,二分法如果和小于n,则需要向后寻找
双指针,时间复杂度降低到O(n)
定义i指向头,j指向尾,求arr[i]+arr[j]
如果大于n,则j需要向前移动,
如果小于n,则i需要向后移动
function findTowNumbers2(arr,n){
const length = arr.length
const res = []
if (length === 0) return res
let i = 0
let j = length-1
while(i<j){
const n1 = arr[i]
const n2 = arr[j]
const sum = n1+n2
if(sum>n){
j--
}else if(sum<n){
i++
}else{
res.push(n1)
res.push(n2)
break
}
}
return res
}
算法题三、求一个二叉搜索树的第k小值
二叉树遍历(递归)
1、前序遍历:root->left->right
2、中序遍历:left->root->right
3、后序遍历:left->right->root
const tree = {
value:5,
left:{
value:3,
left:{
value:2,
left:null,
right:null
},
right:{
value:4,
left:null,
right:null
}
},
right:{
value:7,
left:{
value:6,
left:null,
right:null
},
right:{
value:8,
left:null,
right:null
}
}
}
// 前序遍历
function preOrderTraverse(node){
if(node===null) return
console.log(node.value)
preOrderTraverse(node.left)
preOrderTraverse(node.right)
}
preOrderTraverse(tree)//5 3 2 4 7 6 8
// 中序遍历
function inOrderTraverse(node) {
if (node === null) return
inOrderTraverse(node.left)
console.log(node.value)
inOrderTraverse(node.right)
}
inOrderTraverse(tree)//2 3 4 5 6 7 8
// 后序遍历
function postOrderTraverse(node) {
if (node === null) return
postOrderTraverse(node.left)
postOrderTraverse(node.right)
console.log(node.value)
}
postOrderTraverse(tree)//2 4 3 6 8 7 5
function getKthValue(tree,n){
inOrderTraverse(tree)
return arr[n]||null
}
console.log(getKthValue(tree,3))//5
总结:
1、二叉搜索树的特点:left<=root;right>=root
2、二叉搜索树的价值:可使用二分法进行快速查找
算法题四、斐波那契数列
用js计算斐波那契数列第n个值
注意时间复杂度
//递归方式
function fibonacci(n){
if(n<=0)return 0
if(n===1)return 1
return fibonacci(n-1)+fibonacci(n-2)
}
console.log(fibonacci(8))//21
优化,不用递归用循环
记录中间的结果
时间复杂度为O(n)
function fibonacci2(n) {
if (n <= 0) return 0
if (n === 1) return 1
let n1 = 1//记录n-1的结果
let n2 = 0//记录n-2的结果
let res = 0 //记录结果
for (var i = 2; i <= n; i++) {
res = n1 + n2
// 记录中间结果
n2 = n1
n1 = res
}
return res
}
console.log(fibonacci2(8))//21
注意:动态规划
把一个大问题,拆解为多个小问题,逐级向下拆解
用递归的思路去分析问题,再改为循环来实现
算法三大思维:贪心、二分、动态规划
青蛙连环跳
一只青蛙,一次可以跳一级台阶,也可以跳二级台阶
请问青蛙跳到n级台阶,总共有多少种方式
用动态规划来分析问题:要跳到1级台阶,就1种方式f(1) =1
要跳到2级台阶,就2种方式f(2) = 2
要跳到n级台阶,就f(n) = f(n-1)+f(n-2)
算法题五、将数组中所有的0移动到末尾
如:
输入[1,0,3,0,11,0],输出[1,3,11,0,0,0]
只移动0,其他顺序不变
必须在原数组进行操作
传统思路:遍历数组,遇到0则push到数组末尾,通过是splice截取当前元素,时间复杂度为O(n^2)—不可取(splice改变数组导致性能问题)
function moveZero(arr){
if(arr.length===0)return
let length = arr.length
let zeroLength = 0
for(var i=0;i<length-zeroLength;i++){
if(arr[i]===0){
arr.push(0)
arr.splice(i,1)//本身就有O(n)的时间复杂度
i--//数组截取了一个元素,i要递减,否则连续0就有错误
zeroLength++//累加0的长度
}
}
}
var arr=[1,0,3,0,11,0]
moveZero(arr)
console.log(arr)//[1,3,11,0,0,0]
优化思路:双指针
定义j指向第一个0,i指向j后面的第一个非0
交换i,j的值,继续向后移动
只遍历一次,所以时间复杂度为O(n)
注意:凡事嵌套循环,是不是可以考虑双指针来完成
function moveZero2(arr){
if(arr.length===0)return
let length = arr.length
let i//j后面的第一个非0
let j = -1//j指向第一个0
for(i=0;i<length;i++){
if(arr[i]===0){
if(j<0){
j = i
}
}
if(arr[i]!==0&&j>=0){
let n = arr[i]
arr[i] = arr[j]
arr[j] = n
j++
}
}
}
var arr=[1,0,3,4,0,11,0]
moveZero2(arr)
console.log(arr)//1,3,4,11,0,0,0
注意:数组是连续存储,要慎用splice、unshift等API