算法学习记录(四十四)

119 阅读2分钟

问:

  1. 现在有两个数组arr1,arr2。数组每个元素都是一种面值的硬币。arr1都为普通币,每一种可以取任意枚,arr2都为纪念币,每种只能取一枚。每种硬币都有一个面值,问能拼出面值M的方法数
  2. 4. 寻找两个正序数组的中位数
  3. 同时运行N台电脑的最长时间

解:

  1. 先写出只用arr1拼出0 ~ M的方法和只用arr2拼出0 ~ M的方法。然后遍历0 ~ M,分别选择让arr1拼i个,让arr2拼M-i个。
function getAllNums(arr1, arr2, M) {
    const res1 = getAllPlans1(arr1, M)
    const res2 = getAllPlans2(arr2, M)
    let sum = 0
    // res1是普通币拼出0~M的所有方法数 res2是纪念币拼出0~M的所有方法数
    // 让普通币拼0,纪念币拼M。让普通币拼1,纪念币拼M-1。。。普通币拼M,纪念币拼0
    // 以上所有结果累加返回
    for (let i = 0; i <= M; i++) {
        const one = res1[i]
        const tow = res2[M - i]
        sum += one * tow
    }
    return sum
    function getAllPlans1(arr, target) {
        const dp = []
        // 创建二维表
        for (let i = 0; i <= arr.length; i++) {
            dp[i] = []
            for (let j = 0 ;j <= target ;j++) {
                if (i === arr.length) {
                    dp[i][j] = 0
                }
                if (j === 0) {
                    dp[i][j] = 1
                }
            }
        }
        for (let i = arr.length -1; i >=0;i--) {
            for (let j =0;j<=target;j++) {
                dp[i][j] = (dp[i][j-arr[i]] ?? 0 ) + dp[i+1][j]
            }
        }
        return dp[0]
    }
    function getAllPlans2(arr, target) {
        const dp = []
        for (let i = 0; i <= arr.length; i++) {
            dp[i] = []
            for (let j = 0 ;j <= target ;j++) {
                if (i === arr.length) {
                    dp[i][j] = 0
                }
                if (j === 0) {
                    dp[i][j] = 1
                }
            }
        }
        for (let i = arr.length - 1; i >= 0; i--) {
            for (let j = 0; j <= target; j++) {
                dp[i][j] = dp[i + 1][j] + (dp[i + 1][j - arr[i]] ?? 0)
            }
        }
        return dp[0]
    }
}
  1. 算法原型是两个有序数组中查找第k大的数
    1. 先看查找第k大的数如何解,因为是两个有序数组,所以使用二分查询
    2. 二分查询数组1,判断中位数的元素在另一个数组中可以比多少个数大(考虑到可能存在相同数字,所以要求两个值,分别是这个数在数组中最少压过多少数、最多压过多少数)
    3. 若二分查询结果中这个数最少能压过的数比k大,那么说明这个数太大了,若最多能压过的数比k小,说明这个数太小了。若k在两者之间,说明就是这个数,返回这个数即可
    4. 第k大的数不是在数组1中就是在数组2中,所以若数组1没查到第k大的数,那么就在数组2中
    5. 有了上述的算法原型,把k设定为中位即可
const findMedianSortedArrays = function(nums1, nums2) {
    const len = nums1.length + nums2.length
    const k = len / 2
    const findMidNum = (arr, otherArr, left, right, target) => {
        while (left <= right) {
            const mid = (left + right) >> 1
            // 判断midItem在另一个数组中最多能压几个数和最少能压几个数
            const otherLenMax = findItemIdxMax(otherArr, arr[mid]) + 1
            const otherLenMin = findItemIdxMin(otherArr, arr[mid]) + 1
            // 如果当前压的个数最少都超过了target,说明这个值压多了
            if (mid + otherLenMin > target) {
                right = mid - 1
            } else if (mid + otherLenMax >= target && mid + otherLenMin <= target) {
                // 如果当前压的个数在范围内
                return arr[mid]
            } else {
                // 当前压的个数最多都小于target,说明这个值压少了
                left = mid + 1
            }
        }
    }
    const findItemIdxMax = (arr, target) => {
        let left = 0
        let right = arr.length - 1
        let res = -1
        while (left <= right) {
            const mid = (left + right) >> 1
            if (arr[mid] <= target) {
                res = mid
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return res
    }
    const findItemIdxMin = (arr, target) => {
        let left = 0
        let right = arr.length - 1
        let res = -1
        while (left <= right) {
            const mid = (left + right) >> 1
            if (arr[mid] < target) {
                res = mid
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return res
    }
    const getNum = (target) => findMidNum(nums1, nums2, 0, nums1.length - 1, target) ??
        findMidNum(nums2, nums1, 0, nums2.length - 1, target)
    if (Number.isInteger(k)) {
        const num1 = getNum(~~k)
        const num2 = getNum(~~k - 1)
        return (num1 + num2) / 2
    } else {
        return getNum(~~k)
    }
};

function maxRunTime(n, arr) {
    arr.sort((a, b) => a- b)
    // 预处理,从当前位置结束的电量累加和
    const helpArr = []
    for (let i = 0; i <arr.length; i++) {
        helpArr[i] = arr[i] + (helpArr[i - 1] ?? 0)
    }
    // 运行时间可能的最大值。电池总和 / n
    const possibleMax = Math.ceil(helpArr[arr.length - 1] / n)

    // 二分尝试,找最大的结果
    let left = 0
    let right = possibleMax
    let maxTime = 0
    while (left <= right) {
        const i = Math.floor((left + right) / 2)
        // 小于i的最右下标
        const smallIdx = getIdxOfItem(arr, i)
        // 具备充足电量的电池个数
        const num = arr.length - 1 - smallIdx
        // 还需要搞定多少台电脑
        const need = n - num
        // 所有碎片电池总和
        const remain = helpArr[smallIdx]
        if (need <= 0 || remain >= need * i ) {
            maxTime = i
            left = i + 1
        } else {
            right = i - 1
        }
    }
    return maxTime

    // 找到小于item的最右的下标
    function getIdxOfItem(arr, item) {
        let left = 0
        let right = arr.length - 1
        let mostRight = -1
        while (left <= right) {
            const midIdx = Math.floor((right + left) / 2)
            if (arr[midIdx] < item) {
                left = midIdx + 1
                mostRight = midIdx
            }
            if (arr[midIdx] >= item) {
                right = midIdx - 1
            }
        }
        return mostRight
    }
}