Description
(原题描述太罗嗦了,我转换一下语言)
算出一个数组中的等差序列个数,其中找到的序列为原数组子序列。
Example:
Input: [-1, 1, 2, 3, 4, 7]
Output: 6
Explanation: All arithmetic subsequence slices are[-1,1,3]、[-1,3,7]、[1,2,3]、[2,3,4]、[1,2,3,4]、[1,4,7]
Notice:
The input contains N integers. Every integer is in the range of -2^31 and 2^31-1 and 0 ≤ N ≤ 1000. The output is guaranteed to be less than 2^31-1.
Analyze
从动态规划的思路出发,在这个序列上,我转移到了后面的状态如何运用前面的结果呢?前面应该保存什么信息呢?
稍加思考,会发现我们需要保存之前所有出现过的序列,而[diff,length,last]能唯一确定一个序列。在移动到一个新的数时,比较这个数与尾的差是否等于序列的等差即可判断这个数是否能加到这个序列尾部。这便是基本思路。
但是开始动手实现你就会发现很多难题。以直接保存所有的三元组为前提,初始化是什么情况?一个数不可以直接追加到尾部要怎么处理?(要么它真的是一个很“离谱”的数,要么像上面例子中的7一样,可以挑出1234中的1和4“另起炉灶”)我可以想出一些非常蹩脚的解决办法——蹩脚到甚至提不起劲去实现完整,更没自信去测试和debug。
答案保存信息的方式非常机智,它没有把其中的序列一个个抽离出来看,而是保存下每个数作为last的可能序列(也即包含所有长度为2的可能组合),而这些序列可以完全用diff来区分。
vector<unordered_map<int,int>> dp(A.size());//[index, [difference, count]]
此时状态间转移的关系就清晰了,转到一个新的数时,就向前遍历所有数的所有可能序列,但凡这个新数减去遍历到的那个数得到的差,能在那个数所保存的序列中找到一个其等差与之相等,那么就给新数加上一个以此为差,原长+1的序列。否则,就简单保存一个新的可能组合,即[这个差,1]。
count的方式也很机智,啊,妙不可言!我不知道别人是怎么想到这么巧的方法的,所以也分析不出太多道道,其余的大家就在代码中体会吧。
Code
int numberOfArithmeticSlices(vector<int>& A) {
if(A.empty()) return 0;
vector<unordered_map<int,int>> dp(A.size());//[index, [difference, count]]
int res = 0;
for(int i = 0; i < A.size(); ++i){
for(int j = 0; j < i; ++j){
if((long)A[i] - (long)A[j] > INT_MAX || (long)A[i] - (long)A[j] < INT_MIN) continue;// do not ignore this step, it will help you save time & space to pass OJ.
int dif = A[i] - A[j];
dp[i][dif] += 1;
if(dp[j].find(dif) != dp[j].end()){
dp[i][dif] += dp[j][dif];
res += dp[j][dif];
}
}
}
return res;
}
Summary
这道题给人一种计算困难的感觉,假如像我一样下意识排斥O(n^2)的算法,几乎会认为不可解。回头想想,这道题本身就很暴力,你无法排除新的数跟它前面的每一个数去结合的可能。
想不到这么好的办法啊,于是第一次向答案屈服了,自己跟自己尴尬得不行。我只好安慰自己菜很正常,要平常心。