寫在前面
週末兩天leetcode比賽做題整理。
歡迎大家訪問我的個人博客網站:flamsteed
雙週賽21
5336. 上升下降字符串
給你一個字符串 s ,請你根據下面的算法重新構造字符串:
從 s 中選出 最小 的字符,將它 接在 結果字符串的後面。
從 s 剩餘字符中選出 最小 的字符,且該字符比上一個添加的字符大,將它 接在 結果字符串後面。
重複步驟 2 ,直到你沒法從 s 中選擇字符。
從 s 中選出 最大 的字符,將它 接在 結果字符串的後面。
從 s 剩餘字符中選出 最大 的字符,且該字符比上一個添加的字符小,將它 接在 結果字符串後面。
重複步驟 5 ,直到你沒法從 s 中選擇字符。
重複步驟 1 到 6 ,直到 s 中所有字符都已經被選過。
在任何一步中,如果最小或者最大字符不止一個 ,你可以選擇其中任意一個,並將其添加到結果字符串。
請你返回將 s 中字符重新排序後的 結果字符串 。
示例 1:
輸入:s = “aaaabbbbcccc”
輸出:“abccbaabccba”
解釋:第一輪的步驟 1,2,3 後,結果字符串爲 result = “abc”
第一輪的步驟 4,5,6 後,結果字符串爲 result = “abccba”
第一輪結束,現在 s = “aabbcc” ,我們再次回到步驟 1
第二輪的步驟 1,2,3 後,結果字符串爲 result = “abccbaabc”
第二輪的步驟 4,5,6 後,結果字符串爲 result = “abccbaabccba”
解法一:比賽的時候沒想太多,用了模擬,很蠢,而且代碼量很大。
代碼:
class Solution {
public:
int getMinidx(string& s){
char c = s[0];
int p = 0;
for(int i=1; i<s.length(); i++){
if(c>s[i]){
c = s[i];
p = i;
}
}
return p;
}
int getMaxidx(string& s){
char c = s[0];
int p = 0;
for(int i=1; i<s.length(); i++){
if(c<s[i]){
c = s[i];
p = i;
}
}
return p;
}
char getMin(string& s){
char c = s[0];
for(int i=1; i<s.length(); i++){
if(c>s[i])
c = s[i];
}
return c;
}
char getMax(string& s){
char c = s[0];
for(int i=1; i<s.length(); i++){
if(c<s[i])
c = s[i];
}
return c;
}
string sortString(string s) {
string res = "";
if(s.empty()) return res;
while(true){
//1
char t = getMin(s);
int p = getMinidx(s);
res = res + t;
s.erase(p,1);
if(s.empty()) break;
bool k = true;
while(k){//2 3
k = false;
char m = s[0];
int pp = 0;
for(int i=1; i<s.length(); i++){
if(s[i]>res[res.length()-1]){
k = true;
m = s[i];
pp = i;
break;
}
}
if(k==false) break;
for(int i=0; i<s.length(); i++){
if(s[i]>res[res.length()-1] && s[i]<m){
m = s[i];
pp = i;
}
}
res = res + m;
s.erase(pp,1);
if(s.empty()){
break;
}
}
if(s.empty()) break;
//4
t = getMax(s);
p = getMaxidx(s);
res = res + t;
s.erase(p,1);
if(s.empty()) break;
k = true;
while(k){//5 6
k = false;
char m = s[0];
int pp = 0;
for(int i=1; i<s.length(); i++){
if(s[i]<res[res.length()-1]){
k = true;
m = s[i];
pp = i;
break;
}
}
if(k==false) break;
for(int i=0; i<s.length(); i++){//5
if((s[i]<res[res.length()-1]) && (s[i]>m)){
m = s[i];
pp = i;
}
}
res = res + m;
s.erase(pp,1);
if(s.empty()){
break;
}
}
if(s.empty()){
break;
}
}
return res;
}
};
解法二:用26位的數組存放字母個數,然後從前往後,從後往前加減,答案就出來了,比模擬不知道好到哪裏去了。
代碼:
class Solution {
public:
string sortString(string s) {
if (s.empty()) return s;
vector<int> a(26,0);
for(int i=0; i<s.length(); i++){
a[int(s[i]-'a')]++;
}
string res = "";
do{
for(int i=0; i<26; i++){//1 2 3
if(a[i]>0){
res = res + char(i+int('a'));
a[i]--;
}
}
for(int i=25; i>=0; i--){//4 5 6
if(a[i]>0){
res = res + char(i+int('a'));
a[i]--;
}
}
}while(res.length()<s.length());
return res;
}
};
5337. 每個元音包含偶數次的最長子字符串
給你一個字符串 s ,請你返回滿足以下條件的最長子字符串的長度:每個元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出現了偶數次。
示例 1:
輸入:s = “eleetminicoworoep”
輸出:13
解釋:最長子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 個,以及 0 個 a,u 。
提示:
1 <= s.length <= 5 x 10^5
s 只包含小寫英文字母。
解法:n^2暴搜肯定過不了,超過十萬位了,所以就不嘗試了,這題我是一點思路都沒有,看了題解還是一頭霧水,大部分解法是用bit mask,位操作解法都是非常巧,但是很難想。一共五個元音,2^5=32種狀態,用32個整數表示就完事了,然後相同的狀態重複出現,就是出現了答案,比較出最大值就行了。(太巧秒了orz)
代碼:
class Solution {
public:
int findTheLongestSubstring(string s) {
vector<int> pre(32,INT_MAX);
pre[0]=-1;
const int N=s.size();
int cur=0;
int ans=0;
for(int i=0;i<N;++i){
switch(s[i]){//狀態壓縮
case 'a':cur^=1;break;
case 'e':cur^=2;break;
case 'i':cur^=4;break;
case 'o':cur^=8;break;
case 'u':cur^=16;break;
default:break;
}
if(pre[cur]==INT_MAX) pre[cur]=i;//未記錄過就記錄位置
else ans=max(ans,i-pre[cur]);//統計答案
}
return ans;
}
};
5338. 二叉樹中的最長交錯路徑
給你一棵以 root 爲根的二叉樹,二叉樹中的交錯路徑定義如下:
選擇二叉樹中 任意 節點和一個方向(左或者右)。
如果前進方向爲右,那麼移動到當前節點的的右子節點,否則移動到它的左子節點。
改變前進方向:左變右或者右變左。
重複第二步和第三步,直到你在樹中無法繼續移動。
交錯路徑的長度定義爲:訪問過的節點數目 - 1(單個節點的路徑長度爲 0 )。
請你返回給定樹中最長 交錯路徑 的長度。
解法:數據範圍是最大50000個點,遞歸肯定爆,然後我還是寫了一個遞歸,然後超時了,其實遞歸稍微改一下就行了,自頂向下遍歷二叉樹,方向是對的就上層深度+1,不對就從1開始重新計算,這樣就不會超時了。
代碼:
class Solution {
public:
int res = 0;
void helper(TreeNode* root, int k, int d){
if(root==NULL) return;
res = max(res,d);
if(k){
helper(root->left,0,d+1);
helper(root->right,1,1);
}
else{
helper(root->left ,0,1);
helper(root->right,1,d+1);
}
}
int longestZigZag(TreeNode* root) {
if(root==NULL) return 0;
helper(root,0,0);
helper(root,1,0);
return res;
}
};
5339. 二叉搜索子樹的最大鍵值和
給你一棵以 root 爲根的 二叉樹 ,請你返回 任意 二叉搜索子樹的最大鍵值和。
二叉搜索樹的定義如下:
任意節點的左子樹中的鍵值都小於此節點的鍵值。
任意節點的右子樹中的鍵值都大於此節點的鍵值。
任意節點的左子樹和右子樹都是二叉搜索樹。
解法:比賽的時候想都沒想,其實和上一題大同小異,這裏用vector作爲函數範圍值值得我學習,這樣dfs確實很方便,vector包含四個值{是否是搜索樹,子樹最小值,子樹最大值,當前的鍵值和},然後通過最大值和最小值來和當前的根比較,判斷是否是搜索樹,再統計和,就完事了。
代碼:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int res = 0;
vector<int> dfs(TreeNode* root){
if(root==NULL) return {true, INT_MAX, INT_MIN, 0};
auto lt = dfs(root->left);
auto rt = dfs(root->right);
int p = root->val;
if(!lt[0] || !rt[0] || p<=lt[2] || p>=rt[1])
return {false,0,0,0};
int sum(0),currmin,currmax;
currmin = root->left ? root->left->val : root->val;
currmax = root->right? root->right->val : root->val;
sum += root->val + lt[3] + rt[3];
res = max(res,sum);
return {true, currmin, currmax, sum};
}
int maxSumBST(TreeNode* root) {
auto k = dfs(root);
return res;
}
};
周賽 179
5352. 生成每種字符都是奇數個的字符串
給你一個整數 n,請你返回一個含 n 個字符的字符串,其中每種字符在該字符串中都恰好出現 奇數次 。
返回的字符串必須只含小寫英文字母。如果存在多個滿足題目要求的字符串,則返回其中任意一個即可。
解法:全部填a,偶數的話最後一位填b。
代碼:
class Solution {
public:
string generateTheString(int n) {
string s = "";
if(n==0) return s;
if(n%2==1){
for(int i=1; i<=n; i++)
s = s + 'a';
}
else{
for(int i=1; i<n; i++)
s = s + 'a';
s = s + 'b';
}
return s;
}
};
5353. 燈泡開關 III
房間中有 n 枚燈泡,編號從 1 到 n,自左向右排成一排。最初,所有的燈都是關着的。
在 k 時刻( k 的取值範圍是 0 到 n - 1),我們打開 light[k] 這個燈。
燈的顏色要想 變成藍色 就必須同時滿足下面兩個條件:
燈處於打開狀態。
排在它之前(左側)的所有燈也都處於打開狀態。
請返回能夠讓所有開着的燈都變成藍色的時刻數目 。
解法:先用模擬做了一遍,然後爆了,然後細細一想,其實藍色燈的意思就是:第i個時間,1~i的燈都開了,這時候纔是全藍,求和判斷就完事了。
代碼:
class Solution {
public:
int numTimesAllBlue(vector<int>& light) {
if(light.empty()) return 0;
int res = 0;
int l = light.size();
long s = 0;
for(int i=0; i<l; i++){
s += light[i];
if(s == (long(1+i+1))*(long(i+1))/2) res++;
}
return res;
}
};
5355. T 秒後青蛙的位置
給你一棵由 n 個頂點組成的無向樹,頂點編號從 1 到 n。青蛙從 頂點 1 開始起跳。規則如下:
在一秒內,青蛙從它所在的當前頂點跳到另一個未訪問過的頂點(如果它們直接相連)。
青蛙無法跳回已經訪問過的頂點。
如果青蛙可以跳到多個不同頂點,那麼它跳到其中任意一個頂點上的機率都相同。
如果青蛙不能跳到任何未訪問過的頂點上,那麼它每次跳躍都會停留在原地。
無向樹的邊用數組 edges 描述,其中 edges[i] = [fromi, toi] 意味着存在一條直接連通 fromi 和 toi 兩個頂點的邊。
返回青蛙在 t 秒後位於目標頂點 target 上的概率。
解法:先從target倒着跳,求出跳到根的跳數,同時記錄每次跳到的位置,和時間比較一下,如果時間不夠就輸出0,
因爲青蛙是死腦筋,所以如果target不是葉節點,且時間大於跳數,那青蛙還是不會停在target,輸出0。
完成以上判斷之後,剩下的情況都能跳到,求概率就行了,這時候遍歷之前記錄的位置,統計這些位置的子樹個數,然後計算概率就行了。
代碼:
class Solution {
public:
double frogPosition(int n, vector<vector<int>>& edges, int t, int target) {
if(edges.empty()){
if(t==0) return 0.0;
if(target==1) return 1.0;
return 0.0;
}
int d = 0;
int l = 0;
int r = target;
vector<int> path;
for(int i=0; i<edges.size(); i++){
if(edges[i][0]>edges[i][1]){
int t = edges[i][0];
edges[i][0] = edges[i][1];
edges[i][1] = t;
}
}
bool f = true;
if(target==1){
f = true;
for(int i=0; i<edges.size(); i++){
if(edges[i][0]==target){
f = false;
break;
}
}
if(!f && t>0) return 0.0;
return 1.0;
}
while(l!=1 && f){
f = false;
for(int i=0; i<edges.size(); i++){
if(edges[i][1]==r){
l = edges[i][0];
r = l;
path.push_back(l);
d++;
f = true;
break;
}
}
}
if(t<d || !f) {
if(edges[0][0]==target) return 1.0;
return 0.0;
}
else if(t>d){
f = true;
for(int i=0; i<edges.size(); i++){
if(edges[i][0]==target){
f = false;
break;
}
}
if(!f) return 0.0;
}
vector<int> num;
for(int i=0; i<path.size(); i++){
int s = 0;
for(int j=0; j<edges.size(); j++){
if(edges[j][0]==path[i]){
s++;
}
}
num.push_back(s);
}
double res = 1.0;
for(int i=0; i<num.size(); i++){
res = res * (1.0)/double(num[i]);
}
return res;
}
};
5354. 通知所有員工所需的時間
公司裏有 n 名員工,每個員工的 ID 都是獨一無二的,編號從 0 到 n - 1。公司的總負責人通過 headID 進行標識。
在 manager 數組中,每個員工都有一個直屬負責人,其中 manager[i] 是第 i 名員工的直屬負責人。對於總負責人,manager[headID] = -1。題目保證從屬關係可以用樹結構顯示。
公司總負責人想要向公司所有員工通告一條緊急消息。他將會首先通知他的直屬下屬們,然後由這些下屬通知他們的下屬,直到所有的員工都得知這條緊急消息。
第 i 名員工需要 informTime[i] 分鐘來通知它的所有直屬下屬(也就是說在 informTime[i] 分鐘後,他的所有直屬下屬都可以開始傳播這一消息)。
返回通知所有員工這一緊急消息所需要的 分鐘數 。
解法:因爲每一層的不同領導通知時間會不一樣,只有時間最長的纔是答案,所以用dfs是最合適的,考慮到有40000個點,直接搜肯定爆(然後我還是直接搜了一次),其實只需要做一點點優化就行了,提前用二維數組存下每個領導的手下,在dfs內直接遍歷對應的下屬數組就行了。
代碼:
class Solution {
public:
int res = 0;
void dfs(int headID, vector<vector<int>>& m, vector<int>& a, int t){
if(m[headID].empty()){//到達最下層員工,返回答案。
res = max(res,t);
return;
}
t += a[headID];
for(int i=0; i<m[headID].size(); i++){
dfs(m[headID][i],m,a,t);
}
}
int numOfMinutes(int n, int headID, vector<int>& manager, vector<int>& informTime) {
vector<vector<int>> m(informTime.size()+1,vector<int>());
for(int i=0; i<manager.size(); i++){//預處理領導的下屬。
if(manager[i]!=-1)
m[manager[i]].push_back(i);
}
dfs(headID,m,informTime,0);
return res;
}
};