方法一:暴力枚举
枚举 $a=1,2,3,\dots,n-1$,如果 $a$ 和 $n-a$ 都不包含 $0$,那么返回 $[a,n-a]$。
由于方法三给出了具体的构造方法,所以答案是一定存在的。注意题目保证 $n\ge 2$。
###py
class Solution:
def getNoZeroIntegers(self, n: int) -> List[int]:
for a in count(1): # 枚举 a=1,2,3,...
if '0' not in str(a) and '0' not in str(n - a):
return [a, n - a]
###java
class Solution {
public int[] getNoZeroIntegers(int n) {
for (int a = 1; ; a++) {
if (!Integer.toString(a).contains("0") &&
!Integer.toString(n - a).contains("0")) {
return new int[]{a, n - a};
}
}
}
}
###cpp
class Solution {
public:
vector<int> getNoZeroIntegers(int n) {
for (int a = 1; ; a++) {
if (to_string(a).find('0') == string::npos &&
to_string(n - a).find('0') == string::npos) {
return {a, n - a};
}
}
}
};
###c
bool has_zero(int x) {
while (x) {
if (x % 10 == 0) {
return true;
}
x /= 10;
}
return false;
}
int* getNoZeroIntegers(int n, int* returnSize) {
for (int a = 1; ; a++) {
if (!has_zero(a) && !has_zero(n - a)) {
*returnSize = 2;
int* ans = malloc(2 * sizeof(int));
ans[0] = a;
ans[1] = n - a;
return ans;
}
}
}
###go
func getNoZeroIntegers(n int) []int {
for a := 1; ; a++ {
if !strings.ContainsRune(strconv.Itoa(a), '0') &&
!strings.ContainsRune(strconv.Itoa(n-a), '0') {
return []int{a, n - a}
}
}
}
###js
var getNoZeroIntegers = function(n) {
for (let a = 1; ; a++) {
if (!a.toString().includes("0") && !(n - a).toString().includes("0")) {
return [a, n - a];
}
}
};
###rust
impl Solution {
pub fn get_no_zero_integers(n: i32) -> Vec<i32> {
for a in 1.. {
if !a.to_string().contains('0') && !(n - a).to_string().contains('0') {
return vec![a, n - a];
}
}
unreachable!()
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(n\log n)$。每个数字需要 $\mathcal{O}(\log n)$ 的时间判断是否包含 $0$。
- 空间复杂度:$\mathcal{O}(\log n)$ 或 $\mathcal{O}(1)$,取决于是否用到字符串。
方法二:随机
在 $[1,n-1]$ 中随机整数 $a$,如果 $a$ 和 $n-a$ 都不包含 $0$,那么返回 $[a,n-a]$。
###py
class Solution:
def getNoZeroIntegers(self, n: int) -> List[int]:
while True:
a = randint(1, n - 1)
if '0' not in str(a) and '0' not in str(n - a):
return [a, n - a]
###java
class Solution {
public int[] getNoZeroIntegers(int n) {
Random rand = new Random();
while (true) {
int a = rand.nextInt(n - 1) + 1;
if (!Integer.toString(a).contains("0") &&
!Integer.toString(n - a).contains("0")) {
return new int[]{a, n - a};
}
}
}
}
###cpp
class Solution {
public:
vector<int> getNoZeroIntegers(int n) {
// 可以先 srand 一下,但没必要写
while (true) {
int a = rand() % (n - 1) + 1;
if (to_string(a).find('0') == string::npos &&
to_string(n - a).find('0') == string::npos) {
return {a, n - a};
}
}
}
};
###c
bool has_zero(int x) {
while (x) {
if (x % 10 == 0) {
return true;
}
x /= 10;
}
return false;
}
int* getNoZeroIntegers(int n, int* returnSize) {
// 可以先 srand 一下,但没必要写
while (true) {
int a = rand() % (n - 1) + 1;
if (!has_zero(a) && !has_zero(n - a)) {
*returnSize = 2;
int* ans = malloc(2 * sizeof(int));
ans[0] = a;
ans[1] = n - a;
return ans;
}
}
}
###go
func getNoZeroIntegers(n int) []int {
for {
a := rand.Intn(n-1) + 1
if !strings.ContainsRune(strconv.Itoa(a), '0') &&
!strings.ContainsRune(strconv.Itoa(n-a), '0') {
return []int{a, n - a}
}
}
}
###js
var getNoZeroIntegers = function(n) {
while (true) {
const a = Math.floor(Math.random() * (n - 1)) + 1;
if (!a.toString().includes("0") && !(n - a).toString().includes("0")) {
return [a, n - a];
}
}
};
###rust
use rand::Rng;
impl Solution {
pub fn get_no_zero_integers(n: i32) -> Vec<i32> {
let mut rng = rand::thread_rng();
loop {
let a = rng.gen_range(1..n);
if !a.to_string().contains('0') && !(n - a).to_string().contains('0') {
return vec![a, n - a];
}
}
}
}
复杂度分析
- 时间复杂度:期望 $\mathcal{O}\left(\dfrac{\log n}{0.8^{\log_{10} n}}\right)$。近似估计:考虑 $n$ 的每一位,比如 $n$ 的某一位是 $5$,那么 $a$ 这一位有 $0$ 到 $9$ 共 $10$ 种可能,其中有 $2$ 种会让 $a$ 或者 $b$ 包含 $0$,即 $5=0+5=5+0$,其余 $8$ 种情况 $a$ 和 $b$ 的这一位都不包含 $0$(可以借位),概率为 $\dfrac{8}{10} = 0.8$。每一位都不包含 $0$ 的概率是 $P = 0.8^{\log_{10} n}$,期望循环 $\dfrac{1}{P}$ 次就能找到答案。在本题数据范围下,平均循环次数 $\dfrac{1}{P}<3$。
- 空间复杂度:$\mathcal{O}(\log n)$ 或 $\mathcal{O}(1)$,取决于是否用到字符串。
方法三:构造
比如 $n=666$,每一位分成两个数,比如 $6=3+3$,所以 $666=333+333$。又比如 $n=777$,由于 $7=3+4$,所以 $777=333+444$。
但是,$n=400$ 怎么分呢?如果分成 $400 = 200+200$,就不符合题目要求了。
我们可以把 $400$ 视作 $390 + 10$,也就是把 $400$ 的个位数视作 $10$,十位数视作 $9$,百位数视作 $3$。每一位再分成两个数,就可以得到 $400 = 145+255$ 了。
一般地:
- 从低到高遍历 $n$ 的每一位数字 $d$。
- 如果 $d\ge 2$,那么可以把 $d$ 分成 $\left\lfloor\dfrac{d}{2}\right\rfloor$ 和 $\left\lceil\dfrac{d}{2}\right\rceil$,这两个数都不是 $0$,分配给 $a$ 和 $b$。代码实现时,可以只考虑 $a$ 怎么构造,最后用 $n-a$ 得到 $b$。
- 如果 $d\le 1$,那么借位,把 $d$ 变成 $d+10$,这样就能和上面一样分成两个非零数字,分配给 $a$ 和 $b$。
- 如果遍历到最高位,且最高位是 $1$,那么就把 $1$ 分配给 $b$。$a$ 相当于分配到了 $0$,但这个 $0$ 其实是 $a$ 的前导零,并不算在 $a$ 中。
###py
class Solution:
def getNoZeroIntegers(self, n: int) -> List[int]:
a = 0
base = 1 # 10**k
x = n
while x > 1:
x, d = divmod(x, 10)
if d <= 1:
d += 10
x -= 1 # 借位
# a 这一位填 d//2,比如百位数就是 d//2 * 100
a += d // 2 * base
base *= 10
return [a, n - a]
###java
class Solution {
public int[] getNoZeroIntegers(int n) {
int a = 0;
int base = 1; // 10^k
for (int x = n; x > 1; x /= 10) {
int d = x % 10;
if (d <= 1) {
d += 10;
x -= 10; // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += d / 2 * base;
base *= 10;
}
return new int[]{a, n - a};
}
}
###cpp
class Solution {
public:
vector<int> getNoZeroIntegers(int n) {
int a = 0;
int base = 1; // 10^k
for (int x = n; x > 1; x /= 10) {
int d = x % 10;
if (d <= 1) {
d += 10;
x -= 10; // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += d / 2 * base;
base *= 10;
}
return {a, n - a};
}
};
###c
int* getNoZeroIntegers(int n, int* returnSize) {
int a = 0;
int base = 1; // 10^k
for (int x = n; x > 1; x /= 10) {
int d = x % 10;
if (d <= 1) {
d += 10;
x -= 10; // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += d / 2 * base;
base *= 10;
}
*returnSize = 2;
int* ans = malloc(2 * sizeof(int));
ans[0] = a;
ans[1] = n - a;
return ans;
}
###go
func getNoZeroIntegers(n int) []int {
a := 0
base := 1 // 10^k
for x := n; x > 1; x /= 10 {
d := x % 10
if d <= 1 {
d += 10
x -= 10 // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += d / 2 * base
base *= 10
}
return []int{a, n - a}
}
###js
var getNoZeroIntegers = function(n) {
let a = 0;
let base = 1; // 10^k
for (let x = n; x > 1; x = Math.floor(x / 10)) {
let d = x % 10;
if (d <= 1) {
d += 10;
x -= 10; // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += Math.floor(d / 2) * base;
base *= 10;
}
return [a, n - a];
};
###rust
impl Solution {
pub fn get_no_zero_integers(n: i32) -> Vec<i32> {
let mut a = 0;
let mut base = 1; // 10^k
let mut x = n;
while x > 1 {
let mut d = x % 10;
if d <= 1 {
d += 10;
x -= 10; // 借位
}
// a 这一位填 d/2,比如百位数就是 d/2 * 100
a += d / 2 * base;
base *= 10;
x /= 10;
}
vec![a, n - a]
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(\log n)$。
- 空间复杂度:$\mathcal{O}(1)$。
思考题
如果要求 $a$ 尽量小呢?你能想出一个 $\mathcal{O}(\log n)$ 的做法吗?
欢迎在评论区分享你的思路/代码。
专题训练
见下面贪心与思维题单的「六、构造题」。
分类题单
如何科学刷题?
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
我的题解精选(已分类)
欢迎关注 B站@灵茶山艾府