1. 程式人生 > >[LeetCode] Arithmetic Slices II

[LeetCode] Arithmetic Slices II

A sequence of numbers is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.

For example, these are arithmetic sequences:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

The following sequence is not arithmetic.

1, 1, 2, 5, 7

A zero-indexed array A consisting of N numbers is given. A subsequence slice of that array is any sequence of integers (P0, P1, ..., Pk) such that 0 ≤ P0 < P1 < ... < Pk < N.

A subsequence slice (P0, P1, ..., Pk) of array A is called arithmetic if the sequence A[P0], A[P1], ..., A[Pk-1], A[Pk] is arithmetic. In particular, this means that k ≥ 2.

The function should return the number of arithmetic subsequence slices in the array A.

The input contains N integers. Every integer is in the range of -231 and 231-1 and 0 ≤ N ≤ 1000. The output is guaranteed to be less than 231-1.

Example:

Input: [2, 4, 6, 8, 10]

Output: 7

Explanation:
All arithmetic subsequence slices are:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]


這道題是之前那道Arithmetic Slices的延伸,那道題比較簡單是因為要求等差數列是連續的,而這道題讓我們求是等差數列的子序列,可以跳過某些數字,不一定非得連續,那麼難度就加大了,但還是需要用動態規劃Dynamic Progrmming來做。知道用DP來做是一回事,真正能做出來又是另一回事。刷題的最終目的不是背題,而是訓練思維方式,如何從完全沒思路,變為好像有點思路,慢慢推理演變找出正確解,就像順著藏寶圖的絲絲線索最終發現了寶藏一樣,是無比的令人激動和富有成就感的過程。死記硬背的話,只要題目稍稍變形一下就完蛋。這就是為啥博主喜歡看fun4LeetCode大神的帖子,雖然看範佛力扣大神的帖子像讀論文,但是有推理過程,看完讓人神清氣爽,茶飯不思,燃鵝遇到同類型的還是不會,還是要再看。好,博主皮一下就行了,下面來順著大神的帖子來講吧。

好,既然決定要用DP了,那麼首先就要確定dp陣列的定義了,剛開始我們可能會考慮使用個一維的dp陣列,然後dp[i]定義為範圍為[0, i]的子陣列中等差數列的個數。定義的很簡單,OK,但是基於這種定義的遞迴式卻十分的難想。我們想對於(0, i)之間的任意位置j,如何讓 dp[i] 和 dp[j] 產生關聯呢?是不是隻有 A[i] 和 A[j] 的差值diff,跟A[j]之前等差數列的差值相同,才會有關聯,所以差值diff是一個很重要的隱藏資訊Hidden Information,我們必須要在dp的定義中考慮進去。所以一維dp陣列是罩不住的,必須升維,但是用二維dp陣列的話,差值diff那一維的範圍又是個問題,數字的範圍是整型數,所以差值的範圍也很大,為了節省空間,我們建立一個一維陣列dp,數組裡的元素不是數字,而是放一個HashMap,建立等差數列的差值和當前位置之前差值相同的數字個數之間的對映。我們遍歷陣列中的所有數字,對於當前遍歷到的數字,又從開頭遍歷到當前數字,計算兩個數字之差diff,如果越界了不做任何處理,如果沒越界,我們讓dp[i]中diff的差值對映自增1,因為此時A[i]前面有相差為diff的A[j],所以對映值要加1。然後我們看dp[j]中是否有diff的對映,如果有的話,說明此時相差為diff的數字至少有三個了,已經能構成題目要求的等差數列了,將dp[j][diff]加入結果res中,然後再更新dp[i][diff],這樣等遍歷完陣列,res即為所求。

我們用題目中給的例子陣列 [2,4,6,8,10] 來看,因為2之前沒有數字了,所以我們從4開始,遍歷前面的數字,是2,二者差值為2,那麼在dp[1]的HashMap就可以建立 2->1 的對映,表示4之前有1個差值為2的數字,即數字2。那麼現在i=2指向6了,遍歷前面的數字,第一個數是2,二者相差4,那麼在dp[2]的HashMap就可以建立 4->1 的對映,第二個數是4,二者相差2,那麼先在dp[2]的HashMap建立 2->1 的對映,由於dp[1]的HashMap中也有差值為2的對映,2->1,那麼說明此時至少有三個數字差值相同,即這裡的 [2 4 6],我們將dp[1]中的對映值加入結果res中,然後當前dp[2]中的對映值加上dp[1]中的對映值。這應該不難理解,比如當i=3指向數字8時,j=2指向數字6,那麼二者差值為2,此時先在dp[3]建立 2->1 的對映,由於dp[2]中有 2->2 的對映,那麼加上數字8其實新增了兩個等差數列 [2,4,6,8] 和 [4,6,8],所以結果res加上的值就是 dp[j][diff],即2,並且 dp[i][diff] 也需要加上這個值,才能使得 dp[3] 中的對映變為 2->3 ,後面數字10的處理情況也相同,這裡就不多贅述了,最終的各個位置的對映關係如下所示:

2     4     6     8     10    
     2->1  4->1  6->1  8->1
           2->2  4->1  6->1 
                 2->3  4->2
                       2->4

最終累計出來的結果是跟上面紅色的數字相關,分別對應著如下的等差數列:

2->2:[2,4,6]

2->3:[2,4,6,8]    [4,6,8]

4->2:[2,6,10]

2->4:[2,4,6,8,10]    [4,6,8,10]    [6,8,10]

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        int res = 0, n = A.size();
        vector<unordered_map<int, int>> dp(n);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                long delta = (long)A[i] - A[j];
                if (delta > INT_MAX || delta < INT_MIN) continue;
                int diff = (int)delta;
                ++dp[i][diff];
                if (dp[j].count(diff)) {
                    res += dp[j][diff];
                    dp[i][diff] += dp[j][diff];
                }
            }
        }
        return res;
    }
};

類似題目:

參考資料: