C++力扣題目 647--回文子串 516--最長回文子序列

647. 回文子串

力扣題目鏈接(opens new window)

給定一個字符串,你的任務是計算這個字符串中有多少個回文子串。

具有不同開始位置或結束位置的子串,即使是由相同的字符組成,也會被視作不同的子串。

示例 1:

  • 輸入:"abc"
  • 輸出:3
  • 解釋:三個回文子串: "a", "b", "c"

示例 2:

  • 輸入:"aaa"
  • 輸出:6
  • 解釋:6個回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:輸入的字符串長度不會超過 1000 。

#思路

#暴力解法

兩層for循環,遍歷區間起始位置和終止位置,然后還需要一層遍歷判斷這個區間是不是回文。所以時間復雜度:O(n^3)

#動態規劃

動規五部曲:

  1. 確定dp數組(dp table)以及下標的含義

如果大家做了很多這種子序列相關的題目,在定義dp數組的時候 很自然就會想題目求什么,我們就如何定義dp數組。

絕大多數題目確實是這樣,不過本題如果我們定義,dp[i] 為 下標i結尾的字符串有 dp[i]個回文串的話,我們會發現很難找到遞歸關系。

dp[i] 和 dp[i-1] ,dp[i + 1] 看上去都沒啥關系。

所以我們要看回文串的性質。 如圖:

我們在判斷字符串S是否是回文,那么如果我們知道 s[1],s[2],s[3] 這個子串是回文的,那么只需要比較 s[0]和s[4]這兩個元素是否相同,如果相同的話,這個字符串s 就是回文串。

那么此時我們是不是能找到一種遞歸關系,也就是判斷一個子字符串(字符串的下表范圍[i,j])是否回文,依賴于,子字符串(下表范圍[i + 1, j - 1])) 是否是回文。

所以為了明確這種遞歸關系,我們的dp數組是要定義成一位二維dp數組。

布爾類型的dp[i][j]:表示區間范圍[i,j] (注意是左閉右閉)的子串是否是回文子串,如果是dp[i][j]為true,否則為false。

  1. 確定遞推公式

在確定遞推公式時,就要分析如下幾種情況。

整體上是兩種,就是s[i]與s[j]相等,s[i]與s[j]不相等這兩種。

當s[i]與s[j]不相等,那沒啥好說的了,dp[i][j]一定是false。

當s[i]與s[j]相等時,這就復雜一些了,有如下三種情況

  • 情況一:下標i 與 j相同,同一個字符例如a,當然是回文子串
  • 情況二:下標i 與 j相差為1,例如aa,也是回文子串
  • 情況三:下標:i 與 j相差大于1的時候,例如cabac,此時s[i]與s[j]已經相同了,我們看i到j區間是不是回文子串就看aba是不是回文就可以了,那么aba的區間就是 i+1 與 j-1區間,這個區間是不是回文就看dp[i + 1][j - 1]是否為true。

以上三種情況分析完了,那么遞歸公式如下:

if (s[i] == s[j]) {if (j - i <= 1) { // 情況一 和 情況二result++;dp[i][j] = true;} else if (dp[i + 1][j - 1]) { // 情況三result++;dp[i][j] = true;}
}

result就是統計回文子串的數量。

注意這里我沒有列出當s[i]與s[j]不相等的時候,因為在下面dp[i][j]初始化的時候,就初始為false。

  1. dp數組如何初始化

dp[i][j]可以初始化為true么? 當然不行,怎能剛開始就全都匹配上了。

所以dp[i][j]初始化為false。

  1. 確定遍歷順序

遍歷順序可有有點講究了。

首先從遞推公式中可以看出,情況三是根據dp[i + 1][j - 1]是否為true,在對dp[i][j]進行賦值true的。

dp[i + 1][j - 1] 在 dp[i][j]的左下角,如圖:

647.回文子串

如果這矩陣是從上到下,從左到右遍歷,那么會用到沒有計算過的dp[i + 1][j - 1],也就是根據不確定是不是回文的區間[i+1,j-1],來判斷了[i,j]是不是回文,那結果一定是不對的。

所以一定要從下到上,從左到右遍歷,這樣保證dp[i + 1][j - 1]都是經過計算的

有的代碼實現是優先遍歷列,然后遍歷行,其實也是一個道理,都是為了保證dp[i + 1][j - 1]都是經過計算的。

代碼如下:

for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍歷順序for (int j = i; j < s.size(); j++) {if (s[i] == s[j]) {if (j - i <= 1) { // 情況一 和 情況二result++;dp[i][j] = true;} else if (dp[i + 1][j - 1]) { // 情況三result++;dp[i][j] = true;}}}
}

  1. 舉例推導dp數組

舉例,輸入:"aaa",dp[i][j]狀態如下:

647.回文子串1

圖中有6個true,所以就是有6個回文子串。

注意因為dp[i][j]的定義,所以j一定是大于等于i的,那么在填充dp[i][j]的時候一定是只填充右上半部分

以上分析完畢,C++代碼如下:

class Solution {
public:int countSubstrings(string s) {vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));int result = 0;for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍歷順序for (int j = i; j < s.size(); j++) {if (s[i] == s[j]) {if (j - i <= 1) { // 情況一 和 情況二result++;dp[i][j] = true;} else if (dp[i + 1][j - 1]) { // 情況三result++;dp[i][j] = true;}}}}return result;}
};

以上代碼是為了凸顯情況一二三,當然是可以簡潔一下的,如下:

class Solution {
public:int countSubstrings(string s) {vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));int result = 0;for (int i = s.size() - 1; i >= 0; i--) {for (int j = i; j < s.size(); j++) {if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) {result++;dp[i][j] = true;}}}return result;}
};

  • 時間復雜度:O(n^2)
  • 空間復雜度:O(n^2)

#雙指針法

動態規劃的空間復雜度是偏高的,我們再看一下雙指針法。

首先確定回文串,就是找中心然后向兩邊擴散看是不是對稱的就可以了。

在遍歷中心點的時候,要注意中心點有兩種情況

一個元素可以作為中心點,兩個元素也可以作為中心點。

那么有人同學問了,三個元素還可以做中心點呢。其實三個元素就可以由一個元素左右添加元素得到,四個元素則可以由兩個元素左右添加元素得到。

所以我們在計算的時候,要注意一個元素為中心點和兩個元素為中心點的情況。

這兩種情況可以放在一起計算,但分別計算思路更清晰,我傾向于分別計算,代碼如下:

class Solution {
public:int countSubstrings(string s) {int result = 0;for (int i = 0; i < s.size(); i++) {result += extend(s, i, i, s.size()); // 以i為中心result += extend(s, i, i + 1, s.size()); // 以i和i+1為中心}return result;}int extend(const string& s, int i, int j, int n) {int res = 0;while (i >= 0 && j < n && s[i] == s[j]) {i--;j++;res++;}return res;}
};

  • 時間復雜度:O(n^2)
  • 空間復雜度:O(1)

?

516.最長回文子序列

力扣題目鏈接(opens new window)

給定一個字符串 s ,找到其中最長的回文子序列,并返回該序列的長度。可以假設 s 的最大長度為 1000 。

示例 1: 輸入: "bbbab" 輸出: 4 一個可能的最長回文子序列為 "bbbb"。

示例 2: 輸入:"cbbd" 輸出: 2 一個可能的最長回文子序列為 "bb"。

提示:

  • 1 <= s.length <= 1000
  • s 只包含小寫英文字母

#思路

我們剛剛做過了?動態規劃:回文子串?(opens new window),求的是回文子串,而本題要求的是回文子序列, 要搞清楚這兩者之間的區別。

回文子串是要連續的,回文子序列可不是連續的!?回文子串,回文子序列都是動態規劃經典題目。

回文子串,可以做這兩題:

  • 647.回文子串
  • 5.最長回文子串

思路其實是差不多的,但本題要比求回文子串簡單一點,因為情況少了一點。

動規五部曲分析如下:

  1. 確定dp數組(dp table)以及下標的含義

dp[i][j]:字符串s在[i, j]范圍內最長的回文子序列的長度為dp[i][j]

  1. 確定遞推公式

在判斷回文子串的題目中,關鍵邏輯就是看s[i]與s[j]是否相同。

如果s[i]與s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;

如圖:?

516.最長回文子序列

(如果這里看不懂,回憶一下dp[i][j]的定義)

如果s[i]與s[j]不相同,說明s[i]和s[j]的同時加入 并不能增加[i,j]區間回文子序列的長度,那么分別加入s[i]、s[j]看看哪一個可以組成最長的回文子序列。

加入s[j]的回文子序列長度為dp[i + 1][j]。

加入s[i]的回文子序列長度為dp[i][j - 1]。

那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

516.最長回文子序列1

代碼如下:

if (s[i] == s[j]) {dp[i][j] = dp[i + 1][j - 1] + 2;
} else {dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}

  1. dp數組如何初始化

首先要考慮當i 和j 相同的情況,從遞推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 遞推公式是計算不到 i 和j相同時候的情況。

所以需要手動初始化一下,當i與j相同,那么dp[i][j]一定是等于1的,即:一個字符的回文子序列長度就是1。

其他情況dp[i][j]初始為0就行,這樣遞推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 中dp[i][j]才不會被初始值覆蓋。

vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for (int i = 0; i < s.size(); i++) dp[i][i] = 1;

  1. 確定遍歷順序

從遞歸公式中,可以看出,dp[i][j] 依賴于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1],如圖:

所以遍歷i的時候一定要從下到上遍歷,這樣才能保證下一行的數據是經過計算的

j的話,可以正常從左向右遍歷。

代碼如下:

for (int i = s.size() - 1; i >= 0; i--) {for (int j = i + 1; j < s.size(); j++) {if (s[i] == s[j]) {dp[i][j] = dp[i + 1][j - 1] + 2;} else {dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);}}
}

  1. 舉例推導dp數組

輸入s:"cbbd" 為例,dp數組狀態如圖:

516.最長回文子序列3

紅色框即:dp[0][s.size() - 1]; 為最終結果。

以上分析完畢,C++代碼如下:

class Solution {
public:int longestPalindromeSubseq(string s) {vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));for (int i = 0; i < s.size(); i++) dp[i][i] = 1;for (int i = s.size() - 1; i >= 0; i--) {for (int j = i + 1; j < s.size(); j++) {if (s[i] == s[j]) {dp[i][j] = dp[i + 1][j - 1] + 2;} else {dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);}}}return dp[0][s.size() - 1];}
};
  • 時間復雜度: O(n^2)
  • 空間復雜度: O(n^2)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/697742.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/697742.shtml
英文地址,請注明出處:http://en.pswp.cn/news/697742.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

面試系列之《Spark》(持續更新...)

參考文檔及示例代碼均基于pyspark3.1.2 1.什么是RDD&#xff1f;2.job、stage、task如何劃分&#xff1f;3.什么是寬窄依賴&#xff1f;4.spark有哪幾種部署模式&#xff1f;5.spark中的算子分為哪些類型&#xff0c;舉例說明。6.cache、persist、checkpoint的區別&#xff0c;…

C++模板為什么不能聲明和定義分離

首先我們要直到C程序運行需要進行的四個階段。 預處理->編譯->匯編->鏈接 編譯&#xff1a;對語法語義分析&#xff0c;分析無誤生成匯編&#xff0c;頭文件不參加編譯&#xff0c;多個源文件是分開單獨編譯的。 鏈接&#xff1a;將多個obj文件鏈接合成一個&#x…

ubuntu20.04安裝webots仿真

ubuntu20.04安裝webots仿真 1.首先: wget -qO- https://cyberbotics.com/Cyberbotics.asc | sudo apt-key add - sudo apt-add-repository deb https://cyberbotics.com/debian/ binary-amd64/ sudo apt-get update sudo apt-get install webots .bashrc中添加環境變量:…

Sora----打破虛實之間的最后一根枷鎖----這扇門的背后是人類文明的晟陽還是最后的余暉

目錄 一.Sora出道即巔峰 二.為何說Sora是該領域的巨頭 三.Sora無敵的背后究竟有怎樣先進的處理技術 1.Spacetime Latent Patches 潛變量時空碎片&#xff0c;建構視覺語言系統 2.擴散模型與Diffusion Transformer&#xff0c;組合成強大的信息提取器 3.DiT應用于潛變量時…

關于在分布式環境中RVN和使用場景的介紹4

簡介 在前面的文檔中&#xff0c;我們介紹了RVN的概念&#xff0c;通過RVN可以解決的某類問題和使用技巧&#xff0c;以及處理RVN的邏輯的具體實現。在本文中&#xff0c;我們將要介紹關于如何使用RVN解決另一種在分布式系統中常出現的問題。 問題 假設我們創建了一個servic…

C語言—自定義(構造)類型

2.20&#xff0c;17.56 1.只有當我們使用結構體類型定義變量/結構體數組,系統才會為結構體的成員分配內存空間,用于存儲對應類型的數據 2.strct 結構體 一起作為結構體類型標識符 嘿嘿暫時先這樣&#xff0c;我會回來改的1、定義一個表示公交線路的結構體&#xff0c;要…

pikachu靶場-CSRF

CSRF: 介紹&#xff1a; Cross-site request forgery簡稱為"CSRF”。 在CSF的攻擊場景中攻擊者會偽造一個請求&#xff08;這個請求一般是一個鏈接&#xff09; 然后欺騙目標用戶進行點擊&#xff0c;用戶一旦點擊了這個請求&#xff0c;整個攻擊也就完成了&#xff0…

VSCode-更改系統默認路徑

修改vscode中的默認擴展路徑&#xff1a;"%USERPROFILE%\.vscode" 打開目錄C:\用戶\電腦用戶名&#xff0c;將.vscode文件剪切至D:\VSCode文件夾下 用管理員身份打開cmd.exe命令界面輸入mklink /D "%USERPROFILE%\.vscode" "D:\VSCode\.vscode\"…

同一個包下 golang run時報undefined

問題描述 今天在運行一個項目&#xff0c;一個包下有兩個文件&#xff0c;分別是main.go和route&#xff0c;main函數在main.go文件中&#xff0c;main引用了route.go中的兩個函數&#xff0c;SetupRoutes和SetupAdminRoutes go build 編譯后&#xff0c;直接運行&#xff0c…

【C++私房菜】面向對象中的簡單繼承

文章目錄 一、 繼承基本概念二、派生類對象及派生類向基類的類型轉換三、繼承中的公有、私有和受保護的訪問控制規則四、派生類的作用域五、繼承中的靜態成員 一、 繼承基本概念 通過繼承&#xff08;inheritance&#xff09;聯系在一起的類構成一種層次關系。通常在層次關系的…

Leetcoder Day17| 二叉樹 part06

語言&#xff1a;Java/C 654.最大二叉樹 給定一個不含重復元素的整數數組。一個以此數組構建的最大二叉樹定義如下&#xff1a; 二叉樹的根是數組中的最大元素。左子樹是通過數組中最大值左邊部分構造出的最大二叉樹。右子樹是通過數組中最大值右邊部分構造出的最大二叉樹。 …

進程間傳遞 SQL 文的方法

SQL 文組成 SQL 文有 2 部分組成&#xff1a; SQL 原型&#xff0c;如&#xff1a;INSERT INTO test1 (id,name) VALUES (?,?)Args &#xff0c;? 號對應的值列表 有時&#xff0c;生成 SQL 文的進程和處理 SQL 文的進程&#xff0c;可能不是同一個 這里就涉及到如何高效…

免費搭建個人網盤

免費搭建一個屬于個人的網盤。 服務端 詳情請參考原網站的服務端下載和安裝虛擬磁盤Fuse4Ui可以支持把網盤內容掛載成系統的分區&#xff1b; 掛載工具效果圖&#xff1a;應用端應用端的下載 效果圖

藍橋杯第1374題——鍛造兵器

題目描述 小明一共有n塊鍛造石&#xff0c;第塊鍛造石的屬性值為ai. 現在小明決定從這n塊鍛造石中任取兩塊來鍛造兵器 通過周密計算&#xff0c;小明得出&#xff0c;只有當兩塊鍛造石的屬性值的差值等于C&#xff0c;兵器才能鍛造成功 請你幫小明算算&#xff0c;他有多少種選…

人工智能幾個關鍵節點:深藍,AlphaGo,ChatGPT,Sora

近30年&#xff0c;人工智能幾個關鍵節點&#xff1a;深藍&#xff0c;AlphaGo&#xff0c;ChatGPT&#xff0c;Sora 深藍&#xff1a; 1997年&#xff0c;深藍擊敗卡斯帕羅夫的比賽是通過一系列復雜的算法和策略實現的。深藍的開發團隊使用了一種名為“暴力搜索”的技術&…

OGG-00918 映射中缺少鍵列 id.

2024-02-23 14:54:49 INFO OGG-02756 從線索文件獲取了表 GISTAR.PXPH_PON_ROUTE 的定義。. The following columns did not default because of type mismatches: id OGG-00918 映射中缺少鍵列 id. 目標端有字段ID&#xff0c;由于mysql自增&#xff0c;所以只能是b…

短劇小程序系統,重塑視頻觀看體驗的科技革命

隨著科技的飛速發展&#xff0c;人們對于數字化內容的消費需求也在不斷增長。在這個大背景下&#xff0c;短劇小程序作為一種新型的視頻觀看方式&#xff0c;正逐漸受到大眾的青睞。本文將探討短劇小程序的發展背景、特點以及市場前景&#xff0c;分析其在重塑視頻觀看體驗方面…

如何使用Inno Setup制作Unity構建程序的Windows安裝程序

1. 準備 &#xff08;1&#xff09;準備好Unity構建的程序集合 必須包括&#xff1a; Data文件夾&#xff08;xxx_Data&#xff09; Mono文件夾&#xff08;MonoBleedingEdge&#xff09; 打包的應用程序文件&#xff08;xxx.exe&#xff09; Unity播放器dll文件&#xff…

SpringBoot+Docker:高效容器化的最佳實踐

首先為什么要使用 Docker&#xff1f; Docker 是一個強大的工具&#xff0c;它允許開發者將他們的應用程序打包到容器中&#xff0c;以便可以在任何平臺上輕松部署和運行。當涉及到對 Spring Boot 應用程序進行 Docker 化時&#xff0c;每個開發人員都應該遵循一些最佳實踐&am…

編程筆記 Golang基礎 017 數據類型:字符串類型

編程筆記 Golang基礎 017 數據類型&#xff1a;字符串類型 一、字符串類型小結 在Go語言中&#xff0c;字符串&#xff08;string&#xff09;是一種基本的數據類型&#xff0c;用于表示文本數據。它是一個不可變的字符序列&#xff0c;由UTF-8編碼的字節組成&#xff0c;支持U…