數(shù)據(jù)結(jié)構(gòu)與算法-DP/動態(tài)規(guī)劃

動態(tài)規(guī)劃

三要素:

狀態(tài)
狀態(tài)轉(zhuǎn)移方程
空間換時間:保存每一步的遞推結(jié)果

1. leetcode 300.最長遞增子串 (LIS)
給定一個數(shù)列习瑰,長度為N悔橄,
求這個數(shù)列的最長上升(遞增)子數(shù)列(LIS)的長度.
以1 7 2 8 3 4為例日矫。
這個數(shù)列的最長遞增子數(shù)列是 1 2 3 4斋攀,長度為4到腥;
次長的長度為3, 包括 1 7 8; 1 2 3 等.

什么是狀態(tài)轉(zhuǎn)移方程萌业?
上述狀態(tài)定義好之后坷襟,狀態(tài)和狀態(tài)之間的關(guān)系式,就叫做狀態(tài)轉(zhuǎn)移方程生年。
對于LIS這個問題婴程,狀態(tài)轉(zhuǎn)移方程為:

F1 = 1
Fn =max(Fi +1 | Ak > Ai, i 屬于(1 .. k-1)) (k>1)

用文字解釋一下是:
以第k項(xiàng)結(jié)尾的LIS的長度是:保證第i項(xiàng)比第k項(xiàng)小的情況下,以第i項(xiàng)結(jié)尾的LIS長度加一的最大值抱婉,取遍i的所有值(i小于k)档叔。

#代碼桌粉,開一個同樣空間的F列表來保存每一個子狀態(tài)當(dāng)前的最大長度
#對于每一個當(dāng)前位置i,要比較小于i的所有位置F上的值
#復(fù)雜度 O(n^2)

def lis_dp(nums):
    F = [1] * len(nums)
    for i in range(1,len(nums)):
        tmp_max = 1
        for j in range(i):
            if nums[i] > nums[j]:
                if F[j] + 1 > tmp_max:
                    tmp_max = F[j] + 1
        F[i] = tmp_max
    print(F)
    return max(F)


2. leetcode 70. 爬樓梯
# 爬樓梯問題衙四,一個人每次可以爬一階或兩階樓梯铃肯,問爬上N階樓梯有多少種爬法
# 問題可以轉(zhuǎn)化為子問題N-1階樓梯的爬法與N-2階樓梯的爬法之和(分別再爬一階或兩階)
狀態(tài)轉(zhuǎn)移方程:
dp(i) = dp(i-1) + dp(i-2)
# N = 1 時候有一種爬法
# N = 2 時候有兩種爬法(2或1+1)

def palouti(num):
    dp = [0] * num 
    dp[0] = 1
    dp[1] = 2
    for i in range(2,num):
        dp[i] = dp[i-1] + dp[i-2]

    return dp[num - 1]

# 和斐波那契數(shù)列很像,為了減少計算復(fù)雜度届搁,使用DP的思想缘薛,拿一個數(shù)組保存之前子問題的計算結(jié)果

3. leetcode 53 最大子數(shù)組

Maximum Subarray

# 給定一個整數(shù)數(shù)組,找到一個具有最大和的子數(shù)組卡睦,返回其最大和宴胧。
# 給出數(shù)組[?2,2,?3,4,?1,2,1,?5,3],符合要求的子數(shù)組為[4,?1,2,1]表锻,其最大和為6

def maxSubArray(nums):
    tmp_max = nums[0] # tmp_max 全局到當(dāng)前位置的最大保存值
    cur_max = nums[0] # 當(dāng)前連續(xù)序列的最大值
    for i in range(1,len(nums)):
        cur_max = max(cur_max + nums[i],nums[i]) # 判斷當(dāng)前的數(shù)組元素是否要替換之前連續(xù)的數(shù)組元素
        #比如下面開頭的[-4,5]恕齐,到5當(dāng)前最大[5]為5,大于[-4,5]的1瞬逊,因此新的最大子數(shù)組一定以5開頭
        if cur_max > tmp_max:
            tmp_max  = cur_max
    return tmp_max

if __name__ == '__main__':
    a = [-4,5,-4,5,-4,5,-4,5,-4,5,-4,5,-4,5,-4,5,-4,5,-1000]
    print(maxSubArray(a))

4.最大子數(shù)組 II -lintcode42
# 給定一個整數(shù)數(shù)組显歧,找出兩個 不重疊 子數(shù)組使得它們的和最大。
每個子數(shù)組的數(shù)字在數(shù)組中的位置應(yīng)該是連續(xù)的确镊。
返回最大的和士骤。
# 給出數(shù)組 [1, 3, -1, 2, -1, 2]
這兩個子數(shù)組分別為 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2],它們的最大和都是 7

做法1:
遍歷該數(shù)組蕾域,對于每個分割點(diǎn)拷肌,用上面最大子數(shù)組的方法分別對左邊合右邊求最大子數(shù)組的和,取最大的和旨巷,復(fù)雜度O(n^2)

做法2: 
DP方法巨缘,提前記錄好每個位置上之前的數(shù)組的最大子數(shù)組之和
把該數(shù)組從左到右遍歷,記錄每一個位置上之前數(shù)組的最大子數(shù)組之和
再從右往左遍歷采呐,同樣記錄
之后對于每一個i到len(nums)-1的index若锁,計算從左到右和從右到左對應(yīng)位置最大子數(shù)組之和,最大的為結(jié)果斧吐,復(fù)雜度O(n)
class Solution:
    """
    @param: nums: A list of integers
    @return: An integer denotes the sum of max two non-overlapping subarrays
    """
    

    def subArray(self,nums):
        F = [0] * len(nums)
        tmp_max = nums[0]
        cur_max = nums[0]
        F[0] = nums[0]
        for i in range(1,len(nums)):
            cur_max = max(cur_max + nums[i],nums[i])
            tmp_max = max(tmp_max,cur_max)
            F[i] = tmp_max
        return F


    def maxTwoSubArrays(self, nums):
        all_max = nums[0] + nums[1]
        F = self.subArray(nums) # 從左到右
        nums.reverse()
        F1 = self.subArray(nums) # 從右到左
        
        for i in range(1,len(nums)-1):
            tmp_max = F[i] + F1[len(nums)-2-i] # 這里下標(biāo)需要注意
            all_max = max(tmp_max,all_max)
        
        return all_max


leetcode 62 Unique Paths

在一個M * N的矩陣中又固,start為左下角(1,1)點(diǎn),end為右上角(m,n)煤率,只能向上或者向右走口予,有多少種獨(dú)立的走法?

DFS法:
樹結(jié)構(gòu)走,每次走[0,1]代表上和右涕侈,dfs結(jié)束條件為 up + right = m + n -2 (注意是從1,1開始所以要-2)
dfs返回條件是up >=m or n >= n
該種方法樹剪枝的效率一般沪停,leetcode上會超時,但是方便打印所有走的路線。

class Solution(object):
    def uniquePaths(self, m, n):
        
        def dfs(up,right,m,n,path,nums,nb):
            if up >= m or right >= n:
                return 
            if len(path) ==  m + n - 2:
                nb.append(path) 
                return 
            for i in nums:
                if i == 0:
                    dfs(up + 1,right,m,n,path + [0],nums,nb)
                if i == 1:
                    dfs(up, right + 1,m,n,path+[1],nums,nb)
        
        nums = [0,1]
        path,up,right,nb = [],0,0,[]
        dfs(up,right,m,n,path,nums,nb)
        return len(nb)

DP法
狀態(tài)轉(zhuǎn)移方程:
dp(m,n) = dp(m-1,n) + dp(m,n-1)
走到當(dāng)前位置的走法相當(dāng)于走到左邊的走法加上走到右邊的走法數(shù)量木张。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        dp = [[1 for x in range(n)] for x in range(m)]
        for i in range(1,m):
            for j in range(1,n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

leetcode 63 Unique Path2

從矩陣左下角走到右上角(或叫左上角走到右下角)的獨(dú)立path個數(shù)众辨,只能走up或者right,一次走一步舷礼,矩陣?yán)锸怯衎lock的鹃彻,block表示為1,通路表示為0

如
0 0 0 
0 1 0
0 0 0
從左下走到右上妻献,由于正中間有一個block蛛株,因此只有兩條path
即 [up,up,right,right] 或者 [right,right,up,up]

狀態(tài)轉(zhuǎn)移方程

兩個一樣大小的矩陣M和dp,M保存矩陣中碰到阻礙的情況(1是阻礙)
dp中保存矩陣中起點(diǎn)到當(dāng)前位置的不同path數(shù)量
1. 初始dp[0][0] = 1 - M[0][0] 育拨,因?yàn)镸=0是暢通谨履,M=1是阻礙
2. 當(dāng)i == 0 or j ==0 時: #兩條邊
     if M[i][0] == 1:
        dp[i][0] = 0
     else:
         dp[i][0] = dp[i-1][0] 
對dp[0][j]同理
3. 當(dāng)i >= 1 and j >= 1時:
  if M[i][j] == 1:
      dp[i][j] = 0
  elif M[i][j] == 0:
      dp[i][j] = dp[i-1][j] + dp[i][j-1]

最終代碼:
這種解法可讀性較強(qiáng),空間復(fù)雜度O(m*n), leetcode discuss上也有空間復(fù)雜度O(n)和in-place的熬丧,可見https://discuss.leetcode.com/topic/19795/python-different-solutions-o-m-n-o-n-in-place (其中O(m*n)和這個解法一模一樣笋粟,代碼更trick一些)

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        dp = [[0 for i in range(n)] for j in range(m)]
        dp[0][0] = 1 - obstacleGrid[0][0]
        for i in range(1,m): # lower edge
            if obstacleGrid[i][0] == 1:
                dp[i][0] = 0
            else:
                dp[i][0] = dp[i-1][0]

        for i in range(1,n): # left edge
            if obstacleGrid[0][i] == 1:
                dp[0][i] = 0
            else:
                dp[0][i] = dp[0][i-1]

        for i in range(1,m):
            for j in range(1,n):
                if obstacleGrid[i][j] == 1:
                    dp[i][j] = 0
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]

        return dp[m-1][n-1]

leetcode 64. Minimum Path Sum

給定一個矩陣,從左上角走到右下角析蝴,要求路徑和最小

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Example 1:
[[1,3,1],
 [1,5,1],
 [4,2,1]]
Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.
  1. DFS暴力解法害捕,遍歷全部路徑,記錄sum最短的那條闷畸,見函數(shù)minPathSum-dfs
  2. DP解法尝盼,見函數(shù)dp
狀態(tài)轉(zhuǎn)移方程:
if i == 0 or j == 0 (在兩條邊): 
    dp[i][0] = dp[i-1][0] + M[i][0]
    dp[0][j] = dp[0][j-1] + M[0][j]
else:
  dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + M[i][j] # 如果是sum最大路徑,這里min換成max就可以

DFS在leetcode上會超時不能AC佑菩,一部分tests沒法通過

#coding=utf-8
class Solution(object):
    def minPathSum(self, grid):
        path = [grid[0][0]]
        res = []
        self.res_final = []
        height = len(grid)
        length = len(grid[0])
        self.m = height 
        self.n = length
        self.max_tmp = 1e10
        self.dfs(grid,0,0,path,res)
        return sum(self.res_final)
    def dfs(self,grid,height,length,path,res):
    
        if len(path) == self.m + self.n - 1:
            # print(path)
            if sum(path) < self.max_tmp:
                self.max_tmp = sum(path)
                self.res_final = path
        for i in range(2):
            if i == 0 : # means go right
                if length + 1 > self.m - 1:
                    continue
                self.dfs(grid,height,length + 1,path + [grid[length+1][height]],res)
                
            if i == 1 : # means go down
                if height + 1 > self.n - 1:
                    continue
                self.dfs(grid,height + 1,length,path + [grid[length][height + 1]],res)
               

DP法

    def dp(self,grid):
        height = len(grid)
        length = len(grid[0])
        dp = [[0 for i in range(length)] for j in range(height)]
        dp[0][0] = grid[0][0]
        
        for i in range(1,height):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        for j in range(1,length):
            dp[0][j] = dp[0][j-1] + grid[0][j]
        
        for i in range(1,height):
            for j in range(1,length):
                dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j] # dp[1][1] = dp[1][0] + dp[0][1]
        
        return dp[height-1][length-1]

if __name__ == '__main__':
    Solution = Solution()
    grid = [[1,3,1,1],[4,2,1,1],[4,2,4,1]]#
    # grid = [[1,3,1],[1,5,1],[4,2,1]]
    print(Solution.minPathSum(grid))
    print(Solution.dp(grid))

leetcode 120. Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle
[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
給定一個三角形东涡,從頂往下走,每個點(diǎn)只能走下面的左右相鄰的兩點(diǎn)倘待,求和為最小的路徑的和的值

一般來說,求最后的值不用打印路徑的都可以用DP完成组贺,需要打印路徑的可以用DFS完成凸舵。
顯然狀態(tài)轉(zhuǎn)移方程為下:

T: 三角形的值數(shù)組,dp當(dāng)前和最小路徑的數(shù)組失尖,T和dp大小完全一致啊奄。
1. dp[0][0] = T[0][0]
2. for i in range(1,len(dp)): # 從第二行開始
      對于每行第一個元素:dp[i][j] = dp[i-1][j] + T[i][j] (三角形,只能接收上一行最左元素)
      對于每行最后一個元素:dp[i][j] = dp[i-1][j-1] + T[i][j] (三角形掀潮,只能接收上一行最右元素)
      對于每行中間的元素:dp[i][j] = min(dp[i-1][j-1] ,dp[i-1][j]) + T[i][j]
  

code菇夸,空間O(m*n)

class Solution:
    
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        dp = [[i for i in j] for j in triangle]

        dp[0][0] = triangle[0][0]
        for i in range(1,len(triangle)):
            for j in range(len(triangle[i])):
                if j == 0 :
                    dp[i][j] = dp[i-1][j] + triangle[i][j]
                    print(dp[i][j])
                elif j == len(triangle[i]) - 1:
                    dp[i][j] = dp[i-1][j-1] + triangle[i][j]
                else:
                    dp[i][j] = min(dp[i-1][j-1] ,dp[i-1][j]) + triangle[i][j]
        
        return min(dp[len(dp)-1])

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仪吧,隨后出現(xiàn)的幾起案子庄新,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件择诈,死亡現(xiàn)場離奇詭異械蹋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)羞芍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門哗戈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荷科,你說我怎么就攤上這事唯咬。” “怎么了畏浆?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵胆胰,是天一觀的道長。 經(jīng)常有香客問我全度,道長煮剧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任将鸵,我火速辦了婚禮勉盅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顶掉。我一直安慰自己草娜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布痒筒。 她就那樣靜靜地躺著宰闰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪簿透。 梳的紋絲不亂的頭發(fā)上移袍,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音老充,去河邊找鬼葡盗。 笑死,一個胖子當(dāng)著我的面吹牛啡浊,可吹牛的內(nèi)容都是我干的觅够。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼巷嚣,長吁一口氣:“原來是場噩夢啊……” “哼喘先!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起廷粒,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤窘拯,失蹤者是張志新(化名)和其女友劉穎红且,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體树枫,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡直焙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了砂轻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奔誓。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搔涝,靈堂內(nèi)的尸體忽然破棺而出厨喂,到底是詐尸還是另有隱情,我是刑警寧澤庄呈,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布蜕煌,位于F島的核電站,受9級特大地震影響诬留,放射性物質(zhì)發(fā)生泄漏斜纪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一文兑、第九天 我趴在偏房一處隱蔽的房頂上張望盒刚。 院中可真熱鬧,春花似錦绿贞、人聲如沸因块。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涡上。三九已至,卻和暖如春拒名,著一層夾襖步出監(jiān)牢的瞬間吩愧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工增显, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雁佳,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓甸怕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腮恩。 傳聞我的和親對象是個殘疾皇子梢杭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容