I need to find the number of heavy integers between two integers A
and B
, where A <= B
at all times.
An integer is considered heavy whenever the average of it's digit is larger than
7
.For example:
9878
is considered heavy, because(9 + 8 + 7 + 8)/4 = 8
, while1111
is not, since(1 + 1 + 1 + 1)/4 = 1
.
I have the solution below, but it's absolutely terrible and it times out when run with large inputs. What can I do to make it more efficient?
int countHeavy(int A, int B) {
int countHeavy = 0;
while(A <= B){
if(averageOfDigits(A) > 7){
countHeavy++;
}
A++;
}
return countHeavy;
}
float averageOfDigits(int a) {
float result = 0;
int count = 0;
while (a > 0) {
result += (a % 10);
count++;
a = a / 10;
}
return result / count;
}
答案 0 :(得分:6)
使用查找表计算数字
您可以生成一个表,用于存储 d 位数的整数,其数字之和大于数字 x 。然后,您可以快速查找10,100,1000 ...整数范围内有多少重数。这些表只保存9×d值,因此它们占用的空间非常小,可以快速生成。
然后,要检查B具有 d 位的范围AB,您将表构建为1到 d -1位数,然后将范围AB拆分为10,100,1000的块...并查找表中的值,例如对于范围A = 782,B = 4321:
RANGE DIGITS TARGET LOOKUP VALUE
782 - 789 78x > 6 table[1][ 6] 3 <- incomplete range: 2-9
790 - 799 79x > 5 table[1][ 5] 4
800 - 899 8xx >13 table[2][13] 15
900 - 999 9xx >12 table[2][12] 21
1000 - 1999 1xxx >27 table[3][27] 0
2000 - 2999 2xxx >26 table[3][26] 1
3000 - 3999 3xxx >25 table[3][25] 4
4000 - 4099 40xx >24 impossible 0
4100 - 4199 41xx >23 impossible 0
4200 - 4299 42xx >22 impossible 0
4300 - 4309 430x >21 impossible 0
4310 - 4319 431x >20 impossible 0
4320 - 4321 432x >19 impossible 0 <- incomplete range: 0-1
--
48
如果第一个和最后一个范围不完整(不是* 0 - * 9),请检查目标的起始值或结束值。 (在示例中,2不大于6,因此所有3个重数都包含在范围内。)
生成查找表
对于1位十进制整数,大于 x 的整数 n 是:
x: 0 1 2 3 4 5 6 7 8 9
n: 9 8 7 6 5 4 3 2 1 0
如您所见,这很容易通过取n = 9- x 来计算。
对于2位十进制整数,其位数之和大于值 x 的整数 n 为:
x: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
n: 99 97 94 90 85 79 72 64 55 45 36 28 21 15 10 6 3 1 0
对于3位十进制整数,其位数之和大于值 x 的整数 n 为:
x: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
n: 999 996 990 980 965 944 916 880 835 780 717 648 575 500 425 352 283 220 165 120 84 56 35 20 10 4 1 0
这些序列中的每一个都可以从前一个序列生成:以值10 d 开始,然后从该值中减去前一个序列(跳过第一个零)。例如。从2个数字的序列生成3位数的序列,从10 3 = 1000开始,然后:
0. 1000 - 1 = 999
1. 999 - 3 = 996
2. 996 - 6 = 990
3. 990 - 10 = 980
4. 980 - 15 = 965
5. 965 - 21 = 944
6. 944 - 28 = 916
7. 916 - 36 = 880
8. 880 - 45 = 835
9. 835 - 55 = 780
10. 780 - 64 + 1 = 717 <- after 10 steps, start adding the previous sequence again
11. 717 - 72 + 3 = 648
12. 648 - 79 + 6 = 575
13. 575 - 85 + 10 = 500
14. 500 - 90 + 15 = 425
15. 425 - 94 + 21 = 352
16. 352 - 97 + 28 = 283
17. 283 - 99 + 36 = 220
18. 220 - 100 + 45 = 165 <- at the end of the sequence, keep subtracting 10^(d-1)
19. 165 - 100 + 55 = 120
20. 120 - 100 + 64 = 84
21. 84 - 100 + 72 = 56
22. 56 - 100 + 79 = 35
23. 35 - 100 + 85 = 20
24. 20 - 100 + 90 = 10
25. 10 - 100 + 94 = 4
26. 4 - 100 + 97 = 1
27. 1 - 100 + 99 = 0
顺便说一句,如果使用7以外的值定义“重”数字,则可以使用相同的表格。
代码示例
以下是演示该方法的Javascript代码段(我不会说Java)。它是非常不被优化的,但它在不到0.07ms的时间内完成了0→100,000,000的示例。它也适用于7以外的权重。转换为Java,它应该轻松击败任何实际贯穿数字并检查其权重的算法。
function countHeavy(A, B, weight) {
var a = decimalDigits(A), b = decimalDigits(B); // create arrays
while (a.length < b.length) a.push(0); // add leading zeros
var digits = b.length, table = weightTable(); // create table
var count = 0, diff = B - A + 1, d = 0; // calculate range
for (var i = digits - 1; i >= 0; i--) if (a[i]) d = i; // lowest non-0 digit
while (diff) { // increment a until a=b
while (a[d] == 10) { // move to higher digit
a[d++] = 0;
++a[d]; // carry 1
}
var step = Math.pow(10, d); // value of digit d
if (step <= diff) {
diff -= step;
count += increment(d); // increment digit d
}
else --d; // move to lower digit
}
return count;
function weightTable() { // see above for details
var t = [[],[9,8,7,6,5,4,3,2,1,0]];
for (var i = 2; i < digits; i++) {
var total = Math.pow(10, i), final = total / 10;
t[i] = [];
for (var j = 9 * i; total > 0; --j) {
if (j > 9) total -= t[i - 1][j - 10]; else total -= final;
if (j < 9 * (i - 1)) total += t[i - 1][j];
t[i].push(total);
}
}
return t;
}
function increment(d) {
var sum = 0, size = digits;
for (var i = digits - 1; i >= d; i--) {
if (a[i] == 0 && i == size - 1) size = i; // count used digits
sum += a[i]; // sum of digits
}
++a[d];
var target = weight * size - sum;
if (d == 0) return (target < 0) ? 1 : 0; // if d is lowest digit
if (target < 0) return table[d][0] + 1; // whole range is heavy
return (target > 9 * d) ? 0 : table[d][target]; // use look-up table
}
function decimalDigits(n) {
var array = [];
do {array.push(n % 10);
n = Math.floor(n / 10);
} while (n);
return array;
}
}
document.write("0 → 100,000,000 = " + countHeavy(0, 100000000, 7) + "<br>");
document.write("782 → 4321 = " + countHeavy(782, 4321, 7) + "<br>");
document.write("782 → 4321 = " + countHeavy(782, 4321, 5) + " (weight: 5)");
答案 1 :(得分:2)
我真的很喜欢@ m69的帖子,所以我写的是受它启发的实现。表创建不是那么优雅,但有效。对于n + 1位长整数I求和(最多)来自n位长整数的10个值,每个数字0-9对应一个值。
我使用这种简化来避免任意范围计算:
countHeavy(A,B)= countHeavy(0,B) - countHeavy(0,A-1)
结果以两个循环计算。一个用于短于给定数字的数字,另一个用于其余数字。我无法轻易合并它们。 getResult
只是通过范围检查查找table
,其余代码应该非常明显。
public class HeavyNumbers {
private static int maxDigits = String.valueOf(Long.MAX_VALUE).length();
private int[][] table = null;
public HeavyNumbers(){
table = new int[maxDigits + 1][];
table[0] = new int[]{1};
for (int s = 1; s < maxDigits + 1; ++s) {
table[s] = new int[s * 9 + 1];
for (int k = 0; k < table[s].length; ++k) {
for (int d = 0; d < 10; ++d) {
if (table[s - 1].length > k - d) {
table[s][k] += table[s - 1][Math.max(0, k - d)];
}
}
}
}
}
private int[] getNumberAsArray(long number) {
int[] tmp = new int[maxDigits];
int cnt = 0;
while (number != 0) {
int remainder = (int) (number % 10);
tmp[cnt++] = remainder;
number = number / 10;
}
int[] ret = new int[cnt];
for (int i = 0; i < cnt; ++i) {
ret[i] = tmp[i];
}
return ret;
}
private int getResult(int[] sum, int digits, int fixDigitSum, int heavyThreshold) {
int target = heavyThreshold * digits - fixDigitSum + 1;
if (target < sum.length) {
return sum[Math.max(0, target)];
}
return 0;
}
public int getHeavyNumbersCount(long toNumberIncl, int heavyThreshold) {
if (toNumberIncl <= 0) return 0;
int[] numberAsArray = getNumberAsArray(toNumberIncl);
int res = 0;
for (int i = 0; i < numberAsArray.length - 1; ++i) {
for (int d = 1; d < 10; ++d) {
res += getResult(table[i], i + 1, d, heavyThreshold);
}
}
int fixDigitSum = 0;
int fromDigit = 1;
for (int i = numberAsArray.length - 1; i >= 0; --i) {
int toDigit = numberAsArray[i];
if (i == 0) {
toDigit++;
}
for (int d = fromDigit; d < toDigit; ++d) {
res += getResult(table[i], numberAsArray.length, fixDigitSum + d, heavyThreshold);
}
fixDigitSum += numberAsArray[i];
fromDigit = 0;
}
return res;
}
public int getHeavyNumbersCount(long fromIncl, long toIncl, int heavyThreshold) {
return getHeavyNumbersCount(toIncl, heavyThreshold) -
getHeavyNumbersCount(fromIncl - 1, heavyThreshold);
}
}
它的用法如下:
HeavyNumbers h = new HeavyNumbers();
System.out.println( h.getHeavyNumbersCount(100000000,7));
打印出569484,没有初始化表的重复计算时间低于1us
答案 2 :(得分:1)
我以不同的方式看待问题。我的看法是问题是基于数字的基数10表示,所以你应该做的第一件事就是将数字放入基数为10的表示中。可能有一种更好的方法,但Java Strings代表基数为10的整数,所以我使用了它们。将单个字符转换为整数实际上非常快,因此这不会花费太多时间。
最重要的是,你在这个问题上的计算永远不需要使用除法或浮点数。问题在于核心只是整数。数字(整数)中的所有数字(整数)是否加起来大于或等于数字(整数)的七(整数)倍?
警告 - 我并不是说这是最快的方法,但这可能比你原来的方法更快。
这是我的代码:
package heavyNum;
public class HeavyNum
{
public static void main(String[] args)
{
HeavyNum hn = new HeavyNum();
long startTime = System.currentTimeMillis();
hn.countHeavy(100000000, 1);
long endTime = System.currentTimeMillis();
System.out.println("Time elapsed: "+(endTime- startTime));
}
private void countHeavy(int A, int B)
{
int heavyFound = 0;
for(int i = B+1; i < A; i++)
{
if(isHeavy(i))
heavyFound++;
}
System.out.println("Found "+heavyFound+" heavy numbers");
}
private boolean isHeavy(int i)
{
String asString = Integer.valueOf(i).toString();
int length = asString.length();
int dividingLine = length * 7, currTotal = 0, counter = 0;
while(counter < length)
{
currTotal += Character.getNumericValue(asString.charAt(counter++));
}
return currTotal > dividingLine;
}
}
如果我得到一个整数中的位数,则为this SO Question,而this SO Question表示如何在java中快速将字符转换为整数
在强大的计算机上运行,没有调试器,数字介于1到100,000,000之间,导致此输出:
发现569484重数
已过去的时间:6985
编辑:我最初在寻找数字大于或等于7位数的数字。我以前在7025毫秒内有843,453个数字的结果。
答案 3 :(得分:1)
这是一个非常简单的带有memoization的递归,它为一个固定数字的数字逐个枚举数字的可能性。在计算相应的位数时,您可以通过控制A
的范围来设置B
和i
。
看起来非常快(参见20位数的结果)。
JavaScript代码:
var hash = {}
function f(k,soFar,count){
if (k == 0){
return 1;
}
var key = [k,soFar].join(",");
if (hash[key]){
return hash[key];
}
var res = 0;
for (var i=Math.max(count==0?1:0,7*(k+count)+1-soFar-9*(k-1)); i<=9; i++){
res += f(k-1,soFar+i,count+1);
}
return hash[key] = res;
}
// Output:
console.log(f(3,0,0)); // 56
hash = {};
console.log(f(6,0,0)); // 12313
hash = {};
console.log(f(20,0,0)); // 2224550892070475
答案 4 :(得分:0)
您确实可以使用字符串来获取数字位数,然后添加各个数字的值,以查看他们的sum > 7 * length
是否与Jeutnarg一样。我拿了他的代码并添加了我自己的简单isHeavyRV(int)
:
private boolean isHeavyRV(int i)
{
int sum = 0, count = 0;
while (i > 0)
{
sum += i % 10;
count++;
i = i / 10;
}
return sum >= count * 7;
}
现在,而不是
if(isHeavy(i))
我试过
if(isHeavyRV(i))
我实际上首先使用字符串测试了isHeavy()
的实现,并且在我的机器(旧的iMac)上运行 12388 毫秒,并且发现了843453个重数。
使用我的实现,我找到了完全相同数量的重数字,但时间仅为 5416 毫秒。
字符串可能很快,但它们无法击败一个简单的循环,基本上做Integer.toString(i, 10)
所做的事情,但没有字符串绕道。
答案 5 :(得分:0)
当您向数字添加1时,您将递增一位数字,并将所有较小的数字更改为零。如果将变量从重量增加到非重量数,则因为过多的低位数被归零。在这种情况下,很容易找到下一个重数而不检查其间的所有数字:
public class CountHeavy
{
public static void main(String[] args)
{
long startTime = System.currentTimeMillis();
int numHeavy = countHeavy(1, 100000000);
long endTime = System.currentTimeMillis();
System.out.printf("Found %d heavy numbers between 1 and 100000000\n", numHeavy);
System.out.println("Time elapsed: "+(endTime- startTime)+" ms");
}
static int countHeavy(int from, int to)
{
int numdigits=1;
int maxatdigits=9;
int numFound = 0;
if (from<1)
{
from=1;
}
for(int i = from; i < to;)
{
//keep track of number of digits in i
while (i > maxatdigits)
{
long newmax = 10L*maxatdigits+9;
maxatdigits = (int)Math.min(Integer.MAX_VALUE, newmax);
++numdigits;
}
//get sum of digits
int digitsum=0;
for(int digits=i;digits>0;digits/=10)
{
digitsum+=(digits%10);
}
//calculate a step size that increments the first non-zero digit
int step=1;
int stepzeros=0;
while(step <= (Integer.MAX_VALUE/10) && to-i >= step*10 && i%(step*10) == 0)
{
step*=10;
stepzeros+=1;
}
//step is a 1 followed stepzeros zeros
//how much is our sum too small by?
int need = numdigits*7+1 - digitsum;
if (need <= 0)
{
//already have enough. All the numbers between i and i+step are heavy
numFound+=step;
}
else if (need <= stepzeros*9)
{
//increment to the smallest possible heavy number. This puts all the
//needed sum in the lowest-order digits
step = need%9;
for(;need >= 9;need-=9)
{
step = step*10+9;
}
}
//else there are no heavy numbers between i and i+step
i+=step;
}
return numFound;
}
}
在1到100000000之间找到了569484个重数
已过去的时间:31毫秒
请注意,答案与@ JeutNarg不同,因为您要求平均值&gt; 7,不是平均值> = 7。