8月19日-8月25日 字符串算法总结

115 阅读3分钟

本周的算法学习集中在字符串处理上,以最长回文子序列、字符串的编辑距离和正则表达式匹配这三个问题为例。这些问题不仅锻炼了我对字符串操作的技巧,也是算法竞赛和面试中的热点。

最长回文子序列

最长回文子序列问题要求找出给定字符串中最长的回文子序列。动态规划是解决这个问题的关键。

  1. 状态定义dp[i][j] 表示从索引 i 到 j 的子串中最长回文子序列的长度。
  2. 状态转移:如果 s[i] == s[j]dp[i][j] = dp[i + 1][j - 1] + 2;否则,dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
  3. 初始化dp[i][i] = 1,因为单个字符自身是一个长度为1的回文子序列。
  4. 结果dp[0][n - 1] 存储了整个字符串的最长回文子序列长度。

C++ 代码实现

#include <vector> using namespace std; 
int longestPalindromeSubseq(const string &s) { int n = s.length(); 
vector<vector<int>> dp(n, vector<int>(n, 0)); 
for (int i = n - 1; i >= 0; --i) { dp[i][i] = 1; 
for (int j = i + 1; j < n; ++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][n - 1]; 
  } 

字符串的编辑距离

关键思路

编辑距离问题,又称Levenshtein距离,是计算两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。

  1. 状态定义dp[i][j] 表示将字符串 str1 的前 i 个字符转换为 str2 的前 j 个字符所需的最少操作数。
  2. 状态转移dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (str1[i - 1] != str2[j - 1]))
  3. 初始化dp[0][j] = jdp[i][0] = i

C++ 代码实现

#include <vector> using namespace std; 
int minDistance(const string &str1, const string &str2) { 
    int m = str1.length(), n = str2.length(); 
    vector<vector<int>> dp(m + 1, vector<int>(n + 1)); 
    for (int i = 0; i <= m; ++i) 
        dp[i][0] = i; 
    for (int j = 0; j <= n; ++j) 
        dp[0][j] = j; 
    for (int i = 1; i <= m; ++i) { 
        for (int j = 1; j <= n; ++j) { 
            if (str1[i - 1] == str2[j - 1]) { 
                dp[i][j] = dp[i - 1][j - 1]; 
            } else { 
                dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1; 
            }
       } 
    } 
    return dp[m][n]; 
    } // 其他函数实现...`

正则表达式匹配

关键思路

正则表达式匹配是判断一个字符串是否符合某种模式。

  1. 递归方法:将问题分解为子问题,考虑字符匹配和 * 的两种情况。
  2. 贪心匹配:尽可能多地匹配字符。
  3. 回溯:当贪心匹配失败时,回溯到上一个状态。

C++ 代码实现

#include <vector> using namespace std;
bool isMatch(const string &s, const string &p) { 
        vector<vector<bool>> dp(s.length() + 1, vector<bool>(p.length() + 1, false)); 
        dp[0][0] = true; 
        for (int j = 1; j <= p.length(); ++j) { 
            if (p[j - 1] == '*') { 
                dp[0][j] = dp[0][j - 2]; 
                } 
       } 
       for (int i = 1; i <= s.length(); ++i) { 
           for (int j = 1; j <= p.length(); ++j) { 
               if (s[i - 1] == p[j - 1] || p[j - 1] == '.') { 
                   dp[i][j] = dp[i - 1][j - 1]; 
               } else if (p[j - 1] == '*') { 
                   dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.')); 
                   } 
          } 
   } 
   return dp[s.length()][p.length()]; 
   }

结语

通过本周对字符串问题的深入学习,了解到处理字符串问题,同样可以用之前的动态规划和递归等算法来解决。这些算法不仅提高了编程能力,也为解决实际问题提供了强大的工具。