[leetCode]38. 外观数列
解法一 递归+双指针
f
(
1
)
=
1
f(1) = 1
f(1)=1
f
(
2
)
=
1
1
f(2) = \bm11
f(2)=11
f
(
3
)
=
2
1
f(3) = \bm21
f(3)=21
f
(
4
)
=
1
2
1
1
f(4) = \bm12\bm11
f(4)=1211
…
通过观察可以知道要知道f(n)则需要知道f(n-1)…f(1),因此可以想到递归;通过观察可知,每个外观字符串都是由前一个字符串的每一个字符"计数值"(加粗字符)加上所计数的字符,因此可以使用双指针法对相同字符计数并完成字符串拼接。
class Solution {
public String countAndSay(int n) {
if(n == 1){
return "1";
}else{
String preStr = countAndSay(--n);
String ans = "";
int len = preStr.length();
int i = 0;//指针i指向preStr首字符(慢指针)
for(int j = i+1; j < len; j++){//j为快指针用于跳过相同字符
if(preStr.charAt(j)!=preStr.charAt(i)){//跳过相同字符结束
ans = ans + (j-i) + preStr.charAt(i);//j-i为跳过相同字符的数量, preStr.charAt(i)为所跳过的字符
i = j;//移动i指针
}
}
ans = ans + (len-i) + preStr.charAt(i);//上面遍历结束后可能还存在尾部连续字符,这一步就是拼接尾部字符
return ans;
}
}
}
动态规化
因为递归并没有涉及到重复计算,所以动态规化时间复杂度也没有优化。
class Solution {
public String countAndSay(int n) {
String[] dp = new String[n];
dp[0] = "1";
for(int t = 1; t < n; t++){
dp[t] = "";
String preStr = dp[t-1];
int len = preStr.length();
int i = 0;
for(int j = i + 1; j < len; j++){
if(preStr.charAt(j)!=preStr.charAt(i)){
dp[t]=dp[t]+(j-i)+preStr.charAt(i);
i = j;
}
}
dp[t]=dp[t]+(len-i)+preStr.charAt(i);
}
return dp[n-1];
}
}
由于动态规化只记录了两个状态:前一个字符串和后一个字符串,因此可以用两个String 变量对空间复杂度进行优化:
class Solution {
public String countAndSay(int n) {
String preStr = "1";
String curStr = "";
for(int t = 1; t < n; t++){
int len = preStr.length();
int i = 0;
for(int j = i + 1; j < len; j++){
if(preStr.charAt(j)!=preStr.charAt(i)){
curStr= curStr + (j-i)+preStr.charAt(i);
i = j;
}
}
curStr=curStr + (len-i)+preStr.charAt(i);
preStr = curStr;
curStr = "";
}
return preStr;
}
}