持续创作,加速成长!这是我参与「掘金日新计划 · 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 <= 9digits[i].length == 1digits[i]是从'1'到'9'的数digits中的所有值都 不同digits按 非递减顺序 排列1 <= n <=
思路分析
- 如果组成数字的位数小于 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大,因此不能选择。
- 若 第i位 为
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