Rank
又翻车了…
A. 重新排列单词间的空格
模拟.
class Solution {
public:
string reorderSpaces(string text) {
int len=text.size();
int cnt_word=0,cnt_blank=0;
vector<string> words;
for(int i=0;i<len;){
if(text[i]==' '){
++cnt_blank;
++i;
continue;
}
cnt_word+=1;
int j=i+1;
while(j<len && text[j]!=' ') ++j;
words.push_back(text.substr(i,j-i));
i=j;
}
string ans="";
if(cnt_word==1){
ans+=words[0];
while(ans.size()<len) ans+=" ";
return ans;
}
cnt_blank/=(cnt_word-1);
for(int i=0;i<cnt_word;++i){
ans+=words[i];
if(i+1<cnt_word){
for(int j=0;j<cnt_blank;++j) ans+=" ";
}
}
while(ans.size()<len) ans+=" ";
return ans;
}
};
B. 拆分字符串使唯一子字符串的数目最大
暴力,每个字符与下一个字符可以拆分,也可以不拆分.
class Solution {
public:
int len,ans;
string str;
vector<string> v;
void dfs(int start,int pos){
if(pos==len){
int sz=v.size();
bool flag=true;
for(int i=0;i<sz;++i){
for(int j=i+1;j<sz;++j){
if(v[i]==v[j]) flag=false;
}
}
if(flag) ans=max(ans,(int)v.size());
return;
}
dfs(start,pos+1);
string s=str.substr(start,pos-start+1);
v.push_back(s);
dfs(pos+1,pos+1);
v.pop_back();
}
int maxUniqueSplit(string s) {
str=s;
len=s.length();
dfs(0,0);
return ans;
}
};
C. 矩阵的最大非负积
线性 dp.
dp[i][j][0]
表示从 (0,0)
出发走到(i,j)
的最大非负积,dp[i][j][1]
表示从 (0,0)
出发走到 (i,j)
的最小负数积,然后根据 g[i][j]
的正负号进行分类讨论,最终答案为 dp[n-1][m-1][0]
,初始状态为 dp[0][0][0]
和 dp[0][0][1]
.
class Solution {
public:
int maxProductPath(vector<vector<int>>& grid) {
int n=grid.size(),m=grid[0].size();
long long dp[n][m][2];
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
if(!i && !j){
if(grid[i][j]>=0){
dp[i][j][0]=grid[i][j];
dp[i][j][1]=1;
}
else{
dp[i][j][0]=-1;
dp[i][j][1]=grid[i][j];
}
continue;
}
dp[i][j][0]=-1;
dp[i][j][1]=1;
if(grid[i][j]>=0){
if(i-1>=0){
dp[i][j][0]=max(dp[i][j][0],dp[i-1][j][0]*grid[i][j]);
dp[i][j][1]=min(dp[i][j][1],dp[i-1][j][1]*grid[i][j]);
}
if(j-1>=0){
dp[i][j][0]=max(dp[i][j][0],dp[i][j-1][0]*grid[i][j]);
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]*grid[i][j]);
}
}
else{
if(i-1>=0){
dp[i][j][0]=max(dp[i][j][0],dp[i-1][j][1]*grid[i][j]);
dp[i][j][1]=min(dp[i][j][1],dp[i-1][j][0]*grid[i][j]);
}
if(j-1>=0){
dp[i][j][0]=max(dp[i][j][0],dp[i][j-1][1]*grid[i][j]);
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]*grid[i][j]);
}
}
}
}
long long ans=dp[n-1][m-1][0];
if(ans<0) return -1;
return ans%1000000007;
}
};
D. 连通两组点的最小成本
状压dp. 原问题等价于在 cost
矩阵中选取若干元素,满足矩阵的每一行和每一列都至少有一个元素被选中,同时选中元素的总和最小.
对cost
矩阵的列进行状态压缩,dp[i][j]
表示 [0...i]
行与状态 j
对应的列进行连通的最小成本,则状态转移式为.
d
p
[
i
]
[
j
∣
k
]
=
m
a
x
(
d
p
[
i
]
[
j
∣
k
]
,
d
p
[
i
−
1
]
[
j
]
+
g
[
i
]
[
k
]
)
dp[i][j|k]=max(dp[i][j|k],dp[i-1][j]+g[i][k])
dp[i][j∣k]=max(dp[i][j∣k],dp[i−1][j]+g[i][k])
其中 k
也是压缩状态,上式考虑的是第 i
行元素的选择情况,第 i
行选择状态 k
对应的列,相应的价值之和为 g[i][k]
,数组 g
可以通过预处理得到,最终答案为 dp[n-1][(1<<m)-1]
,初始状态为 dp[0]
.
注意在枚举状态时,从1开始而不是从0开始,因为0表示空集(没有元素被选中),是不合法状态.
有一处可以优化的地方:不必枚举 k
的所有情况,可以分别枚举第 i
行只选择 1 列的情况,以及第 i
行选择若干不在状态 j
中列的情况(因为在状态 j
中的列已经满足要求了,多选择一个元素一定会使得结果更大).
另外还可以通过滚动数组优化空间,将 dp
数组变成一维. 为了便于理解,下面的代码并没有用滚动数组优化.
class Solution {
public:
const static int inf=1e9;
int connectTwoGroups(vector<vector<int>>& cost) {
int n=cost.size(),m=cost[0].size();
int dp[n][1<<m],g[n][(1<<m)];
for(int i=0;i<n;++i){
for(int j=0;j<(1<<m);++j){
g[i][j]=0;
for(int k=0;k<m;++k){
if(j>>k&1) g[i][j]+=cost[i][k];
}
}
}
for(int i=0;i<n;++i){
for(int j=0;j<(1<<m);++j){
if(!i) dp[i][j]=g[i][j];
else dp[i][j]=inf;
}
}
for(int i=1;i<n;++i){
for(int j=1;j<(1<<m);++j){
for(int k=0;k<m;++k){
dp[i][j|(1<<k)]=min(dp[i][j|(1<<k)],dp[i-1][j]+cost[i][k]);
}
int tot=(1<<m)-1-j;
for(int k=tot;k>=1;k=tot&(k-1)){
dp[i][j|k]=min(dp[i][j|k],dp[i-1][j]+g[i][k]);
}
}
}
return dp[n-1][(1<<m)-1];
}
};
学到了一个新知识点:枚举一个 n
位二进制数 x
对应的所有子集,比如 4 位二进制数 x=1010
,那么 x
的所有子集为 1010
,0010
,1000
,0000
. 代码可以这样实现.
for(int y=x;y>=0;y=x&(y-1)){ //对y进行操作 }