[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,即遞推過程:
\[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]}); \]注意初始化: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;
}
};