题目描述
给定一个字符串S和一个正整数N。你需要从S中删除N个字符,使得删除后的字符串字典序最小。
解题思路
1. 贪心算法
通过观察题目,可以发现,字典序最小的字符串肯定是先删除比较大的字符,即从左到右依次删除大的字符,因为左边的字符对于字典序的影响更大。
对于每次删除,可以从左往右找,找到第一个比右边字符大的字符删除。特别地,当字符串长度为N时,删除所有字符即可。
以下是具体实现的伪代码:
while(count < N)
{
int i = 0;
while(i < S.size()-1 && S[i] <= S[i+1])
{
i++;
}
S.erase(i, 1);
count++;
}
2. 动态规划
一个字符串的子问题可以由该字符串中的子串推导出来。我们可以从删除一个字符的情况考虑,然后推导出删除两个字符、三个字符、……、N个字符的情况。
状态转移方程为:
$$dp[i][j]=\min\{dp[i-1][k]+S[i]|j\leq k
$dp[i][j]$表示对于字符串的前$i$个字符,删除$j$个字符后得到的最小字典序值,$dp[i-1][k]$表示字符串的前$i-1$个字符,删除$k$个字符后得到的最小字典序值,$S[i]$表示第$i$个字符。
以下是具体实现的伪代码:
// 初始化dp数组
for(int i=0; i<=S.size(); i++)
{
for(int j=0; j<=N; j++)
{
dp[i][j] = INF;
}
}
dp[0][0] = "";
// 计算dp数组
for(int i=1; i<=S.size(); i++)
{
for(int j=0; j<=N; j++)
{
if(j > i)
{
continue;
}
for(int k=j; k<i; k++)
{
string temp = dp[k][j];
temp += S[i-1];
if(temp < dp[i][j])
{
dp[i][j] = temp;
}
}
}
}
string result = dp[S.size()][N];
代码实现
1. 贪心算法
string removeCharacters(string S, int N)
{
int count = 0;
while(count < N)
{
int i = 0;
while(i < S.size()-1 && S[i] <= S[i+1])
{
i++;
}
S.erase(i, 1);
count++;
}
return S;
}
2. 动态规划
const int MAXN = 1010;
const string INF = "z";
string dp[MAXN][MAXN];
string removeCharacters(string S, int N)
{
for(int i=0; i<=S.size(); i++)
{
for(int j=0; j<=N; j++)
{
dp[i][j] = INF;
}
}
dp[0][0] = "";
for(int i=1; i<=S.size(); i++)
{
for(int j=0; j<=N; j++)
{
if(j > i)
{
continue;
}
for(int k=j; k<i; k++)
{
string temp = dp[k][j];
temp += S[i-1];
if(temp < dp[i][j])
{
dp[i][j] = temp;
}
}
}
}
string result = dp[S.size()][N];
if(result == INF)
{
return "";
}
return result;
}
总结
本题可以通过贪心算法和动态规划算法来求解。
贪心算法通过每次删除比较大的字符来得到字典序最小的字符串,时间复杂度为$O(N^2)$。
动态规划算法通过推导出子串的情况来求解整个字符串的最小字典序值,时间复杂度为$O(N^3)$。
在实际编写代码时,要注意对于字符串为空的情况的特判,以及对于字符串和N相等的情况直接返回空字符串的处理。