自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

徹底理解動(dòng)態(tài)規(guī)劃:編輯距離

開發(fā) 前端
如果word1與word2的第一個(gè)字符相等,假設(shè)word1是hor、word2是hr,那么我們可以放心的排除掉兩個(gè)字符串的第一個(gè)字符,即EditDistance("hor", "hr")一定等于EditDistance("or", "r")。

大家好,我是小風(fēng)哥。

這是動(dòng)態(tài)規(guī)劃主題的第三篇,本篇的題目非常經(jīng)典,幾乎是面試必備,即,編輯距離問題,edit distance;

給定兩個(gè)字符串word1以及word2,返回將word1轉(zhuǎn)為word2需要的最少步驟,在每一步中你可以針對(duì)字符串word1進(jìn)行以下操作:

  • 新增一個(gè)字符
  • 刪除一個(gè)字符
  • 替換一個(gè)字符

假如word1是"horse",word2是“ros”,那么你的程序需要返回3,也就是說將word1轉(zhuǎn)為word2至少需要三個(gè)步驟:

  1. 將word1中的第一個(gè)字符h替換為字符r:horse -> rorse,此時(shí)word1變?yōu)閞orse,word1與word2前兩個(gè)字符相等
  2. 將word1中的第三個(gè)字符r刪掉:rorse -> rose,此時(shí)word1變?yōu)閞ose,word1與word2的前三個(gè)字符相等
  3. 將word1中的最后一個(gè)字符刪掉:rose -> ros,此時(shí)word1與word2相等。

想一想該怎樣用動(dòng)態(tài)規(guī)劃解決這個(gè)問題。

選擇與子問題

和之前的題目一樣,你首先應(yīng)該找出子問題是什么,子問題與原始問題的依賴關(guān)系是什么。

找出子問題的關(guān)鍵在于每一步的選擇。

如果word1與word2的第一個(gè)字符相等,假設(shè)word1是hor、word2是hr,那么我們可以放心的排除掉兩個(gè)字符串的第一個(gè)字符,即EditDistance("hor", "hr")一定等于EditDistance("or", "r"):

圖片

此時(shí)我們得到了一個(gè)子問題EditDistance("or", "r"),原始問題EditDistance("hor", "hr")的值等于該子問題。

真正有趣的是如果word1與word2的第一個(gè)字符不相等的情況,假設(shè)word1為“hor”,而word2為“ro”,此時(shí)根據(jù)該問題的規(guī)則針對(duì)word1的第一個(gè)字符有三種操作:

1,在word1的第一個(gè)字符前新增(Insert)一個(gè)字符r,此時(shí)word1變?yōu)閞hor,由于此時(shí)word1 的第一個(gè)字符等于word2的第一個(gè)字符,可以放心的忽略掉,因此我們得到了子問題EditDistance("hor","o"),由于執(zhí)行了一次新增操作,因此:

EditDistance("hor","ro") = EditDistance("hor","o") + 1

2,將word1的第一個(gè)字符刪掉(Delete),此時(shí)word1變?yōu)椤皁r”,我們得到了一個(gè)新的子問題EditDistance("or","ro"),由于執(zhí)行了一次刪除操作,因此:

EditDistance("hor","ro") = EditDistance("or","ro") + 1

3,將word1的第一個(gè)字符替換(Replace )為r,此時(shí)word1變?yōu)榱恕皉or”,由于word1的第一個(gè)字符等于word2的第一個(gè)字符,因此可以放心的忽略掉,我們得到了一個(gè)新的子問題EditDistance("or","o"),由于執(zhí)行了一次刪除操作,因此:

EditDistance("hor","ro") = EditDistance("or","o") + 1

根據(jù)題目要求,我們需要得到最小的編輯距離,因此:

EditDistance("hor","ro") = min(EditDistance("hor","o"),
EditDistance("or","ro"),
EditDistance("or","o")) + 1

即:

圖片

可以看到,如果word1與word2的第一個(gè)字符如果不相等的話那么我們會(huì)得到三個(gè)子問題,取這三個(gè)子問題的最小值然后加1就是原始問題的解。

現(xiàn)在我們找到了子問題與原始問題之間的依賴關(guān)系。

實(shí)際上,根據(jù)上述討論我們還可以進(jìn)一步擴(kuò)展從而得到完整的狀態(tài)空間樹。

圖片

從這棵樹中可以看到最小的編輯距離是2。

現(xiàn)在你應(yīng)該清楚的知道該怎樣我們是怎樣一步步將問題不斷的分解為更小的子問題,然后利用子問題的解來得到原始問題的解了。

自頂向下遞歸代碼

上圖中每個(gè)方框都是一個(gè)子問題,決定一個(gè)子問題的因素在于word1與word2當(dāng)前處理到了哪個(gè)位置,假設(shè)對(duì)word1處理到了第i個(gè)位置,對(duì)word2處理到了第j個(gè)位置,因此我們可以對(duì)問題進(jìn)行定義:

int EditDistance(int i, int j);

該函數(shù)表示從i到word1的末尾形成的字符串與從j從word2的末尾形成的字符串的編輯距離。

因此如果調(diào)用該函數(shù)時(shí)我們應(yīng)該這樣使用:

EditDistance(0, 0);

有了該定義與上述分析,你可以輕而易舉的寫出這樣的遞歸代碼:

string word1;
string word2;

int EditDistance(int i, int j) {
if (i == word1.length() && j == word2.length()) return 0;
if (i == word1.length()) return word2.length() - j;
if (j == word2.length()) return word1.length() - i;

if (word1[i] == word2[j]) return EditDistance(i + 1, j + 1);
else {
return min(EditDistance(i + 1, j + 1), min(
EditDistance(i, j + 1),
EditDistance(i + 1, j))) + 1;
}
}

我們將word1與word2聲明為全局變量,這樣你可以清楚的看到?jīng)Q定EditDistance函數(shù)值的因素只有這兩個(gè)參數(shù)i和j,i的取值為[0, word1.length()],j的取值為[0, word2.length()],也就是說子問題的個(gè)數(shù)只有(word1.length() + 1) * (word2.length() + 1) 個(gè),上述遞歸代碼存在大量重復(fù)計(jì)算問題,因此可以通過增加cache進(jìn)行優(yōu)化,這個(gè)改動(dòng)就留給大家啦。

接下來我們著手將自頂向下的遞歸代碼改為自底向上的動(dòng)態(tài)規(guī)劃代碼。


自底向上動(dòng)態(tài)規(guī)劃代碼

由于子問題的個(gè)數(shù)只有(word1.length() + 1) * (word2.length() + 1) 個(gè),因此可以定義一個(gè)相同大小的二維數(shù)組dp:

vector<vector<int>>dp(word1.length() + 1, vector<int>(word2.length() + 1, 0));

接下來我們要求解最小子問題,最小子問題就是上述遞歸代碼的遞歸出口:

if (i == word1.length() && j == word2.length()) return 0;

該最小子問題的解包含在了dp數(shù)組的初始化中。

接下來的子問題是另外兩個(gè)遞歸出口:

if (i == word1.length()) return word2.length() - j;
if (j == word2.length()) return word1.length() - i;

我們可以簡(jiǎn)單的構(gòu)造出兩種情況下的所有i和j來初始化數(shù)組dp,即:

for (int j = word2.length() - 1; j >= 0; j--)
dp[word1.length()][j] = word2.length() - j;
for (int i = word1.length() - 1; i >= 0; i--)
dp[i][word2.length()] = word1.length() - i;

最后我們利用兩個(gè)for循環(huán)來構(gòu)造出所有的i和j,從而將遞歸函數(shù)的最后一部分:

if (word1[i] == word2[j]) return EditDistance(i + 1, j + 1);
else {
return min(EditDistance(i + 1, j + 1), min(
EditDistance(i, j + 1),
EditDistance(i + 1, j))) + 1;
}

放置在for循環(huán)中,并將對(duì)遞歸函數(shù)的調(diào)用替換為對(duì)數(shù)組dp的讀寫:

for (int i = word1.length() - 1; i >= 0; i--) {
for (int j = word2.length() - 1; j >= 0; j--) {
if (word1[i] == word2[j])
dp[i][j] = dp[i + 1][j + 1];
else
dp[i][j] = min(dp[i + 1][j + 1], min(dp[i][j + 1], dp[i + 1][j])) + 1;
}
}

最終,完整的動(dòng)態(tài)規(guī)劃代碼為:

int minDistance(string word1, string word2) {
vector<vector<int>>dp(word1.length() + 1, vector<int>(word2.length() + 1, 0));
for (int j = word2.length() - 1; j >= 0; j--)
dp[word1.length()][j] = word2.length() - j;
for (int i = word1.length() - 1; i >= 0; i--)
dp[i][word2.length()] = word1.length() - i;
for (int i = word1.length() - 1; i >= 0; i--) {
for (int j = word2.length() - 1; j >= 0; j--) {
if (word1[i] == word2[j])
dp[i][j] = dp[i + 1][j + 1];
else
dp[i][j] = min(dp[i + 1][j + 1], min(dp[i][j + 1], dp[i + 1][j])) + 1;
}
}

return dp[0][0];
}


責(zé)任編輯:武曉燕 來源: 碼農(nóng)的荒島求生
相關(guān)推薦

2022-12-29 08:12:51

動(dòng)態(tài)規(guī)劃profit

2022-12-11 10:37:15

動(dòng)態(tài)規(guī)劃字符串超序列

2021-09-06 06:31:40

理解動(dòng)態(tài)規(guī)劃

2025-02-13 09:06:27

2021-05-13 08:55:33

Android架構(gòu)功能

2019-11-07 10:37:36

CookieSessionToken

2019-06-11 14:45:25

2019-01-09 08:31:07

2020-03-03 14:15:49

Redis持久化數(shù)據(jù)庫(kù)

2024-03-15 08:23:26

異步編程函數(shù)

2019-12-10 13:55:10

Go指針存儲(chǔ)

2022-10-24 08:08:27

閉包編譯器

2023-12-28 10:39:57

數(shù)組節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)

2021-12-27 09:33:12

內(nèi)存泄漏程序

2021-10-15 09:53:12

工具

2024-04-29 08:15:07

I/OCPU密集型

2022-02-28 11:10:42

ZGCG1收集器

2021-12-06 11:19:47

語言指針內(nèi)存

2023-10-27 11:21:20

C語言Multics語言

2022-08-16 09:03:01

JavaScript前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)