第 121 题:统计 1 ~ n 整数中出现 1 的次数
统计 1 ~ n 整数中出现 1 的次数,例如统计 1 ~ 400W 出现 1 的次数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
统计 1 ~ n 整数中出现 1 的次数,例如统计 1 ~ 400W 出现 1 的次数。
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(45)
function counts(num) {
let count = 0;
for (let i = 0; i<=4000000; i++) { //遍历1到 4000000的所有整数
let str = i + ""; //数字转成字符串
while(str.indexOf(num) != -1) {
count++;
str = str.substring(str.indexOf(num) + 1);
}
}
return count;
}
console.log(counts(1))
function countNum(n, count = 0) {
for (let i = 0; i <= n; i++) {
if ((i + '').indexOf('1') > -1) {
count++;
continue;
}
}
return count;
}
直接输出吧 鄙人才疏学浅 望各位指点
/**
*/
var countDigitOne = function(n) {
if(n <= 0)
return 0;
if(n < 10)
return 1;
let current = n, i = 1, total = 0;
while(current){
if(current%10 === 0)
total += Math.floor(current/10)*i;
if(current%10 === 1)
total += Math.floor(current/10)*i+(n%i)+1;
//在[1-10],统计为2
if(current%10>1)
total += Math.ceil(current/10.0)i;
//除10,移动到下一个高位的数字
current=Math.floor(current/10);
//下一个要查看的数字的位数
i=i10;
}
return total;
};
参考leetcode上面写的
先实现:
楼上大佬的算法,性能优化了好多
先找规律
按递归的思路思考一个整数,比如n = 388,
我们可以把0-388划成四部分:
对于前三个部分,如果我们省略最高位的话,那它们就是相同问题,可以归结为 howManyOnes(99) * 3;
对于最后一个部分,就是 howManyOnes(88);
最后再考虑 100 - 199 这部分,因为388大于100,因此包含了所有最高位是1的100个整数;
因此,1的总数 = howManyOnes(99) * 3 + howManyOnes(88) + 100 = 179。
那么对于最高位不大于1的整数呢? 也很容易得到规律,比如n = 166:
我们可以把0-166划成两部分:
同样的,我们先考虑第一部分,即howManyOnes(99);
然后再看第二部分,先拿到howManyOnes(66),然后再统计100 - 166之间最高位是1的整数,是 66 + 1 个;
因此,1的总数 = howManyOnes(99) + howManyOnes(66) + 66 + 1 = 104。
代码实现
找到方法后代码实现就不难了:
这个跟之前自己写过的一道统计0的次数是一样的。
你应该没有测试过的吧
先记录下自己的想法,再看大佬们的优化方法
才疏学浅,只能暴力了,哈哈
function add(n){
var sum = 0;
for(var i = 0; i <= n; i++){
var i = "" + i;
sum += i.split("1").length - 1;
}
return sum;
}
console.log(add(11));//4
console.log(add(40000));//26000
9=>1,99=>20,999=>300,9999=>4000,99999=>50000
根据这个规律写出来的代码:
function orgnum(num){
var str = num.toString();
var arr = str.split("");
var arrlen = arr.length;
var totle=0;
for(var i=0;i<arrlen;i++){
if(arr[i]==0){continue}
var x = arr[i]* Math.pow(10,arrlen-i-1);
prev(i);
totle+=maxnum(x);
}
console.log(totle);
function prev(n){
if(n==0){
return;
}
var shu=0;
for(var j=n;j>0;j--){
arr[j-1]==1&&shu++;
}
totle+=shux;
}
function maxnum(n){
var str=n.toString();
var len = str.length;
function num(leng){
//console.log(leng * Math.pow(10, leng-1));
return lengMath.pow(10,leng-1)
}
if(str[0]==1){
return num(len-1)+1;
}
return num(len)-num(len-1)*(10-str[0]) ;
}
}
把数字转成字符串,利用正则进行字符串匹配
传入 n,输出 1-n 之间出现 1 的次数
function findOne(arr = Array.from({ length: 4000000 }, (v, i) => i + '')) { let num = 0; arr.forEach(element => { element.split('').forEach(item => { if (item === '1') { num += 1 } }) }); return num }
给大佬献出膝盖
发现这个逻辑可以用于计算1-9出现次数
var num = [];
for (var i = 1; i<=20;i++) {
num.push(i);
}
var str = num.join('');
var num1 = str.match(/1/g);
console.log('num1 :', num1.length);
function sum (res) {
let ac = ''
for (let ab = 1; ab <= res; ab ++) {
ac+= ab;
}
console.log (ac.split('1').length - 1)
}
sum(400000)
剑指 Offer 的版本
以
21345
例,为了方便递归求解,将其分为两段约定如下变量代表其中一些值:
1346 ~ 21345 段计数过程
1. 先统计最高位出现 1 的次数
first > 1
以本例来说,将存在区间 [10000, 19999] 共计 10000 个数字,即最高位出现 10000 个 1
即
10 ^ (length - 1)
first = 1
假设该数为 11345
则最高位出现 1 的区间范围是 [10000, 11345],共计 1345 + 1 个数
即
remain + 1
2. 再统计 1346 ~ 21345 段中非最高位出现 1 的次数
由于 [1346, 21345] 段是完整连续的 20000 个数,因此除最高位外,剩下的4个位置,出现 0 ~ 9 的机会是均等的
因此,根据排列组合,我们可以得出如下计算方式
first * (remain.length) * 10 ^ (remain.length - 2)
其含义是,剩下的4个位置,选择其中一个放 1 ,其他位置可放置 0 ~ 9 中任意一个数字
最高位决定这样的排列组合重复几遍
以上,即为 1346 ~ 21345 段计数过程,再加上递归求解的剩余子串出现次数,即为总出现次数
如下为一个实现示例:
function init(mathNum){
var num = 0;
var str = "1";
var len = (mathNum+"").length-1;
for(var i=0;i<len;i++){
num += mathNum/10;
str += "0";
}
if(mathNum+"0".substr(0,1)=="1"){
num+=1;
}else{
num += Number(str)
}
}
console.log(init(2000));
一位数: 1
二位数: [10-99): 19, 1+19 = 20
三位数: [100-200): 20+100, [200-1000): 8*(1+19) [0-1000): 160+140
四位数: [1000-2000): 1000+300 [0-2000): 1000+300+300
可以这样搞得, 也可以采用了 @wilesen 这种
我也是使用数学的思路写的
Array.from({length:4000000},(_,i)=>i+1).join('').split('').filter(val=>val==='1').length
排列组合,问高中生
// 思路:for循环,对每一个数进行出现1的统计
// 最后累加即可
function calcOnetimes(num, sum = 0) {
if (num) {
if (num % 10 === 1) {
sum++
}
sum = calcOnetimes(parseInt(num / 10), sum)
}
return sum
}
const arr = []
for (let i = 1; i <= 2333; i++) {
arr.push(i)
}
const result = arr.reduce((sum, num) => {
return calcOnetimes(num, sum) //是把return的结果累加到sum身上
}, 0)
console.log(result)//1774
这差距太明显了,用规律和暴力的时间差距太大,一个用了26870ms, 一个小于1ms.
这不剑指原题么,有一个公式可以直接推出来
原文
function test(n){
let count = 0;
while(n>0){
if(n.toString().indexOf('1')>-1){ count ++;}
n--;
}
return count;
}
主要就是考虑当前位数上的数字大小
为啥都是暴力循环,这不都是算法题吗?
分析归纳一下,按照每一位上的数字来分析
比如55, 个位可能产生的 1 是 6个(1, 11, 21, 31, 41, 51,
注意这里11只计算的个位的1
), 十位5 可能产生的 1是 10个,(10 - 19,这里的11只计算的十位的1
);比如222, 个位 可能产生的 1 是 23个(1, 11, 21, ... 221, 只关注个位), 十位2 可能产生的 1是 30个(10-19, 110-119, 210-219, 只关注十位), 百位2 产生的 1是100个(100 - 199, 只关注百位).
以此类推, 每一位数字可能产生的1的个数跟他的高位部分和低位部分相关:
其中0和1需要特殊处理,代码如下
动不动400W,这怎么跑起来
let findOne = (len)=> { let count = ''; for (let i = 1; i <= len; i++) { count += i.toString().replace(/[^1]/ig, ''); } return count.length; }
先解决有没有,后续再解决好不好。
var count = 0;
var n = 4000000;
for(var i = 1; i <= n;i++){
if(i.toString().match(/[1]/g)){
count++;
}
}
console.log(count);
比如说1-11中1,10,11算4次,即11算2次
看看数学规律 看每位出现1的次数
n为个位,f(n) = 个位
n为十位,f(n) = 十位+个位
n为百位,f(n) = 十位+个位+百位
以此类推
这样子是以十进制去循环的,效率大概在log(10)400000 = log(10^5 *4)
log(log10^6) > 结果> log(log10^5) =》 6~5之间
应该是说1-n的时候中间出现1的次数,比如1到11的时候 1、10、11这种 其实就算4次了,因为11中有两个1
感觉不太清晰,像11这个数字,算是出现1次还算是出现2次呢?像130这种带1的是不是也算呢?感觉详细描述下,或者举几个例子这样会更好理解一些 @yygmind
数字是有序还是无序的呢