算法系列博客之Dynamic Programming
動態規劃是和貪心算法比較相似的一種算法策略
很多時候它們一般都遵從於某種線性的策略,使得整個邏輯和複雜度都看上去是線性的
但其二者有着本質的區別
動態規劃實際上是在劃分子問題,子問題可以用同種方法進行再度拆解,湊巧劃分的過程大多數時候是線性的
而貪心算法則是解決問題的步驟看似是一個線性過程,但每一步可以看作一個原子操作,不可拆解
本篇博客將運用動態規劃的思想來解決leetcode上413號問題
問題描述:
A sequence of number is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.
A zero-indexed array A consisting of N numbers is given. A slice of that array is any pair of integers (P, Q) such that 0 <= P < Q < N.
A slice (P, Q) of array A is called arithmetic if the sequence:
A[P], A[p + 1], …, A[Q - 1], A[Q] is arithmetic. In particular, this means that P + 1 < Q.
The function should return the number of arithmetic slices in the array A.
題目看上去很複雜,需要考慮連續以及間斷掉的情況,但實際上連續的這個限制就已經給我們提供了線索
掃描每個數字的時候都判斷它是否與之前的差距相等,一旦出現不等,就可以以該數字爲起點當作一個子問題來處理了
現在來看差距相等的連續數字個數k與arithmetic slices個數nums[k]之間的關係:
· 如果k < 3,根據arithmetic的定義,nums[k] = 0
· 如果連續的數字大於等於3個,則nums[k] = nums[k-1] + (k-2)
在向前推進的過程中,逐漸將nums[k] 加到結果res中去(實際上完全可以用res來替換掉數組nums)
當出現與前面的數字差不一樣的數字的時候,重置k,以該數字爲起點,保留現有res的基礎上進行子問題求解
class Solution(object):
def numberOfArithmeticSlices(self, A):
length = len(A)
if length < 3:
return 0
k = 1
diff = A[1] - A[0]
res = 0
for i in range(2, length):
if A[i] - A[i-1] == diff:
res += k
k += 1
else :
diff = A[i] - A[i-1]
k = 1
return res
上面的代碼在邏輯上看上去是非常簡明清晰的,但是實際上對於res累加的過程是依據於表達式nums[k] = nums[k-1] + (k-2)的
仔細觀察發現每次else的時候對k重置,累加也就重新開始, 而通過簡單的數學推理就可以發現只要利用重置前的k值進行一個簡單的數學運算就可以替換掉之前的累加運算來減少每個循環單元的運算次數(不過由於循環規模並沒有降低,因而並不會降低算法複雜度)
class Solution(object):
def numberOfArithmeticSlices(self, A):
def f(n):
return 0 if n < 2 else (n*(n-1)) / 2
length = len(A)
if length < 3:
return 0
k = 1
diff = A[1] - A[0]
res = 0
for i in range(2, length):
if A[i] - A[i-1] == diff:
k += 1
else :
diff = A[i] - A[i-1]
res += f(k)
k = 1
return res + f(k)
時間複雜度分析,整個過程只有一個線性循環,兩種實現循環內部都只有常數倍的運算,因而O(n)
空間複雜度分析,兩種實現都只是額外需要四個基本變量,因而O(1)