前端总结数据结构与算法基础

翻书问题或者走台阶问题。

问:共有n个台阶,每次只能上1个台阶或者2个台阶,共有多少种方法爬完台阶?共有n页书,每次只能翻1页或者2页书,共有多少种方法翻完全书?
ps:本质上是斐波那契数列问题。假设只有一个台阶,则只有一种跳法,f(1)=1;如果两个台阶,那么有两种跳法:1,一次跳一级,2,一次跳两级,f(2) = 2。如果大于2的n级台阶,那么一次跳一级台阶,剩下还有n-1级台阶,有f(n-1)种跳法。假如一次跳2级台阶,剩下n-2级台阶,有f(n-2)中跳法。这就表示f(n) = f(n-1)+f(n-2)。

function fibonacci(n) {
    if (n === 1 || n === 2) {
        return n;
    } else {
        return fibonacci(n-1) + fibonacci(n-2)
    }
}
// 一个记忆化的斐波那契数列
let tem = [0, 1]
function fibonacci(n) {
   let result = tem[n]
   if(typeof result !== 'number') {
        result = fibonacci(n-1)+fibonacci(n-2)
        tem[n] = result   // 将每次 fibonacci(n) 的值都缓存下来
   }
   return result
}
// 动态规划:从底部开始解决问题, 将所有小问题解决掉, 然后合并成一个整体解决方案, 从而解决掉整个大问题 。
function fibonacci(n) {
    let last = 1;
    let nextLast = 1;
    let result = 1;
    for (let i = 2; i < n; ++i) {
        result = last + nextLast;
        nextLast = last;
        last = result;
    }
    return result;
}

二分查找。

数组array包含了顺序的元素,[1,2,3,...,10],查找目标元素t是否在数组中。
我们已经提前知道数组是顺序排列的,比如递增顺序。
时间复杂度为O(logN)
递推公式:
f(N) = f(N/2) + O(1) = f(N/4) + 2 * O(1)
假设 N = 2 ^ M
最后可以推出
f(N) = O(logN)

let list = [1,2,3,4,5,6,7,8,9,10]
function binarySearch(array, t) {
    let left = 0, right = array.length - 1;
    while(left <= right) {
        let mid = parseInt((right + left)/2)
        if(array[mid] < t) {
            left = mid + 1
        } else if(array[mid] > t) {
            right = mid - 1
        } else {
            return true
        }
    }
    return false
}
// 递归写法
function binarySearch2(array, t, left, right) {
    if(left > right) return -1
    let mid = parseInt((right - left)/2) + left
    if(array[mid] === t) {
        return mid
    }else if(array[mid] < t){
        return binarySearch2(array, t, mid + 1, right)
    }else if(array[mid] > t){
        return binarySearch2(array, t, left, mid - 1)
    }
}
let test1 = binarySearch(list, 5)
let test2 = binarySearch2(list, 5, 0, list.length-1)
console.log(test1, test2)

贪心算法--最少硬币找零问题

所谓贪心,就是先选择当前阶段的最优解,不去考虑这次的选择会不会对未来造成影响,想要通过每个阶段的局部最优解,达到全局最优解。

假设你为一家自动售货机厂家编程序,自动售货机要每次找给顾客最少数量硬币,美国有以下面额的硬币:1美分、5美分、10美分、25美分。比如说要找36美分的零钱,我们可以用1个25美分、1个10美分和一个1美分。
(ps:找零问题,先找大额的硬币25分,依次递减)

function minCoinChange(amount, coins) {
    let minCoin = amount
    let count = 0
    for(let i = 0, l = coins.length; i < l; i++) {
        if (coins[i] <= minCoin) {
            count += Math.floor(minCoin / coins[i])
            console.log(coins[i] + '*' + Math.floor(minCoin / coins[i]))
            minCoin = minCoin % coins[i]
        }
    }
    return count
}
let counts = minCoinChange(61, [25, 10, 5, 1])
console.log(counts)

动态规划--01背包问题

image.png
function main(volume, value, c){
    let tArray = []
    let useGoodsNo = []
    for(let i = 0, l = volume.length; i <= l; i++) {
        tArray[i] = []
        useGoodsNo[i] = 0
        for(let j = 0; j <= c; j++) {
            if(i == 0 || j == 0){
                tArray[i][j] = 0
            }
        }
    }

    volume.unshift(0)  //让i = 1的时候对应的是1号物品
    value.unshift(0)
    
    for(let i = 1, l = volume.length; i < l; i++) {  // i从1开始 tArray[0][j]已经填满
        for(let j = 1; j <= c; j++) {
            if(j < volume[i]){
                tArray[i][j] = tArray[i-1][j]
            } else {
                //如果装了剩下的体积
                let leftVolume = j - volume[i]
                // 当前物品的价值
                let curValue = value[i]
                // 剩下的体积的价值是 (tArray[i - 1]因为0 1背包,不能重复放同个物品)
                let leftValue = tArray[i - 1][leftVolume]

                tArray[i][j] = Math.max(tArray[i-1][j], curValue+leftValue)
            }
        }
    }
    // 填满表格
    console.table(tArray)
    // 逆向获取物品
    let C = c
    for(let i = value.length-1; i > 0; i--){
        if(tArray[i][C] > tArray[i-1][C]){
            useGoodsNo[i] = 1
            C = C - volume[i]
        }
    }
    console.table(useGoodsNo)  // 所选的物品
    console.log(tArray[value.length-1][c])  // 最大价值
}
main([2,3,4,5], [3,4,5,6], 8)

展平数组

// 展平数组 [[1, 2], 3, [[[4], 5]]] => [1, 2, 3, 4, 5]
function flatten(arr) {
    return [].concat(
        ...arr.map(x => Array.isArray(x) ? flatten(x) : x)
    )
}

随机打乱数组

function shuffle(arr) {
    for(let i = 0; i < arr.length-1; i++){
        const j = i + Math.floor(Math.random() * (arr.length - i))
        [arr[i], arr[j]] = [arr[j], arr[i]]
    }
}

函数节流,防抖

// 函数节流
function throttle(func, delay = 60) {
    let lock = false
    return (...args) => {
        if(lock) return
        func(...args)
        lock = true
        setTimeout(()=> {
            lock = false
        }, delay)
    }
}
// 防抖函数
function debounce (fn, delay = 60) {
        let timer
        return (...arg) => {
            clearTimeout(timer)
            timer = setTimeout(()=>{
                fn(...arg)
            }, delay)
        }
    }

深拷贝

let obj = {
        a: 100,
        b: [10, 20, 30],
        c: {
            x: 10
        },
        d: /^\d+$/
}
function deepClone(obj) {
    if(obj === null) return null
    if(typeof obj != 'object') return obj
    if(obj instanceof RegExp) { return new RegExp(obj) }
    if(obj instanceof Date) { return new Date(obj) }
        
    let newObj = new obj.constructor // 不直接创建空对象,克隆的结果和之前保持相同的所属类
    for(let key in obj){
        if(obj.hasOwnProperty(key)) {
            newObj[key] = deepClone(obj[key])
        }
    }
    return newObj
}

柯里化

对于curry(foo),g函数参数足够4个,就调用foo(a,b,c,d),如果小于4就返回一个可以继续积累参数的函数
const curry = func => {
    const g = (...allArgs) => allArgs.length >= func.length ?
    func(...allArgs) : (...args) => g(...allArgs, ...args)
    return g
} 
const foo = curry((a, b, c, d) =>{
    console.log(a, b, c, d)
})
foo(1)(2)(3)(4) // 1 2 3 4
foo(1)(2)(3)  // 不返回
const f = foo(1)(2)(3)
f(4) // 1 2 3 4 5

滑动窗口

image.png
力扣1343题,求大小为 K 且平均值大于等于threshold的子数组数目,和所有子数组?
var arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
var numOfSubarrays = function(arr, k, threshold) {
    let sums = 0  // 子数组的和
    let nums = 0  // 结果数目
    let target = k * threshold
    let result = []  // 结果子数组
    let sub = []  

    if (arr.length < k) return 0

    // 初始化子数组
    for(let i = 0; i < k; i++) {
        sums += arr[i]
        sub.push(arr[i])
    }
    if (sums >= target){
        result.push(sub.join(','))
        nums++
    }

    // 滑动窗口
    for(let i = k, l = arr.length; i < l; i++) {
        sums = sums - arr[i - k]
        sums = sums + arr[i]

        sub.shift()
        sub.push(arr[i])

        if (sums >= target) {
            result.push(sub.join(','))
            nums++
        }
    }

    return {
        nums,
        result
    }
}

let result = numOfSubarrays2(arr, k,threshold)
console.log(result)

Y组合子

const y = function(le) {
    return function (f) {
        return f(f)
    }(function (f) {
        return le(
            function(...x) {
                return (f(f))(...x)
            }
        )
    })
}
const curryY = func => y(g => {
        (...allArgs) => {
            allArgs.length >= func.length ? func(...allArgs) : (...args) =>g(...allArgs, ...args)
        }
    }
)
const foo = curryY((a, b, c, d) => {
    console.log(a, b, c, d)
})
foo(1)(2)(3)(4)

链表

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

function ListNode(val) {
    this.val = val
    this.next = null
}
let node1 = new ListNode(1)
let node2 = new ListNode(2)
let node3 = new ListNode(3)
node1.next = node2
node2.next = node3
console.log(node1)

function recursiveTraverse(head) {
    if(head != null) {
        console.log(head.val)
        recursiveTraverse(head.next)
    }
}
recursiveTraverse(node1)

// 翻转一条单向链表
// 输入 1 -> 2 -> 3 -> null
// 输出 3 -> 2 -> 1 -> null
function reverseLinkedList(head){
    let dummy = head
    let tem = dummy
    while(head != null && head.next != null){
        dummy = head.next
        head.next = dummy.next
        dummy.next = tem
        tem = dummy
    }
    return dummy
}
let reverseLink = reverseLinkedList(node1)
console.log(reverseLink)

二叉树

function TreeNode(val) {
    this.val = val
    this.left = null
    this.right = null
}
let root = new TreeNode(2)
root.left = new TreeNode(1)
root.right = new TreeNode(3)
console.log(root)
/*
*     2
*   /   \
*  1     3
*/

/*
    中序遍历:(
        定义:
        1,中序遍历左子树
        2,遍历根节点
        3,中序遍历右子树
    )
    前序遍历:(
        定义:
          1,遍历根节点
          2,前序遍历左子树
          3,前序遍历右子树
    )
    后序遍历:(
        定义:
          1,后序遍历左子树
          2,后序遍历右子树
          3,遍历根节点
    )
*/

function inOrder(root) {
    if(root) {
        console.log('前',root.val)  // 2 1 3
        inOrder(root.left)
        console.log('中',root.val)  // 1 2 3
        inOrder(root.right)
        console.log('后',root.val)  //  1 3 2
    }
}
inOrder(root)

/*
    二叉查找树/二叉搜索树:(
        定义:
        左子树的所有节点的值小于根节点
        右子树的所有节点的值大于根节点
        左子树和右子树都是二叉查找树
    )
    二叉搜索树的中序遍历就是排好序的元素。
*/
function binarySearchTreeFind(root, target) {
    if(!root) return false
    if(root.val === target) {
        return true
    } else if(root.val < target){
        return binarySearchTreeFind(root.right, target)
    } else if(root.val > target) {
        return binarySearchTreeFind(root.left, target)
    }
}
console.log(binarySearchTreeFind(root, 1))

First In Last Out(FILO)
先进后出,后进先出

function Stack(){
    this.stack = []

    this.isEmpth = function(){
        return this.size() === 0
    }
    this.size = function() {
        return this.stack.length
    }
    // 出栈
    this.pop = function(){
        if(this.isEmpth()){
            return null
        } else {
            return this.stack.pop()
        }
    }
    // 进栈
    this.push = function (val){
        return this.stack.push(val)
    }
        // 返回栈顶元素
    this.peak= function(){
        if(this.isEmpth()) {
            return null
        } else {
            return this.stack[this.stack.length - 1]
        }
    }
}

let test = new Stack()
test.push(1)
console.log(test.peak())
console.log(test.isEmpth())
test.pop()
console.log(test.peak())

队列

First In First Out(FIFO)
先进先出

function Queue() {
    this.queue = []

    this.size = function() {
        return this.queue.length
    }
    this.isEmpty = function(){
        return this.size() === 0
    }
    // 入队列
    this.enqueue = function (val){
        this.queue.unshift(val)
    } 
    // 出队列
    this.dequeue = function(){
        if(this.isEmpty()){
            return null
        } else {
            return this.queue.pop()
        }
    }
}
let test = new Queue()
test.enqueue(1)
test.enqueue(2)
console.log(test.queue)
test.dequeue()
console.log(test.queue)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容