Digit Divide Numbers
这是一道笔者纠结了很久的题,Lintcode编号742,经过一系列处理终于通过了测试,也算完成了一项小小的心愿,在这里就分享下解题的过程和思路。
已知:
Digit Divide Numbers 是这样一类数,以128举例,128能整除1,2,8,所以这个数符合要求,也就是说一个数能被自身所有整数整除即可。
限定:0<=L<=R<=2^31-1,R-L<=10^6
第一回合:这题乍一看真心简单,写一个判定函数,然后从lower开始一直遍历到upper就好了,这部分代码着实易写:
public static List<Integer> digitDivideNums(int lower, int upper) {
List<Integer> ret = new ArrayList<Integer>();
for (int i = lower; i <= upper; i++) {
if(isDigitDivideNum(i)){
ret.add(i);
}
}
return ret;
}
private static boolean isDigitDivideNum(int num) {
if (num < 10) {
return true;
}
String s = String.valueOf(num);
if (s.contains("0")) {
return false;
}
int temp = num;
while (temp > 0) {
if (num % (temp % 10) != 0) {
return false;
}
temp /= 10;
}
return true;
}
然后Eclipse运行的时候,即使是最高的10^6级别的数据,时间也很快完成了,因此满心欢喜贴上代码,还在纳闷怎么这么少人答出,结果…
(您好,1-1000000,您的空间复杂度太高,可能的原因是创造了一个不需要的二维数组)
ミ゚Д゚彡
第二回合:空间复杂度太高?什么意思?没用什么额外空间啊??难道是动态规划,可以由前一个数递推下一个数?不可能啊,没理由递推啊。而且时间复杂度肯定是很低的,1-1000000所花时间也极少,空间复杂度怎么降低啊?
以上是我的OS,然后连续几天都纠结于,比如说数字含有7,应该怎么处理,含有5,怎么处理,含有3,6,9等等等等,然而并不能通过,空间复杂度始终没有长进
第三回合:1-1000000以内的数字中,其实符合条件的只有几千个,我将这些数都输出了,然后发现了一些特征:
相邻两个数自己的差值,不会超过两个数的最大位的数
也就是说两个数是9XXXXX和9YYYYY,这两个数都符合条件,那么这两个数肯定差值在9以上,反过来想,9XXXXX可以被9整除,那么下一个可能满足条件的就是这个数+9,且当数不超过1000000时,都可以以9为跨度,而不是以之前的1为跨度!
也就是说:以X为开头的数从第一个符合的数+X,会比原来少验很多肯定不符合要求的数字,如果以平均值5计算,100W以内数字可能只会验证大约1/5的数字,也就是20W左右,空间复杂度应该就够了
还有一点小优化:当当前数含有0的时候,比如说XX0XXX,那么下一个可能满足条件的数只能是XX1111
综上: 代码如下:
import java.util.ArrayList;
import java.util.List;
public class Solution {
static String OneS = "1111111111111111";
static String ZeroS = "0000000000000000";
public static List<Integer> digitDivideNums(int lower, int upper) {
List<Integer> ret = new ArrayList<Integer>();
for (int i = lower; i <= upper;) {
String s = String.valueOf(i);
if (s.indexOf("0") == -1) {
if (isDigitDivideNum(i)) {
ret.add(i);
if (i < 10 || Integer.valueOf(s.substring(0, 1)) == 1) {
i++;
} else {
int minUpper = calculate(s, upper);
//x是每次的跨度,minUpper是当前上限
int x = Integer.valueOf(s.substring(0, 1));
for (int j = i + x; j <= minUpper && j > 0; j += x) {
if (isDigitDivideNum(j)) {
ret.add(j);
}
}
i = minUpper;
if (minUpper == upper) {
break;
}
}
} else {
i++;
if (i < 0) {
break;
}
}
} else {
int index = s.indexOf("0");
s = s.substring(0, index)
+ OneS.substring(0, s.length() - index);
i = Integer.valueOf(s);
}
}
return ret;
}
//举个栗子,当前数为21112,则到30000为止为上限
//部分代码奇怪是因为数字本身可能超过int的上限
private static int calculate(String s, int upper) {
int x = Integer.valueOf(s.substring(0, 1));
long longTempUpper = Long.valueOf(x + 1
+ ZeroS.substring(0, s.length() - 1));
//我们假定的上限超过了int上限,则上限改为int上限
int tempUpper = longTempUpper > Integer.MAX_VALUE ? Integer.MAX_VALUE
: (int) longTempUpper;
return upper > tempUpper ? tempUpper : upper;
}
private static boolean isDigitDivideNum(int num) {
if (num < 10) {
return true;
}
String s = String.valueOf(num);
if (s.contains("0")) {
return false;
}
int temp = num;
while (temp > 0) {
if (num % (temp % 10) != 0) {
return false;
}
temp /= 10;
}
return true;
}
}
上述代码中有一些比较奇怪的代码片段,主要是因为测试用例里面到了int的最大值,也就是2^32 - 1, 超过边界直接变负数,负数还是符合我刚开始的限定条件的,因此后来我又加上了必须大于0的约束,long那部分是因为int最大值是2XXXXXX的形式,但是3000000000超过了int的上界,因此这部分需要把上界改为Integer.MaX_VALUE,且为了不影响其他代码,这里先用long将300000000存下来.
然后终于通过了,空间复杂度没有超过,喜极而泣٩(๑❛ᴗ❛๑)۶
谢谢您的阅读,希望对您有所帮助