LeetCode 每日一题:最大为 N 的数字组合

185 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 8 天,点击查看活动详情

最大为 N 的数字组合

原题地址

给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

返回 可以生成的小于或等于给定整数 n 的正整数的个数 。

示例 1:

输入:digits = ["1","3","5","7"], n = 100
输出:20
解释:
可写出的 20 个数字是:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

示例 2:

输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。

示例 3:

输入:digits = ["7"], n = 8
输出:1

提示:

  • 1 <= digits.length <= 9
  • digits[i].length == 1
  • digits[i] 是从 '1' 到 '9' 的数
  • digits 中的所有值都 不同 
  • digits 按 非递减顺序 排列
  • 1 <= n <=10910^9

思路分析

  • 如果组成数字的位数小于 n 的位数,那么一定满足小于n的条件
  • 大于n的位数,则不满足条件;
  • 等于n的位数,那么需要分情况讨论「针对要组成数字的 第i位 来分析」;
    • 第i位current,为了满足题目中小于n的条件,那么只能在[1, current] 中取数,然而数字都取自于 nums,又由于nums本身是有序的,因此可以使用二分法找到小于 nums[j] 的最大下标 right,然后分析 nums[right]current 的值大小:
      • nums[right] < current: 位置 i 共有 right+1 中选择,后面的每个位置都有 digits.length 种选择,有 n-i 个位置;
      • nums[right] === current: 位置 i 共有 right+1 中选择, 若 i 之前的数字选择 right 之前的数字,那么肯定小于 current,后面的每个位置都有 digits.length 种选择,有 n-i 个位置;
      • nums[right] > cur: 说明 nums 中所有元素都比 current 大,因此不能选择。

AC 代码

/**
 * @param {string[]} digits
 * @param {number} n
 * @return {number}
 */
var atMostNGivenDigitSet = function(digits, n) {
    let res = 0
    let arr = []
    while (n !== 0) {
        arr.push(n % 10)
        n = parseInt(n / 10)
    }
    n = arr.length
    let m = digits.length
    const nums = []
    for (let i = 0; i < m; i++) {
        nums[i] = parseInt(digits[i])
    }
    // 位数 小于 n 的部分
    for (let i = 1; i < n; i++) {
        res += permutations(m, i)
    }
    // 位数 等于 n 的部分
    for(let i = n - 1, j = 1; i >= 0; i--, j++) {
        let current = arr[i]
        if (nums[0] > current) {
            break
        }
        if (nums[m - 1] < current) {
            res += m * permutations(m, n - j)
            break
        }
        const left = binarySearch(nums, current)
        if (nums[left] < current) {
            res += (left + 1) * permutations(m, n - j)
            break;
        }
        res += left * permutations(m, n - j)
        if (i === 0) {
            res++
        }
    }
    return res
}

/**
* 排列数:每次可以选择k个数字,选择n次,结果数
* @param k
* @param n
* @return
*/
const permutations = function(k, n) {
    return Math.pow(k, n)
}

/**
* 右边界二分搜索,在 递增序列 nums 中寻找 <= target 的最后一个元素位置
* @param nums
* @param target
* @return
*/
const binarySearch = function(nums, target) {
    let left = 0, right = nums.length
    while (left < right) {
        const mid = left + ((right - left) >> 1)
        if (nums[mid] <= target) {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return left - 1
}

结果:

  • 执行结果: 通过
  • 执行用时:60 ms, 在所有 JavaScript 提交中击败了75.00%的用户
  • 内存消耗:41.5 MB, 在所有 JavaScript 提交中击败了62.50%的用户
  • 通过测试用例:84 / 84

END