斐波那契数列
斐波那契数列的计算问题。例如:f(0) = 1,f(1) = 1; f(i) = f(i - 1) + f(i - 2);(i >=2)
求:f(n)
//解法1:简单的递归
public int fib(int n) {
if (n < 0) return -1;
else if (n == 1 || n == 0) return 1;
return fib(n - 1) + f(n - 2);
} 这样解法存在最大的问题是做了太多重复的计算。时间复杂度O(N^2);
例如 fib(n - 1) + f(n - 2)中 fib(n-1) = fib(n-3) + f(n-2);可见f(n - 2)等计算了多次.
解决办法:记忆体,时间复杂度O(N);
Map<Integer, Integer> map;//记忆体
public int fib(int n) {
//特判
if (n < 0) return 0;
map = new HashMap<>();
map.put(0, 0);
map.put(1, 1);
return dfs(n);
}
public int dfs(int n) {
//记忆搜索。
if (map.containsKey(n)) {
return map.get(n);
}
int res = dfs(n - 2) + dfs(n - 1);
map.put(n, res);//存储记忆。
return res;
}
将数组拆分成斐波那契序列
输入:"123456579" 输出:[123,456,579]
毫无疑问,递归;只要确定了第一个、第二个数,则第三个数可以确定,以此递推
几个特判条件:
- 多数字时,数字不能以 0 开头;
- 数字不能超出量程;
public List<Integer> splitIntoFibonacci(String S) {
List<Integer> list = new ArrayList<>();
backTrack(list, S, S.length(), 0, 0, 0);
return list;
}
/**
拆分字符串为斐波那契序列
@parameter list:存放拆分后字符串。
@parameter S 需要拆分的字符串
@parameter length 字符串长度
@parameter index 当前拆分位置
@parameter sum 前两数之和
@parameter prev 前一个数
@return true:能够拆分为斐波那契序列
false:不能拆分
*/
public boolean backTrack(List<Integer> list, String S, int length, int index, int sum, int prev) {
//特判
if (index == length) {
return list.size() > 2;
}
long curr = 0;
for (int i = index; i < length; i++) {
//以0开始(如"01", 但单独的0可以!)
if (i > index && s.charAt(i) == '0') {
break;
}
curr = curr*10 + (s.charAt(i) - '0');
//curr超出量程
if (curr > Integer.MAX_VALUE) {
break;
}
int temp = (int) curr;
//不为第一、二个数。
if (list.size() >= 2) {
if (temp > sum) {//前两个值相加无法构成当前值。
break;
} else if (temp < sum) {//curr 小于前两数相加,curr继续增大。
continue;
}
}
list.add(temp);//匹配成功,或者前两个数;
if (backTrack(list, S, length, i, pre + temp, temp)) {//后续能够形成斐波那契序列
return true;
} else {
//回溯,本质是修改第一个、第二个数;
list.remove(list.size() - 1);
//return false;不能return false;否则无法修改第一、二个数;
}
}
return false;
} 最长的斐波那契子序列的长度
解法1:暴力遍历
穷举第一、二个数,遍历后续是否有后续。时间复杂度O(N^3)
public int lenLongestFibSubseq(int[] arr) {
int len = arr.length;
int max = 2;
//穷举第一、二个数
for (int i = 0; i < len; i++) {
for (int j = i + 1; j < len; j++) {
int tempLen = 2;//记录单次最大值
int pre = arr[i], late = arr[j];
int sum = pre + late;
//循环后续的数。
for (int k = j + 1; k < len; j++) {
if (arr[k] == sum) {
pre = late;
late = sum;
sum = pre + late;
tempLen++;
} else if (arr[k] > sum) {
break;
}
}
//更新最大长度。
max = Math.max(tempLen, max);
}
}
return max >= 3 ? max : 0;
} 解法2:Hash加速第三层循环。
第三层循环是为查找arr中是否存在sum,使用hash代替。时间复杂度O(N^2*logM),M为arr中最大值
public int lenLongestFibSubseq(int[] arr) {
int len = arr.length;
int max = 2;
//set判断是否存在其和。
HashSet<Integer> set = new HashSet<Integer>();
for (int i : arr) {
set.add(i);
}
for (int i = 0; i < len; i++) {
for (int j = i + 1; j < len; j++) {
int tempLen = 2;//记录单次最大值
int pre = arr[i], late = arr[j];
int sum = pre + late;
//hash查找。
while(set.contains(sum)) {
tempLen++;
pre = late;
late = sum;
sum = pre + late;
}
//更新最大长度。
max = Math.max(tempLen, max);
}
}
return max >= 3 ? max : 0;
} 可以看到,虽然hash查找使得速度变快,但依然存在大量的重复计算
例如:[1,2,3,4,5,6,7,8] 中计算了1,2 -> 3 -> 5;也计算了直接的3 + 5
解法3:在解法2基础上将中间结果存储起来(map),便于后续查询
时间复杂度O(N^2);
public int lenLongestFibSubseq(int[] A) {
int N = A.length;
Map<Integer, Integer> index = new HashMap();
for (int i = 0; i < N; ++i)
index.put(A[i], i);
//用于存储中间结果,用于后续查询。
Map<Integer, Integer> longest = new HashMap();
int ans = 0;
for (int k = 0; k < N; ++k)
for (int j = 0; j < k; ++j) {
int i = index.getOrDefault(A[k] - A[j], -1);
if (i >= 0 && i < j) {
int cand = longest.getOrDefault(i * N + j, 2) + 1;
longest.put(j * N + k, cand);
ans = Math.max(ans, cand);
}
}
return ans >= 3 ? ans : 0;
} 


京公网安备 11010502036488号