引言
一维动态规划根据转移方程,复杂度一般有两种情况。
func(i) 只和 func(i-1)有关,时间复杂度是O(n),这种情况下空间复杂度往往可以优化为O(1)
func(i) 和 func(1~i-1)有关,时间复杂度是O(n*n),这种情况下空间复杂度一般无法优化,依然为O(n)
本篇讨论第一种情况
例题 1
Jump Game
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
For example:
A = [2,3,1,1,4]
, return true
.
A = [3,2,1,0,4]
, return false
.
class Solution { public: bool canJump(int A[], int n) { } };
这道题目肯定是用DP来做。
我一开始的想法为定义bool reachable[n] 数组,reachable[i] = true 表示第 i 元素可以到达末尾。
因此reachable[i] = if(reachable[i+1] == true || reachable[i+2] == true || …|| reachable[i+A[i]] == true)
返回reachable[0]即为答案。
但是如果按这种思路写,需要用一个二维循环来完成整个过程,时间复杂度依然为O(n2)
按这种思路写出来的代码:
class Solution { public: bool canJump(int A[], int n) { if(n <= 1) return true; bool *reachable = new bool[n-1]; if(A[0] >= (n-1)) return true; for(int i = n-2; i >= 0; --i){ if(A[i] >= (n-1-i)) reachable[i] = true; else{ int j; for(j = 1; j <= A[i]; ++j){ if(reachable[i+j]){ reachable[i] = true; break; } } if(j > A[i]) reachable[i] = false; } } return reachable[0]; } };
LeetCode上大数据是过不了的,超时。
网上参考了 http://blog.csdn.net/xiaozhuaixifu/article/details/13628465 的博文后,明白了上面这种思路的状态转移方程之所以效率低,是因为用bool 作为数组元素,这种思路本身就不是一个动态规划中推荐的思路。动态规划为了节省时间,往往尽可能地利用数组来存储最大量的信息,bool值只能存true和false。
改进版的思路是:这个数组不再单纯地存可达或不可达这样的bool值,而是存储从0位置出发的最大可达长度。定义数组int canStillWalk[n],canStillWalk[i]表示到达 i 位置后,依然有余力走出的最大长度。如果canStillWalk[i] < 0,表示走不到位置i。
状态转移方程为:
canStillWalk[i] = max(canStillWalk[i-1], A[i-1]) – 1;
这样我们在计算canStillWalk[i]时,就不再需要循环。
时间复杂度O(n), 空间复杂度 O(n)
class Solution { public: bool canJump(int A[], int n) { if(n <= 1) return true; if(A[0] >= (n-1)) return true; int *canStillWalk = new int[n]; canStillWalk[0] = A[0]; for(int i = 1; i < n; ++i){ canStillWalk[i] = max(canStillWalk[i-1], A[i-1]) - 1; if(canStillWalk[i] < 0) return false; } return canStillWalk[n-1] >= 0; } };
接着可以再简化,因为canStillWalk[i] 只和 canStillWalk[i-1]相关,那么我们就不需要定义一个数组来存放消息了,直接用pre和 cur就可以搞定,时间复杂度O(n), 空间复杂度 O(1):
class Solution { public: bool canJump(int A[], int n) { if(n <= 1) return true; if(A[0] >= (n-1)) return true; int pre = A[0], cur = 0; for(int i = 1; i < n; ++i){ cur = max(pre, A[i-1]) - 1; if(cur < 0) return false; pre = cur; } return cur >= 0; } };
小结
对于动态规划的题,状态转移方程比较重要,这道题的特殊性在于”步数连续”,就是说,A[i] = s,表明从A[i] 可以走1~s步,而不是给定的几个不连续的值,这样我们可以通过定义最长可达距离这个转移方程来简化思想。
例题 2
Decode Ways
A message containing letters from A-Z
is being encoded to numbers using the following mapping:
'A' -> 1 'B' -> 2 ... 'Z' -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.
For example,
Given encoded message "12"
, it could be decoded as "AB"
(1 2) or "L"
(12).
The number of ways decoding "12"
is 2.
时间复杂度O(n), 空间复杂度 O(1) 解法
class Solution { public: int numDecodings(string s) { if(s.empty()) return 0; int pre = -1, cur = 1, tmp; for(int i = 1; i <= s.length(); ++i){ tmp = cur; if(s[i-1] == '0'){ if(isCode(s, i-2, i-1)) cur = pre; else return 0; }else if(isCode(s, i-2, i-1)) cur += pre;; pre = tmp; } return cur; } private: bool isCode(string &s, int i, int j){ if(i < 0) return false; if(s[i] >= '3' || s[i] == '0') return false; if(s[i] == '2' && s[j] >= '7') return false; return true; } };