算法系列博客之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)