1. 程式人生 > >Leetcode 第137場周賽解題報告

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;
    }
};