引言
一維動態規划根據轉移方程,復雜度一般有兩種情況。
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; } };