Leetcode 第137場周賽解題報告
今天的比賽的題目相對來說比較「直白」,不像前幾周都是一些特定的演算法,如果你沒學過不可能想出來。
做了這些周,對leetcode比賽的題目也發現了一些「規律」。 一般前兩道題都很「簡單」,只要有想法,直接敲程式碼就能解出來。更多考察的是結果是否正確,速度其次。
後兩道題有些難度 ,不同場次難度不一樣,也可能和不同人的水平感受不同。但是肯定比前兩道要難。
一般在做後兩道題的時候,只要複雜度是對的,一些細節也不用考慮太多。例如陣列開的空間大小,一些線性的提前剪枝判斷,寫不寫都可以過。最主要的是複雜度是同一個量級的。
相信leetcode這麼設計是為了「人性化」,讓選手更關注比賽題目核心,能夠在一個半小時內完成比賽題目。
總之leetcode的比賽還是很人性化,很注重主要考點,不糾結於細節。利用這些特性,可以在比賽中排除一些錯誤想法。
下面是詳細的題解和思考。
比賽的地址 Weekly Contest 137
https://leetcode-cn.com/contest/weekly-contest-137
1. 最後一塊石頭的重量
題目:
最後一塊石頭的重量(Last Stone Weight)
地址:
https://leetcode-cn.com/contest/weekly-contest-137/problems/last-stone-weight/
題意:
有一堆石頭,每塊石頭的重量都是正整數。
每一回合,從中選出兩塊最重的石頭,然後將它們一起粉碎。假設石頭的重量分別為 x 和 y,且 x <= y。那麼粉碎的可能結果如下:
如果 x == y,那麼兩塊石頭都會被完全粉碎;
如果 x != y,那麼重量為 x 的石頭將會完全粉碎,而重量為 y 的石頭新重量為 y-x。
最後,最多隻會剩下一塊石頭。返回此石頭的重量。如果沒有石頭剩下,就返回 0。
思路:
一個數組,每次把最大的兩個數拿出來相減,然後把絕對值放回原陣列。一直重複到最後只剩下一個元素,輸出即可。
典型的模擬題,按照題目的意思寫即可。可以用堆來實現,每次拿堆頂的兩個最大元素。
由於是第一題,每次都排序一遍,也能通過。不過在日常工程中,還是老老實實用堆來實現吧。
class Solution { public: int lastStoneWeight(vector<int>& stones) { priority_queue< int > q; for(auto &stone : stones) { q.push(stone); } while(q.size()>1) { int x = q.top(); q.pop(); int y = q.top(); q.pop(); int z = abs(x-y); q.push(z); } return q.top(); } };
2. 刪除字串中的所有相鄰重複項
題目:
刪除字串中的所有相鄰重複項(Remove All Adjacent Duplicates In String)
地址:
https://leetcode-cn.com/contest/weekly-contest-137/problems/remove-all-adjacent-duplicates-in-string/
題意:
給出由小寫字母組成的字串 S,重複項刪除操作會選擇兩個相鄰且相同的字母,並刪除它們。
在 S 上反覆執行重複項刪除操作,直到無法繼續刪除。
在完成所有重複項刪除操作後返回最終的字串。答案保證唯一。
思路:
類似於遊戲「愛消除」,相同的兩個字母抵消掉,形成的新字串再接著抵消,直到穩定為止。
用棧來實現,遍歷字串的每個字元。如果棧為空,則插入字元,否則比較字元和棧頂元素,相同則彈出棧頂元素,不同則壓棧。
最後輸出棧內的字串即可。
程式碼:
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;
for(auto ch : S)
{
if(st.empty())
{
st.push(ch);
}
else
{
if(st.top()==ch)
{
st.pop();
}
else
{
st.push(ch);
}
}
}
string res;
while(!st.empty())
{
res.push_back(st.top());
st.pop();
}
reverse(res.begin(), res.end());
return res;
}
};
3. 最長字串鏈
題目:
最長字串鏈(Longest String Chain)
地址:
https://leetcode-cn.com/contest/weekly-contest-137/problems/longest-string-chain/
題意:
給出一個單詞列表,其中每個單詞都由小寫英文字母組成。
如果我們可以在 word1 的任何地方新增一個字母使其變成 word2,那麼我們認為 word1 是 word2 的前身。例如,"abc" 是 "abac" 的前身。
詞鏈是單詞 [word_1, word_2, ..., word_k] 組成的序列,k >= 1,其中 word_1 是 word_2 的前身,word_2 是 word_3 的前身,依此類推。
從給定單詞列表 words 中選擇單片語成詞鏈,返回詞鏈的最長可能長度。
思路:
這道題本質是圖演算法。
分兩步解:
第一步先構造出每個單詞之間的關係,判斷任意兩個單詞是為前身後繼關係。構造完關係就能畫出了圖。
第二步就是求解這個圖中最長路徑。由於是單向有向圖,而且沒有環。
構造一個集合,每次給集合放入新的點A,都判斷集合中其他的點到該點的距離,取最大值為集合內部到新點A的最大距離L。下次再加入新的點A1,如果A和A1連通,則集合到A1的距離為L+1。
由於終點有多個,最後要遍歷所有點的最長距離。
其實這道題的思想和Dijkstra演算法是一樣的。
程式碼:
class Solution {
public:
bool canChange(string& s1, string& s2)
{
int len1 = s1.length();
int len2 = s2.length();
if(len1+1!=len2)
return false;
int i=0;
int j=0;
while(j<len2)
{
if(s1[i]==s2[j])
{
++i;
++j;
}
else
{
++j;
if(j-i>1)
{
return false;
}
}
}
return true;
}
int longestStrChain(vector<string>& words) {
int n = words.size();
vector<vector<int>> g(n, vector<int>(n, 0));
sort(words.begin(), words.end(), [](string& w1, string& w2)
{
return w1.length()<w2.length();
}
);
for(int i = 0; i < n; ++i)
{
for(int j = i+1; j < n; ++j)
{
if(canChange(words[i], words[j]))
{
g[i][j] = 1;
}
}
}
vector<int> lcnt(n, 1);
for(int i=0;i<n;++i)
{
for(int j=0;j<i;++j)
{
if(g[j][i])
{
int tmp = lcnt[j]+1;
lcnt[i] = max(tmp, lcnt[i]);
}
}
}
return *max_element(lcnt.begin(), lcnt.end());
}
};
4. 最後一塊石頭的重量 II
題目:
最後一塊石頭的重量 II(Last Stone Weight II)
地址:
https://leetcode-cn.com/contest/weekly-contest-137/problems/last-stone-weight-ii/
題意:
有一堆石頭,每塊石頭的重量都是正整數。
每一回合,從中選出任意兩塊石頭,然後將它們一起粉碎。假設石頭的重量分別為 x 和 y,且 x <= y。那麼粉碎的可能結果如下:
如果 x == y,那麼兩塊石頭都會被完全粉碎;
如果 x != y,那麼重量為 x 的石頭將會完全粉碎,而重量為 y 的石頭新重量為 y-x。
最後,最多隻會剩下一塊石頭。返回此石頭最小的可能重量。如果沒有石頭剩下,就返回 0。
思路:
和第一題的題意只有一句差別,就是每次拿石頭是「任意」的。問最後能消掉剩餘的最小值是多少。
一般最開始可能想到用貪心,但實際上沒有這種演算法的。
由於石頭碎掉之後還能放回去,類似於把石頭分成兩堆來看。只要依次拿兩堆的石頭互相粉碎,最後剩下的就是最小整數。
最多有100個石頭,每個石頭最多300的重量。所以兩個集合最大的差值不會超過30000。
用陣列構造結果。
在加入第n個石頭重量為m時,查詢n-1個石頭能夠組成的兩堆石頭的差值的絕對值為diff。
該石頭兩個選擇,放入多的堆,則差值更大,為diff+m;
放入小的堆,則差值為|diff-m|。這時更新n個石頭能組成的所有重量。
最後輸出最後一個石頭能組成的最小重量即可。
程式碼:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int diff[101][30001]={0};
int sum = 0;
int n = stones.size();
for(int i=0;i<n;++i)
{
sum+=stones[i];
}
diff[0][stones[0]] = 1;
for(int i=1;i<n;++i)
{
for(int j=0;j<=sum;++j)
{
if(diff[i-1][j])
{
diff[i][j+stones[i]] = 1;
diff[i][abs(j-stones[i])] = 1;
}
}
}
for(int i = 0; i <= sum; ++i)
{
if(diff[n-1][i])
{
return i;
}
}
return 0;
}
};