给定数组arr,取n个数,和为sum,有哪些种取法

递归解法

function main(arr, sum, n) {
    let result = []
    if (n === 1) {
        arr.filter(item => item === sum)
            .forEach(item2 => result.push([item2]))
        return result
    }
    for (let i = 0; i < arr.length; i++) {
        let el = arr[i]
        let subArr = arr.slice()
        subArr.splice(i, 1)
        let subResult = main(subArr, sum - el, n - 1)
        if (subResult.length > 0) {
            subResult.forEach(item3 => {
                item3.unshift(el)
                result.push(item3)
            })
        }
    }
    return result
}

let result = main([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 18, 3)
let result2 = result.map(item => item.sort().join(","))
console.log([...new Set(result2)])

递归优化,计算过程中去重

思路一的做法,存在大量的重复,实际上对 for 循环做一点修改,就可以在过程中避免重复

function main(arr, n, sum) {
    if (n === 1) {
        return arr.includes(sum) ? [
            [sum]
        ] : []
    }

    let result = []
    for (let i = 0; i < arr.length - n; i++) {
        let arr1 = arr.slice(i + 1)
        let addend = arr[i]
        let arr2 = main(arr1, n - 1, sum - addend)
        arr2.forEach(r => {
            r.unshift(addend)
            result.push(r)
        })
    }
    return result
}

let result = main([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 15)
debugger

迭代

function main(arr, n, sum) {
    let result = []
    let step = 1
    let stack = [{
        sum: [],
        arr
    }]
    while (step < n) {
        let newStack = []
        stack.forEach(s => {
            for (let i = 0; i < s.arr.length; i++) {
                let newSum = [...s.sum, s.arr[i]]
                if (newSum.reduce((a, b) => a + b, 0) < sum) {
                    newStack.push({
                        sum: newSum,
                        arr: s.arr.slice(i + 1)
                    })
                }
            }
        })
        stack = newStack
        step++
    }
    stack.forEach(s => {
        for (let i = 0; i < s.arr.length; i++) {
            let newSum = [...s.sum, s.arr[i]]
            if (newSum.reduce((a, b) => a + b, 0) === sum) {
                result.push([...s.sum, s.arr[i]])
            }
        }
    })
    return result
}

let result = main([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 15)
debugger

砝码问题

给一组砝码,给一个重量,问用该组砝码能否称出该重量。例如,一组砝码 [1,3],一个重量 2,返回 true

递归

function main(arr, weight) {
    // 终止条件
    if (arr.length === 0) {
        return false
    }
    if (arr.length === 1) {
        return arr[0] === weight ? true : false
    }

    if (arr.includes(weight)) {
        return true
    }

    let item = arr[0]
    if (main(arr.slice(1), weight)) {
        return true
    }

    if (main(arr.slice(1), weight + item)) {
        return true
    }

    if (main(arr.slice(1), weight - item)) {
        return true
    }

    return false
}

let result = main([1, 2, 10], 6)
debugger

迭代

function main(arr, weight) {
    let stack = [0]
    let i = 0
    while (i < arr.length) {
        let stackCopy = []
        for (let j = 0; j < stack.length; j++) {
            let s = stack[j]
            if (s === weight) {
                return true
            }
            if (s + arr[i] === weight) {
                return true
            }
            if (s - arr[i] === weight) {
                return true
            }
            stackCopy.push(s + arr[i])
            stackCopy.push(s - arr[i])
            stackCopy.push(s)
        }
        stack = stackCopy
        i++
    }
    return false
}

let result = main([1, 2, 10], 3)
debugger

+1,*2计算最少步数

给一个数,只能从1开始,+1或者*2,问最少多少步可以达到这个数

function main(x) {
    let result = []
    result.push(Math.ceil(x / 2))
    // 7 => (1+1+1)*2+1 共4步
    // 12 => (1+1+1+1+1+1)*2 共6步
    let i = 1
    let count = 0
    while (i * 2 <= x) {
        i = i * 2
        count++
    }
    count = count + x - i
    result.push(count)
    return result
    // 用 +1 的方法,步数始终是 Math.ceil(x/2)
    // 用 *2 后 +1 的方法,主要是考虑当 2^(n+1) > x > 2^n 时,从 2^n 到 x 需要进行多少次 +1
}

let result = main(15)
debugger

斐波那契数列

传统递归方法

function fib1(n) {
    if (n < 2) {
        return n
    }
    return fib1(n - 1) + fib1(n - 2)
}

console.log(fib1(10))

迭代

function fib2(n) {
    if (n < 2) {
        return n
    }
    let arr = [0, 1]
    for (let i = 2; i < n + 1; i++) {
        arr.push(arr[i - 1] + arr[i - 2])
    }
    return arr[n]
}

console.log(fib2(10))

迭代优化

function fib3(n) {
    if (n < 2) {
        return n
    }
    let arr = [0, 1]
    for (let i = 2; i < n; i++) {
        let sum = arr[0] + arr[1]
        arr[0] = arr[1]
        arr[1] = sum
    }
    return arr[0] + arr[1]
}

console.log(fib3(10))

递归优化一

function fib4(n, p, k) {
    if (n === 1) {
        return k
    }
    if (n === 0) {
        return p
    }
    return fib4(n - 1, k, p + k)
}

console.log(fib4(10, 0, 1))

递归优化二

function fib5(n, p, k) {
    if (n === 1) {
        return k
    }
    if (n === 0) {
        return p
    }
    return fib5(n - 2, p + k, p + k + k)
}

console.log(fib5(10, 0, 1))

一点感悟

迭代是自下而上,递归是自上而下

求 0,1,1,2,3,5,8,13,21,34,55 的fib4(10)

等于求 1,1,2,3,5,8,13,21,34,55 的fib4(9)

等同于求 1,2,3,5,8,13,21,34,55 的fib4(8)

每递归调用一次,都自下而上计算一次

递归的关键还是在于如何划分子问题,确定子问题与父问题之间的关系


nbb3210
436 声望31 粉丝

优雅地使用JavaScript解决问题