【DP演算法篇之初學】LIS\LCS\二維DP\帶條件DP
最近參加2016華為軟體精英挑戰賽,題目也比較直接,就是求過定點的最短路。這題和以前練得不一樣,感覺是不是要用DP(動態規劃)。可是對於DP演算法,我還是啥都不懂,於是好好補補。
看完入門,有點感覺了,然後是LIS問題,文中又提到了LCS問題,說這個更基礎,於是轉去看LCS。
這張圖是演算法關鍵:
注意這裡的C[i][j],x[i],y[j]下標含義一樣,指向相同的位置~
看完LCS,就拿poj1458練練手:
//time:16MS //mem:396K #include <string> #include <iostream> #include <vector> using namespace std; inline int max(int a, int b) { return a > b ? a : b; } int LCS(const string x, const string y) { int m, n, i, j; m = x.length(); n = y.length(); vector<vector<int> > table(m + 1, vector<int>(n + 1)); for(i = 0; i < m + 1; ++i) { for(j = 0; j < n + 1; ++j) { if(i == 0 || j == 0) table[i][j] = 0; else if(x[i - 1] == y[j - 1]) table[i][j] = table[i - 1][j - 1] + 1; else table[i][j] = max(table[i - 1][j], table[i][j - 1]); } } return table[m][n]; } int main() { string x, y; while(cin >> x) { cin >> y; cout << LCS(x, y) << endl; } return 0; }
程式碼裡特別要注意的是 table[][] 陣列下標與 x[] , y[] 下標含義不同,比如 x[i] 表示第 i+1 個元素,而 table[i][j]則表示截止到x[i-1]與y[j-1]的LCS。有關LCS序列的輸出可以參照上面神奕的部落格。
接著返回我們的 LIS問題:
最關鍵的狀態轉移方程如下:
設 A[i] 是該序列,並用 D[i] 表示以A[i]結尾的最長非降子序列的長度:
D[j] = max {1, A[i] + 1},if A[j] >= A[i],其中 i < j
此時,我們可以拿 leetcode #300 Longest Increasing Subsequence 練練手:
//leetcode300 Longest Increasing Subsequence //144ms class Solution { public: int lengthOfLIS(vector<int>& nums) { int i, j, len = 1, SIZE = nums.size(); if(!nums.size()) //防止陣列為空 return 0; vector<int> D(SIZE, 1); for(i = 1; i < SIZE; ++i) { for(j = 0; j <= i - 1; ++j) { if(nums[j] < nums[i] && D[j] + 1 > D[i]) //遞增序列,若求非降子序列,改nums[j] <= nums[i] D[i] = D[j] + 1; if(len < D[i]) len = D[i]; } } return len; } };
此O(n^2)演算法果然慢些,換成O(nlogn):
//leetcode300 Longest Increasing Subsequence
//4 ms
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int i, j, maxLen = 1, SIZE = nums.size();
if(!nums.size())
return 0;
vector<int> D(SIZE, 1);
D[1] = nums[0];
D[0] = 0; //此處設定要讓D[0]小於數組裡的所有值,此處的0感覺還是大了,不過居然過了。。巧合吧
for(i = 1; i < SIZE; ++i){
if(nums[i] > D[maxLen])
D[++maxLen] = nums[i];
else
for(j = maxLen; j >= 1; --j){
if(D[j] >= nums[i] && D[j-1] < nums[i]){ //找到陣列D裡第一個剛好小於nums[i]的值,替換之
D[j] = nums[i];
break;
}
}
}
return maxLen;
}
};
注意在該題中,子序列是遞增的,而非非降子序列,所以與我們上面的公式稍微有所不同。我們還要注意裡面的下標,還是依據C裡面下標從0開始這一規則。
接著是二維DP問題,突然回想起LCS不就是個二維DP解法嗎。。
可以用 leetcode 上 Unique Path I & II練手:
//Unique Path I
//0 ms
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int> > D(m, vector<int>(n));
int i, j;
if((m && n) == 0) //防止原矩陣為空
return 0;
for(i = 0; i < m; ++i)
D[i][0] = 1;
for(i = 0; i < n; ++i)
D[0][i] = 1;
for(i = 1; i < m; ++i)
for(j = 1; j < n; ++j)
D[i][j] = D[i - 1][j] + D[i][j - 1];
return D[m-1][n-1];
}
};
//Unique Path II
//4 ms
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
if((m && n) == 0)
return 0;
int i, j;
vector<vector<int> > D(m, vector<int>(n, 0));
for(i = 0; i < m; ++i)
if(!obstacleGrid[i][0])
D[i][0] = 1;
else
break;
for(i = 0; i < n; ++i)
if(!obstacleGrid[0][i])
D[0][i] = 1;
else
break;
for(i = 1; i < m; ++i)
for(j = 1; j < n; ++j)
if(!obstacleGrid[i][j])
D[i][j] = D[i - 1][j] + D[i][j - 1];
return D[m - 1][n - 1];
}
};
然後是帶有額外條件的DP問題:題解巧妙地使用陣列下標,立刻讓我們對問題的解清晰不少,這其實顯示瞭解題者挖掘出了題目中的一些性質。
題目:
無向圖G有N個結點,它的邊上帶有正的權重值。
你從結點1開始走,並且一開始的時候你身上帶有M元錢。如果你經過結點i, 那麼你就要花掉S[i]元(可以把這想象為收過路費)。如果你沒有足夠的錢, 就不能從那個結點經過。在這樣的限制條件下,找到從結點1到結點N的最短路徑。 或者輸出該路徑不存在。如果存在多條最短路徑,那麼輸出花錢數量最少的那條。 限制:1<N<=100 ; 0<=M<=100 ; 對於每個i,0<=S[i]<=100;正如我們所看到的, 如果沒有額外的限制條件(在結點處要收費,費用不足還不給過),那麼, 這個問題就和經典的迪傑斯特拉問題一樣了(找到兩結點間的最短路徑)。 在經典的迪傑斯特拉問題中, 我們使用一個一維陣列來儲存從開始結點到每個結點的最短路徑的長度, 即M[i]表示從開始結點到結點i的最短路徑的長度。然而在這個問題中, 我們還要儲存我們身上剩餘多少錢這個資訊。因此,很自然的, 我們將一維陣列擴充套件為二維陣列。
題解:
我們用M[i][j]表示從開始結點到結點i的最短路徑長度, 且剩餘j元。通過這種方式,我們將這個問題規約到原始的路徑尋找問題。 在每一步中,對於已經找到的最短路徑,我們找到它所能到達的下一個未標記狀態(i,j), 將它標記為已訪問(之後不再訪問這個結點),並且在能到達這個結點的各個最短路徑中, 找到加上當前邊權重值後最小值對應的路徑,即為該結點的最短路徑。 (寫起來真是繞,建議畫個圖就會明瞭很多)。不斷重複上面的步驟, 直到所有的結點都訪問到為止(這裡的訪問並不是要求我們要經過它, 比如有個結點收費很高,你沒有足夠的錢去經過它,但你已經訪問過它) 最後Min[N-1][j]中的最小值即是問題的答案(如果有多個最小值, 即有多條最短路徑,那麼選擇j最大的那條路徑,即,使你剩餘錢數最多的最短路徑)。
虛擬碼:
讀著挺繞的,要試著走一遍過程,我花了好幾個小時。。。其實過程和 Dijkstra 演算法還是蠻相似的,在每一階段的狀態變化還是服從距離最短這一要點,同時保證錢的限制,所以增加了"剩餘錢量"那一維度,所以為了能夠在路徑一樣短時可以比較不同路徑所所花的錢,我們把判斷是否 visited 過的物件從點變成了 Min[][] 陣列。注意到與 Dijkstra 演算法的相同點與不同點即可。
不過,感覺此題的複雜度貌似不低。
最後是高階DP,它高在我們要仔細揣摩題目,將其規約為DP問題。
終於看完,收穫很多。DP演算法核心就在於將一個大問題的分成一個一個的階段,即一個一個小問題(狀態),然後試著解決它(狀態轉移方程)。狀態與狀態轉移方程缺一不可。如果看起來是個DP問題,但你卻無法定義出狀態, 那麼試著將問題規約到一個已知的DP問題。此外,我們還看出了DP演算法與BFS,貪心演算法的聯絡與區別。
相關推薦
【DP演算法篇之初學】LIS\LCS\二維DP\帶條件DP
最近參加2016華為軟體精英挑戰賽,題目也比較直接,就是求過定點的最短路。這題和以前練得不一樣,感覺是不是要用DP(動態規劃)。可是對於DP演算法,我還是啥都不懂,於是好好補補。 看完入門,有點
【java面試】演算法篇之堆排序
一、堆的概念 堆是一棵順序儲存的完全二叉樹。完全二叉樹中所有非終端節點的值均不大於(或不小於)其左、右孩子節點的值。 其中每個節點的值小於等於其左、右孩子的值,這樣的堆稱為小根堆; 其中每個節點的值大
【Docker篇之三】Docker鏡像實戰
mkdir 不同的 專用 文件目錄 main safe 地方 host 運行 構建Nginx鏡像 創建Nginx鏡像創建專用目錄 cd /optmkdir nginxcd nginxvim Dockerfile FROM centos //基於基礎鏡像為cen
【再回首Python之美】【矩陣】求矩陣中最大元素/最小元素的行列座標 For 層次聚類演算法Hierarchical Clustering Alg
求多維矩陣中最小元素的行列座標,這個在層次聚類演算法中用到,這裡實現記錄一下。1.簡介矩陣M: [[1 3 2] [2 6 0] [9 8 5]]最大元素是9,對應的行列座標為(2,0)最小元素是
【新年第二篇金融科技】申卡與還款之第二篇
世界 bug 例如 強烈 更新 特點 旅行 中間 民生銀行 《申卡與還款》 還款原則1.不能異名還款(不能用張三的儲蓄卡給李四信用卡還款,這種刷卡和消費都是別人幫你還款的銀行會監控到這樣的卡會
【新年第一篇金融科技】征信報告揭秘50問之第一篇
能力 卡機 數據源 現實生活 主體 存在 變更 的人 萬塊 《征信報告揭秘50問》 前言:大家對征信這個詞肯定不會陌生,但大家對這個詞的由來可能就不是了解的很清晰。其實這兩個字已經有2000多年的歷史,中國
【機器學習系列之七】模型調優與模型融合(程式碼應用篇)
這是本人對模型的融合的程式碼合集,環境是python3,只要複製過去就可以用了,非常方便。 目錄 1.交叉驗證 1.1 原理 1.2 GridSearchCV 2.繪製學習曲線 3.stacking 3.1 stacking原理 3.2 程式碼實現不
【SSH進階之路】Hibernate映射——一對一單向關聯映射(五)
技術 iyu 標識 tails for sso 3.0 sdn 例如 【SSH進階之路】Hibernate基本原理(一) ,小編介紹了Hibernate的基本原理以及它的核心,採用對象化的思維操作關系型數據庫。 【SSH進階之路】Hibernate搭建開發環境+簡單實例
【SSH進階之路】Struts + Spring + Hibernate 進階開端(一)
height 一段 ioc 效率 陽光大道 面向對象的思想 text ase 們的 Long Long ago。就聽說過SSH。起初還以為是一個東東,詳細內容更是不詳,總認為高端大氣上檔次,經過學習之後才發現,不不過高大上,更是低調奢華有內涵,經過一段時間的
【SSH進階之路】Hibernate基本映射(三)
tor res 主動 tran clas oid 支持包 lose 包括 【SSH進階之路】Hibernate基本原理(一) ,小編介紹了Hibernate的基本原理以及它的核心。採用對象化的思維操作關系型數據庫。 【SSH進階之路】Hibernate搭建開發環境+簡單
【SSH進階之路】Struts基本原理 + 實現簡單登錄(二)
target doctype 掌握 pack insert enter snippet file manage 上面博文,主要簡單的介紹了一下SSH的基本概念,比較宏觀。作為剛開始學習的人可以有一個總體上的認識,個人覺得對學習有非常好的輔助功能,它不不過
【Nutch基礎教程之七】Nutch的2種執行模式:local及deploy
mapred nap ont nal servlet miss mos ant issue 在對nutch源碼執行ant runtime後,會創建一個runtime的文件夾。在runtime文件夾下有deploy和local 2個文件夾。 [[email
【輕松前端之旅】HTML的塊元素、行內元素和空元素
mod charts 內聯元素 學習編程 https -s 網址 tip htm 塊(block)元素 顯示成一塊,前後有換行。塊元素常用於web頁面的主要構造模塊。例如:<div>,<p>,<h1>~<h6>,<bl
【WEB前端系列之CSS】CSS3動畫之Tranition
transform log 值變化 mozilla 大眾 適應 int 處理 中一 前言 css中的transition允許css的屬性值在一定的時間區間內平滑的過渡。這種效果可以在鼠標點擊、獲得焦點、被點擊或對元素任何改變中觸發,並圓滑的以動畫效果改變CSS的屬性值。語法
【輕松前端之旅】CSS盒子模型
webp 技術分享 activity 屬性 概念 type title border eight 盒子模型,也叫框模型,在CSS裏是很重要的概念。 每個元素都可以看做一個盒子。盒子包含四個部分:外邊距(margin)、邊框(border)、內邊距(padding)
【OCR技術系列之三】大批量生成文字訓練集
9.png false per store else value 隨機 %d alt 放假了,終於可以繼續可以靜下心寫一寫OCR方面的東西。上次談到文字的切割,今天打算總結一下我們怎麽得到用於訓練的文字數據集。如果是想訓練一個手寫體識別的模型,用一些前人收集好的手寫文字集就
【OCR技術系列之四】基於深度學習的文字識別(3755個漢字)
架構 indices 編碼 協調器 論文 準備 分享 深度 ast 上一篇提到文字數據集的合成,現在我們手頭上已經得到了3755個漢字(一級字庫)的印刷體圖像數據集,我們可以利用它們進行接下來的3755個漢字的識別系統的搭建。用深度學習做文字識別,用的網絡當然是CNN,那具
【校招面試 之 C++】第1題 為什麽優先使用構造函數的初始化列表
初始化 校招 操作 struct st2 使用 mage div 賦值運算 1.首先看一個例子: #include<iostream> using namespace std; class Test1 { public: Test1() // 無參
【校招面試 之 C++】第4題 拷貝構造函數被調用的3個時機
舉例 inf 操作符 -c 接收 clu 分享圖片 his 校招 1、被調用的3個時機: (1)直接初始化或拷貝初始化; (2)將一個對象作為一個實參傳遞,形參采用非指針或非引用的對象進行接收時; (3)函數的返回值是一個非指針或者非對象被接收時。 2、舉例說明: #
【java代碼之美】---Java8 Stream
如果 姓名 有用 補充 ref term 聚合操作 art 觸發 Stream 第一次看到Stream表達式就深深把我吸引,用它可以使你的代碼更加整潔而且對集合的操作效率也會大大提高,如果你還沒有用到java8的Stream特性,那就說明你確實out啦。 一、概述 1