Skip to content

Commit 5ca9d1c

Browse files
committed
306
1 parent 9bf7ef2 commit 5ca9d1c

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,5 @@
250250
* [301. Remove Invalid Parentheses](leetcode-301-Remove-Invalid-Parentheses.md)
251251
* [303. Range Sum Query - Immutable](leetcode-303-Range-Sum-Query-Immutable.md)
252252
* [304. Range Sum Query 2D - Immutable](leetcode-304-Range-Sum-Query-2D-Immutable.md)
253+
* [306. Additive Number](leetcode-306-Additive-Number.md)
253254
* [更多](more.md)

leetcode-306-Additive-Number.md

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# 题目描述(中等难度)
2+
3+
306、Additive Number
4+
5+
Additive number is a string whose digits can form additive sequence.
6+
7+
A valid additive sequence should contain **at least** three numbers. Except for the first two numbers, each subsequent number in the sequence must be the sum of the preceding two.
8+
9+
Given a string containing only digits `'0'-'9'`, write a function to determine if it's an additive number.
10+
11+
**Note:** Numbers in the additive sequence **cannot** have leading zeros, so sequence `1, 2, 03` or `1, 02, 3` is invalid.
12+
13+
14+
15+
**Example 1:**
16+
17+
```
18+
Input: "112358"
19+
Output: true
20+
Explanation: The digits can form an additive sequence: 1, 1, 2, 3, 5, 8.
21+
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
22+
```
23+
24+
**Example 2:**
25+
26+
```
27+
Input: "199100199"
28+
Output: true
29+
Explanation: The additive sequence is: 1, 99, 100, 199.
30+
1 + 99 = 100, 99 + 100 = 199
31+
```
32+
33+
34+
35+
**Constraints:**
36+
37+
- `num` consists only of digits `'0'-'9'`.
38+
- `1 <= num.length <= 35`
39+
40+
**Follow up:**
41+
How would you handle overflow for very large input integers?
42+
43+
给一个字符串,判断字符串符不符合某种形式。就是从开头选两个数,它俩的和刚好是下一个数,然后第 `2` 个数和第 `3` 数字的和又是下一个数字,以此类推。
44+
45+
# 解法一
46+
47+
理解了题意的话很简单,可以勉强的归到回溯法的问题中,我们从暴力求解的角度考虑。
48+
49+
首先需要两个数,我们可以用两层 `for` 循环依次列举。
50+
51+
```javascript
52+
//i 表示第一个数字的结尾(不包括 i)
53+
for (let i = 1; i < num.length; i++) {
54+
//j 表示从 i 开始第二个数字的结尾(不包括 j)
55+
for (let j = i + 1; j < num.length; j++) {
56+
let num1 = Number(num.substring(0, i));
57+
let num2 = Number(num.substring(i, j));
58+
}
59+
}
60+
```
61+
62+
然后需要处理下特殊情况,将以 `0` 开头但不是`0`的数字去掉,比如 `023` 这种是不合法的,
63+
64+
```java
65+
//i 表示第一个数字的结尾(不包括 i)
66+
for (let i = 1; i < num.length; i++) {
67+
// 0 开头, 并且当前数字不是 0
68+
if (num[0] === '0' && i > 1) {
69+
return false;
70+
}
71+
//j 表示从 i 开始第二个数字的结尾(不包括 j)
72+
for (let j = i + 1; j < num.length; j++) {
73+
// 0 开头, 并且当前数字不是 0
74+
if (num[i] === '0' && j - i > 1) {
75+
break;
76+
}
77+
let num1 = Number(num.substring(0, i));
78+
let num2 = Number(num.substring(i, j));
79+
}
80+
}
81+
```
82+
83+
有了这两个数字,下边的就好说了。
84+
85+
只需要计算 `sum = num1 + num2` ,然后看 `sum` 在不在接下来的字符串中。
86+
87+
如果不在,那么就考虑下一个 `num1``num2`
88+
89+
如果在的话,`num1` 就更新为 `num2``num2` 更新为 `sum` ,有了新的 `num1``num2` ,然后继续按照上边的步骤考虑。
90+
91+
举个例子,
92+
93+
```java
94+
1 2 2 1 4 16
95+
^ ^
96+
i j
97+
98+
num1 = 12
99+
num2 = 2
100+
sum = num1 + num2 = 14
101+
102+
接下来的数字刚好是 14, 那么就更新 num1 和 num2
103+
104+
num1 = num2 = 2
105+
num2 = sum = 14
106+
107+
然后继续判断即可。
108+
```
109+
110+
代码的话,我们可以写成递归的形式。
111+
112+
```javascript
113+
/**
114+
* @param {string} num
115+
* @return {boolean}
116+
*/
117+
var isAdditiveNumber = function (num) {
118+
if (num.length === 0) {
119+
return true;
120+
}
121+
for (let i = 1; i < num.length; i++) {
122+
if (num[0] === '0' && i > 1) {
123+
return false;
124+
}
125+
for (let j = i + 1; j < num.length; j++) {
126+
if (num[i] === '0' && j - i > 1) {
127+
break;
128+
}
129+
let num1 = Number(num.substring(0, i));
130+
let num2 = Number(num.substring(i, j));
131+
if (isAdditiveNumberHelper(num.substring(j), num1, num2)) {
132+
return true;
133+
}
134+
}
135+
}
136+
return false;
137+
};
138+
139+
function isAdditiveNumberHelper(num, num1, num2) {
140+
if (num.length === 0) {
141+
return true;
142+
}
143+
//依次列举数字,判断是否等于 num1 + num2
144+
for (let i = 1; i <= num.length; i++) {
145+
//不考虑以 0 开头的数字
146+
if (num[0] === '0' && i > 1) {
147+
return false;
148+
}
149+
let sum = Number(num.substring(0, i));
150+
if (num1 + num2 === sum) {
151+
//传递剩下的字符串以及新的 num1 和 num2
152+
return isAdditiveNumberHelper(num.substring(i), num2, sum);
153+
//此时大于了 num1 + num2, 再往后遍历只会更大, 所以直接结束
154+
} else if (num1 + num2 < sum) {
155+
break;
156+
}
157+
}
158+
return false;
159+
}
160+
```
161+
162+
主要思想就是上边的了,看了 [这里](https://leetcode.com/problems/additive-number/discuss/75567/Java-Recursive-and-Iterative-Solutions) 的分析,代码的话,可以简单的优化下。
163+
164+
第一点,如果两个数的位数分别是 `a` 位和 `b` 位,`a >= b`,那么它俩相加得到的和至少是 `a` 位。比如 `100 + 99` 得到 `199`,依旧是三位数。
165+
166+
所以我们的 `num1``num2` 其中的一个数的位数一定不能大于等于字符串总长度的一半,不然的话剩下的字符串一定不等于 `num1``num2` 的和,因为剩下的长度不够了。
167+
168+
第二点,上边的代码中 `isAdditiveNumberHelper` 函数,略微写的复杂了些,我们可以直接利用 `String``startsWith` 函数,判断 `num1 + num2 ` 是不是字符串的开头即可。
169+
170+
基于上边两点,优化后的代码如下。
171+
172+
```javascript
173+
/**
174+
* @param {string} num
175+
* @return {boolean}
176+
*/
177+
var isAdditiveNumber = function (num) {
178+
if (num.length === 0) {
179+
return true;
180+
}
181+
//这里取了等号,是因为长度是奇数的时候,除以二是向下取整
182+
for (let i = 1; i <= num.length / 2; i++) {
183+
if (num[0] === '0' && i > 1) {
184+
return false;
185+
}
186+
for (let j = i + 1; j < num.length; j++) {
187+
if (num[i] === '0' && j - i > 1 || (j - i) > num.length / 2) {
188+
break;
189+
}
190+
let num1 = Number(num.substring(0, i));
191+
let num2 = Number(num.substring(i, j));
192+
if (isAdditiveNumberHelper(num.substring(j), num1, num2)) {
193+
return true;
194+
}
195+
}
196+
}
197+
return false;
198+
};
199+
200+
function isAdditiveNumberHelper(num, num1, num2) {
201+
if (num.length === 0) {
202+
return true;
203+
}
204+
return num.startsWith(num1 + num2) && isAdditiveNumberHelper(num.substring((num1+num2+'').length), num2, num1 + num2);
205+
}
206+
```
207+
208+
当然,我们最初的分析很明显是迭代的形式,我们内层也可以直接写成循环,不需要递归函数。
209+
210+
```javascript
211+
/**
212+
* @param {string} num
213+
* @return {boolean}
214+
*/
215+
var isAdditiveNumber = function (num) {
216+
if (num.length === 0) {
217+
return true;
218+
}
219+
for (let i = 1; i <= num.length / 2; i++) {
220+
if (num[0] === '0' && i > 1) {
221+
return false;
222+
}
223+
for (let j = i + 1; j < num.length; j++) {
224+
if (num[i] === '0' && j - i > 1 || (j - i) > num.length / 2) {
225+
break;
226+
}
227+
let num1 = Number(num.substring(0, i));
228+
let num2 = Number(num.substring(i, j));
229+
let temp = num.substring(j);
230+
while(temp.startsWith(num1 + num2)) {
231+
let n = num1;
232+
num1 = num2;
233+
num2 = num1 + n;
234+
temp = temp.substring((num2 + '').length);
235+
if (temp.length === 0) {
236+
return true;
237+
}
238+
}
239+
}
240+
}
241+
return false;
242+
};
243+
```
244+
245+
还有一个扩展,`How would you handle overflow for very large input integers?`
246+
247+
让我们考虑数字太大了怎么办,此时解决大数相加的问题即可,所有的操作在字符串层面上进行。参考 [第 2 题](https://leetcode.wang/leetCode-2-Add-Two-Numbers.html)
248+
249+
#
250+
251+
这道题主要的想法就是列举所有情况,但总觉得先单独列举两个数字不够优雅,思考了下怎么把它合并到递归中,无果,悲伤。
252+
253+
上边其实是一种思路,只是在写法上可能有所不同,唯一的优化就是列举数字的时候考虑一半即可,但时间复杂度不会变化。
254+
255+
之所以说勉强归到回溯法,是因为遍历路径的感觉是,先选两个数,然后一路到底,失败的话不是回到上一层,而是直接回到开头,然后重新选取两个数,继续一路到底,不是典型的回溯。

0 commit comments

Comments
 (0)