每日一题做题记录,参考官方和三叶的题解 |
题目要求
思路一:模拟
简单题亘古不变的模拟。
Java
class Solution {
public List<Integer> selfDividingNumbers(int left, int right) {
List<Integer> res = new ArrayList<>();
out:for(int i = left; i <= right; i++) {
int cur = i;
while(cur != 0) {
int t = cur % 10;
if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
continue out;
cur /= 10;
}
res.add(i);
}
return res;
}
}
- 时间复杂度: O ( n log r i g h t ) O(n\log right) O(nlogright),其中 n n n为区间大小,即 n = r i g h t − l e f t + 1 n = right - left + 1 n=right−left+1
- 空间复杂度: O ( 1 ) O(1) O(1)
out
- 这个
out
没见过,搜了一下发现是个标签,用来跳出循环,通常break
、continue
啥的都只能跳出当前循环,那加个out
就可以跳出被其标记的那个循环; - 所以他只是个名字,叫别的也行。
C++
C++里面是没有java那个标签功能的,那想到三个替代思路:
goto
跳到指定位置(直接放弃、问就是因为爱情);- 设置一个
f
l
a
g
flag
flag判断(类似思路三里的
ok
); - 写成一个单独函数
return
出去。【用这个写一下】
class Solution {
public:
vector<int> selfDividingNumbers(int left, int right) {
vector<int> res;
for(int i = left; i <= right; i++)
if(isDN(i))
res.emplace_back(i);
return res;
}
//for循环写成函数
bool isDN(int i) {
int cur = i;
while(cur != 0) {
int t = cur % 10;
if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
return false;
cur /= 10;
}
return true;
}
};
- 时间复杂度: O ( n log r i g h t ) O(n\log right) O(nlogright)
- 空间复杂度: O ( 1 ) O(1) O(1)
Python3
可以一行解决,所以写一下,时空复杂度应该是同上了……吧
class Solution:
def selfDividingNumbers(self, left: int, right: int) -> List[int]:
return [num for num in range(left, right + 1)
if all(i > 0 and num % i == 0
for i in map(int, str(num)))]
- 时间复杂度: O ( n log r i g h t ) O(n\log right) O(nlogright)
- 空间复杂度: O ( 1 ) O(1) O(1)
思路二:二分
数据范围不大(
[
1
,
1
0
4
]
[1,10^4]
[1,104]),所以可以先把所有自除数都找出来,然后再根据题目要求圈定范围。
采用二分找到范围内最小的自除数,然后依次输出就可了。
Java
【前面搞static是因为leetcode的判定机制,其实都写函数里面就ok,这样提交的时候就不会每个example都算一遍,提升判定效率,三叶姐的static大法。】
class Solution {
//提前找到数据范围内所有的自除数
static List<Integer> list = new ArrayList<>();
static {
out:for(int i = 1; i < 10000; i++) {
int cur = i;
while(cur != 0) {
int t = cur % 10;
if(t == 0 || i % t != 0)
continue out;
cur /= 10;
}
list.add(i);
}
}
//二分找在题目范围内最小的
public List<Integer> selfDividingNumbers(int left, int right) {
List<Integer> res = new ArrayList<>();
int l = 0, r = list.size() - 1;
while (l < r) {
int m = l + r >> 1;
if(list.get(m) >= left)
r = m;
else
l = m + 1;
}
while(list.get(r) <= right)
res.add(list.get(r++));
return res;
}
}
- 时间复杂度: O ( n + log C ) O(n+\log C) O(n+logC), n n n为区间大小, C C C为数据范围,本题中 C = 1 0 4 C=10^4 C=104
- 空间复杂度: O ( C ) O(C) O(C)
C++
用刚才的2.写,设置一个stop
的flag。
然后,没想到C++怎么搞类似上面那种自动执行的static函数,就直接写里面了。
class Solution {
public:
vector<int> selfDividingNumbers(int left, int right) {
//提前找到数据范围内所有的自除数
vector<int> list;
for(int i = 1; i < 10000; i++) {
int cur = i;
bool stop = false;
while(cur != 0 && !stop) {
int t = cur % 10;
if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
stop = true;
cur /= 10;
}
if(!stop)
list.emplace_back(i);
}
//二分找在题目范围内最小的
vector<int> res;
int l = 0, r = list.size() - 1;
while(l < r) {
int m = l + r >> 1;
if(list.at(m) >= left)
r = m;
else
l = m + 1;
}
while(list.at(r) <= right)
res.emplace_back(list.at(r++));
return res;
}
};
- 时间复杂度: O ( n + log C ) O(n+\log C) O(n+logC)
- 空间复杂度: O ( C ) O(C) O(C)
vector
- 学习参考链接
- 就只是提一下直接访问元素
list[i]
和用list.at(i)
的区别,简单讲后者更安全,它会自动检查 i i i是否在合法范围内,不会产生越界等问题。
思路三:哈希表
避免二分,把索引也预处理出来(不会改变空间复杂度)。
i
d
x
[
i
]
idx[i]
idx[i]:表示不超过
i
i
i的最大自除数在
l
i
s
t
list
list中的编号。
【判定不超过
l
e
f
t
left
left的最大自除数是否等于自己,等就恰好从此开始,不等就跳到下一个开始】
Java
class Solution {
//提前找到数据范围内所有的自除数
static List<Integer> list = new ArrayList<>();
static int[] idx = new int[100001]; //list编号
static {
for(int i = 1; i < 10000; i++) {
int cur = i;
boolean ok = true;
while(cur != 0 && ok) {
int t = cur % 10;
if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
ok = false;
cur /= 10;
}
if(ok)
list.add(i);
idx[i] = list.size() - 1; //当前情况下最大的自除数编号
}
}
//找在题目范围内最小的
public List<Integer> selfDividingNumbers(int left, int right) {
List<Integer> res = new ArrayList<>();
int i = list.get(idx[left]) == left ? idx[left] : idx[left] + 1; //判断从left还是下一个开始
while(list.get(i) <= right)
res.add(list.get(i++));
return res;
}
}
- 时间复杂度: O ( n ) O(n) O(n), n n n为区间大小
- 空间复杂度: O ( C ) O(C) O(C)
C++
class Solution {
public:
vector<int> selfDividingNumbers(int left, int right) {
//提前找到数据范围内所有的自除数
vector<int> list;
int idx[100001]; //list编号
for(int i = 1; i < 10000; i++) {
int cur = i;
bool stop = false;
while(cur != 0 && !stop) {
int t = cur % 10;
if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
stop = true;
cur /= 10;
}
if(!stop)
list.emplace_back(i);
idx[i] = list.size() - 1; //当前情况下最大的自除数编号
}
//找在题目范围内最小的
vector<int> res;
int i = list.at(idx[left]) == left ? idx[left] : idx[left] + 1; //判断从left还是下一个开始
while(list.at(i) <= right)
res.emplace_back(list.at(i++));
return res;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( C ) O(C) O(C)
总结
简单题本来模拟就结了,基于数据范围和判定模式,还引申出来一种二分和哈希表避免二分的方法,挺有意思的。
【三月徽章get√,四月加油】
欢迎指正与讨论! |