[Leetcode Weekly Contest]207
链接:LeetCode
[Leetcode]1592. 重新排列单词间的空格
给你一个字符串 text ,该字符串由若干被空格包围的单词组成。每个单词由一个或者多个小写英文字母组成,并且两个单词之间至少存在一个空格。题目测试用例保证 text 至少包含一个单词 。请你重新排列空格,使每对相邻单词之间的空格数目都 相等 ,并尽可能 最大化 该数目。如果不能重新平均分配所有空格,请 将多余的空格放置在字符串末尾 ,这也意味着返回的字符串应当与原 text 字符串的长度相等。
返回 重新排列空格后的字符串 。
以单词分割,统计单词和空格个数即可。
class Solution {
public:
vector<string> split(const string &text,char stop){
vector<string> res;
string t;
for(auto ch:text){
if(ch==stop){
if(t!="") res.push_back(t);
t = "";
}
else{
t += ch;
}
}
if(t!="") res.push_back(t);
return res;
}
string reorderSpaces(string text) {
vector<string> split_res = split(text,' ');
int n = text.size();
int m = split_res.size();
int blank = 0;
for(auto ch : text) blank += ch==' ' ;
if(m==1){
string res = split_res[0];
for(int i=0;i<blank;++i){
res += ' ';
}
return res;
}
int space_join = (int)blank/(m-1);
int space_back = blank%(m-1);
string res;
for(int i=0;i<m-1;i++){
res += split_res[i];
for(int j=0;j<space_join;++j){
res += ' ';
}
}
res += split_res[m-1];
for(int i=0;i<space_back;++i){
res += ' ';
}
return res;
}
};
[Leetcode]1593. 拆分字符串使唯一子字符串的数目最大
给你一个字符串 s ,请你拆分该字符串,并返回拆分后唯一子字符串的最大数目。
字符串 s 拆分后可以得到若干 非空子字符串 ,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是 唯一的 。
注意:子字符串 是字符串中的一个连续字符序列。
根据题意DFS暴力即可。
class Solution {
public:
int res_max = 0;
unordered_set<string> set;
int maxUniqueSplit(string s) {
dfs(s);
return res_max;
}
void dfs(string &s){
if(s==""){
res_max = max(res_max,int(set.size()));
}
if(s.size()+set.size()<res_max) return;
for(int i=1;i<=s.size();++i){
string tmp = s.substr(0,i);
string rest = s.substr(i);
if(set.find(tmp)==set.end()){
set.insert(tmp);
dfs(rest);
set.erase(tmp);
}
}
}
};
[Leetcode]1594. 矩阵的最大非负积
给你一个大小为 rows x cols 的矩阵 grid 。最初,你位于左上角 (0, 0) ,每一步,你可以在矩阵中 向右 或 向下 移动。在从左上角 (0, 0) 开始到右下角 (rows - 1, cols - 1) 结束的所有路径中,找出具有 最大非负积 的路径。路径的积是沿路径访问的单元格中所有整数的乘积。
返回 最大非负积 对 109 + 7 取余 的结果。如果最大积为负数,则返回 -1 。
注意,取余是在得到最大积之后执行的。
动态规划。设置两个DP数组分别记录路劲最大值dp1和路径最小值dp2,即递推过程:
注意初始化:for(int i = 1 ; i < row; i++) dp1[i][0] =dp1[i-1][0] * grid[i][0];dp2同理
class Solution {
public:
int maxProductPath(vector<vector<int>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<vector<long long>>dp1(row,vector<long long>(col));
vector<vector<long long>>dp2(row,vector<long long>(col));
dp1[0][0] = grid[0][0];
dp2[0][0] = grid[0][0];
for(int i = 1 ; i < row; i++){
dp1[i][0] =dp1[i-1][0] * grid[i][0];
dp2[i][0] =dp2[i-1][0] * grid[i][0];
}
for(int i = 1 ; i < col; i++){
dp1[0][i] =dp1[0][i-1] * grid[0][i];
dp2[0][i] =dp2[0][i-1] * grid[0][i];
}
for(int i = 1; i < grid.size();i++){
for(int j = 1; j < grid[0].size();j++){
dp1[i][j]=max({dp1[i-1][j]*grid[i][j], dp1[i][j-1]*grid[i][j],dp2[i-1][j]*grid[i][j], dp2[i][j-1]*grid[i][j]});
dp2[i][j]=min({dp1[i-1][j]*grid[i][j], dp1[i][j-1]*grid[i][j],dp2[i-1][j]*grid[i][j], dp2[i][j-1]*grid[i][j]});
}
}
if(dp1[row-1][col-1] < 0) return -1;
else return dp1[row-1][col-1]%1000000007;
}
};
[Leetcode]1595. 连通两组点的最小成本
给你两组点,其中第一组中有 size1 个点,第二组中有 size2 个点,且 size1 >= size2 。
任意两点间的连接成本 cost 由大小为 size1 x size2 矩阵给出,其中 cost[i][j] 是第一组中的点 i 和第二组中的点 j 的连接成本。如果两个组中的每个点都与另一组中的一个或多个点连接,则称这两组点是连通的。换言之,第一组中的每个点必须至少与第二组中的一个点连接,且第二组中的每个点必须至少与第一组中的一个点连接。
返回连通两组点所需的最小成本。
状压DP,或者回溯+贪心+剪枝。问题等价于: 在一个矩阵中选取一些值, 满足矩阵的每一行和每一列都至少有一个元素被选中, 同时选中元素的总和最小 (此矩阵就是 cost 矩阵).
对于DP,由于矩阵的列数较少, 我们可以用状压 DP 来表示每一行的选取情况, 假设矩阵有 \(m\) 行 \(n\) 列, 那么我们维护一个 DP 矩阵 dp[m][1 << n], dp[i][j]表示当前选取到第 \(i\) 行, 每列的选取状况为 \(j\) 时总的最小开销, 其中 \(j\) 的第 \(k\) 位为 \(1\) 即表示第 \(k\) 列已经被选取过了. 那么状态转移方程为
其中 costMatrix[i][j] 表示第\(i\)行选取状况为\(j\)时该行被选取得元素总和.
另外,可采用贪心加剪枝的方法。贪心,就是在每一行优先选取比较小的代价去进行dfs回溯,那些比较大的代价有可能会被剪枝剪掉。剪枝,如果某条路径当前的代价和已经超过了目前找到的最优代价和,就及时回退。
class Solution {
public:
int connectTwoGroups(vector<vector<int>> &cost) {
int size1 = cost.size(), size2 = cost[0].size(), stateNum = 1 << size2; //stateNum为第二组总的状态数+1
vector<int> dp(stateNum, INT_MAX); //dp数组初始化为很大的数
dp[0] = 0; //初始状态
for (int i = 0; i < size1; ++i) { //迭代每一行
vector<int> temp(stateNum, INT_MAX); //滚动数组
for (int state = 0; state < stateNum; ++state) { //枚举所有状态
if (dp[state] == INT_MAX) continue; //若状态不可达,continue
for (int j = 0; j < size2; ++j) { //方案一:任选一条边相连
int nextState = state | (1 << j); //相连后到达的状态
temp[nextState] = min(temp[nextState], dp[state] + cost[i][j]);//更新最小花费
}
int flipState = (stateNum - 1) ^ state; //方案二:连接若干未连接的边,使用异或进行位反转得到所有未连接的边
for (int subState = flipState; subState; subState = flipState & (subState - 1)) {//枚举未连接的边的子集
int sum = 0; //记录花费
for (int k = 0; k < size2; ++k) //枚举size2
if (subState & (1 << k)) sum += cost[i][k]; //若子集中存在该边,则更新花费
int nextState = state | subState; //相连后到达的状态
temp[nextState] = min(temp[nextState], dp[state] + sum); //更新最小花费
}
}
dp = move(temp);//滚动数组
}
return dp.back();//返回结果
}
};
下面是回溯+贪心+剪枝的方法。
class Solution {
int m,n;
int ans=2147483647;
int row_chosen[12]={0};
int mincost_of_row[12];
int v[12][12];
int cost[12][12];
public:
inline void fun(int j,int curcost){
//curcost和rest总是在一起以和的形式出现,所以干脆把他俩合并成curcost
//逐列进行深搜
for(int k=0;k<m;k++){
int i=v[j][k];
if(!row_chosen[i]){
curcost-=mincost_of_row[i];
row_chosen[i]++;
if(curcost+cost[i][j]<ans){
//在这里进行剪枝
if(j+1==n)ans=curcost+cost[i][j];
else fun(j+1,curcost+cost[i][j]);
}
row_chosen[i]--;
curcost+=mincost_of_row[i];
}
else{
row_chosen[i]++;
if(curcost+cost[i][j]<ans){
//在这里进行剪枝
if(j+1==n)ans=curcost+cost[i][j];
else fun(j+1,curcost+cost[i][j]);
}
row_chosen[i]--;
}
}
}
int connectTwoGroups(vector<vector<int>>& _cost) {
m=_cost.size();
n=_cost[0].size();
for(int i=0;i<m;i++)memcpy(cost[i],&_cost[i][0],4*n);
//将vector<vector<int>>数据送入int[][]
int temp[m];
for(int i=0;i<m;i++)temp[i]=i;
for(int j=0;j<n;j++){
memcpy(v[j],temp,sizeof(temp));
sort(v[j],v[j]+m,[&](int x,int y)->bool{return cost[x][j]<cost[y][j];});
}
//用int[]替换vector<int>
int Sum=0;
for(int i=0;i<m;i++){
mincost_of_row[i]=*min_element(cost[i],cost[i]+n);
Sum+=mincost_of_row[i];
}
fun(0,Sum);
//预处理完成后,进行深搜回溯
return ans;
}
};