Leetcode 第136場周賽解題報告

owenandhisfriends發表於2019-05-14

週日的比賽的時候正在外面辦事,沒有參加。賽後看了下題目,幾道題除了表面要考的內容,還是有些能發散擴充套件的地方。

做題目不是最終目的,通過做題發現知識盲區,去研究學習,才能不斷提高。

理論和實際是有關係的,一些題目也都有現實意義。計算機的一些模擬操作,通過數學演算法,能夠大大減輕程式碼量和演算法複雜度。

第一題是機器人在座標系上直走和轉彎,通過簡單的模擬就能實現。但是仔細思考發現還能通過線性代數,座標變換的方式做,這樣在實際中計算更快。甚至還可以用複數來做。

實際掃地機器人可能就用到了類似的演算法。讓他能夠不至於始終原地打轉。

第四題是典型的字尾樹、字尾陣列應用,找字串最長重複子串。在搜尋引擎,或DNA檢測中,都是有實際使用場景的。在70年代就已經有應用了,是一個很經典的演算法。而且在90年代至今,一直有科學家提升建立字尾樹和字尾陣列的時間複雜度。這個演算法也是在不斷髮展的。而且在2016年中國的李志澤,李建和霍紅衛三位科學家提出了線性時間複雜度,常數空間的最優構造演算法。是中國人對演算法的貢獻。

下面是詳細的題解和思考。


比賽的地址 Weekly Contest 136

https://leetcode-cn.com/contest/weekly-contest-136

困於環中的機器人

題目:

困於環中的機器人(Robot Bounded In Circle)

地址:

https://leetcode-cn.com/contest/weekly-contest-136/problems/robot-bounded-in-circle/

題意:

在無限的平面上,機器人最初位於 (0, 0) 處,面朝北方。機器人可以接受下列三條指令之一:

"G":直走 1 個單位
"L":左轉 90 度
"R":右轉 90 度

機器人按順序執行指令 instructions,並一直重複它們。

只有在平面中存在環使得機器人永遠無法離開時,返回 true。否則,返回 false。

思路:

假設機器人重複走N次指令後,面朝北:

此時如果座標在原點,則N次迴圈後就會重複從前的路徑。

如果座標不在原點,此時把當前位置當作原點,就會每N次移動遠離一段和當前原點的距離。距離最初的(0,0)位置越來越遠。就不存在迴圈會最原始原點的問題。

其實至多經過四次,機器人就會面朝北。

經過一次指令後,機器人面朝西或東,相當於逆時針或順時針轉了90度,則再經過三次,就面朝北了。

經過一次指令後,朝南則轉了180度,共移動兩次指令後朝北。

數學方法:

還可以把指令集先計算一遍,得出經過一個指令集後的相對移動位置和方向轉角。用矩陣計算,就不用每次都執行一大堆指令模擬,加快運算速度;

還可以用複數來運算,複數對於轉90度有簡單的運算方法。

class Solution {
public:
    bool isRobotBounded(string instructions) {
        int x = 0;
        int y = 0;
        int i = 0;
        int dir[][2] = {{0,1},{1,0},{0,-1},{-1,0}};
        do
        {
            for(auto ch : instructions)
            {
            if(ch=='G')
            {
                x+=dir[i][0];
                y+=dir[i][1];
            }
            else if(ch=='R')
            {
                ++i;
                i%=4;
            }
            else
            {
                i+=4;
                i--;
                i%=4;
            }
            }   
        }while(i!=0);
        if(x==0&&y==0)
            return true;   
        return false;
    }
};

不鄰接植花

題目:

不鄰接植花(Flower Planting With No Adjacent)

地址:

https://leetcode-cn.com/contest/weekly-contest-136/problems/flower-planting-with-no-adjacent/

題意:

在一個無向圖中,每個點的出度都不超過3。有四種顏色,給每個點著色,要求有邊相連的點顏色不同。

給出著色方案。

思路:

由於每個點出度不超過3,四個顏色,肯定可以有解。暴力列舉即可。由於圖的點很多,邊少。在尋找和點相連的點時,不要按點遍歷,要按邊遍歷,否則會超時。

程式碼:

class Solution {
public:
    map<int, map<int, int>> mr;
    vector<int> res;
    int dfs(int index, int N)
    {
        if(index > N)
            return 0;
        for(int color=1;color<=4;++color)
        {
            res[index-1] = color;
            map<int, int> & tmp = mr[index];
            bool flag = false;
            for(auto it=tmp.begin();it!=tmp.end();++it)
            {
                if(res[it->first-1]==color)
                {
                    flag = true;
                    break;
                }
            }
            if(flag == true)
                continue;
            int ret = dfs(index+1, N);
            if(ret == 0)
                return 0;
        }
        return -1;
    }
    vector<int> gardenNoAdj(int N, vector<vector<int>>& paths) {
        res.resize(N, 0);
        for(int i=0; i<paths.size(); ++i)
        {
            int x = paths[i][0];
            int y = paths[i][1];
            mr[x][y] = 1;
            mr[y][x] = 1;
        }
        dfs(1, N);
        return res;
    }
};

分隔陣列以得到最大和

題目:

分隔陣列以得到最大和(Partition Array for Maximum Sum)

地址:

https://leetcode-cn.com/contest/weekly-contest-136/problems/partition-array-for-maximum-sum/

題意:

給出整數陣列 A,將該陣列分隔為長度最多為 K 的幾個(連續)子陣列。分隔完成後,每個子陣列的中的值都會變為該子陣列中的最大值。

返回給定陣列完成分隔後的最大和。

思路:

該問題可以劃分為子問題求解。

設陣列有N個元素A[0]A[1]...A[N-1],sum(i)表示從A[i]~A[N]求解的最大和。

則sum(i) = max( max(A[i]-A[i+m-1])*m + sum(m) ) 其中i<=m<=k;

就是每個從i開始到陣列結尾的最大和,等於前m個元素單獨劃分,再加上剩下元素的最大和。這k中劃分方案最大的,就是從i開始到陣列結尾最大和最大的。

依次計算到第0個位置結束。

為了計算統一,會用到sum(n),實際沒有這個元素,初始化零計算即可。

程式碼:

class Solution {
public:
    int dp[501]={0};
    int maxSumAfterPartitioning(vector<int>& A, int K) {
        int n = A.size();
            for(int i = n-1; i>=0; --i)
            {
                int ma = 0;
                int j;
                for(j=i;j<i+K && j < n ;++j)
                {
                    ma = max(ma, A[j]);
                    dp[i] = max(dp[i], ma*(j - i + 1) + dp[j + 1]);
                }
            }
        return dp[0];
    }
};

最長重複子串

題目:

最長重複子串(Longest Duplicate Substring)

地址:

https://leetcode-cn.com/contest/weekly-contest-136/problems/longest-duplicate-substring/

題意:

給出一個字串 S,考慮其所有重複子串(S 的連續子串,出現兩次或多次,可能會有重疊)。

返回任何具有最長可能長度的重複子串。(如果 S 不含重複子串,那麼答案為 ""。)

思路:

字尾陣列教科書般的例題。

字尾陣列是字尾樹的一種變種,能夠節省空間。構造的方法有「倍增演算法」,「DC3演算法」。

主要思想:

設字串為S(1-n)由n個字元組成。則字串有n個相同字尾的子串。分別為s(1-n),s(2-n),...,s(n-n)。

然後構建一個SA陣列,每個陣列儲存這些字尾的子串,儲存後進行字典序排序。

最後構造出一個height陣列,表示SA陣列每個元素和前一個元素相同字首的字元個數。

那麼,最長重複子串的長度就是height陣列的最大值。

因為最長重複子串一定是兩個不同字尾的公共字首,而且這兩個不同字尾的字典序排列後一定是相連的。否則一定有比他更長的。

所以height的最大值能夠找到那兩個字尾,然後提取公共字首就找到答案。

程式碼:

namespace SA
{
bool cmp(int *r, int a, int b, int l)
{
    return r[a] == r[b] && r[a + l] == r[b + l];
}
void da(int str[], int sa[], int rank[], int height[], int n, int m)
{
    n++;
    int i, j, p, *x = t1, *y = t2;
    for (i = 0; i < m; i++)
        c[i] = 0;
    for (i = 0; i < n; i++)
        c[x[i] = str[i]]++;
    for (i = 1; i < m; i++)
        c[i] += c[i - 1];
    for (i = n - 1; i >= 0; i--)
        sa[--c[x[i]]] = i;
    for (j = 1; j <= n; j <<= 1)
    {
        p = 0;
        for (i = n - j; i < n; i++)
            y[p++] = i;
        for (i = 0; i < n; i++)
            if (sa[i] >= j)
                y[p++] = sa[i] - j;
        for (i = 0; i < m; i++)
            c[i] = 0;
        for (i = 0; i < n; i++)
            c[x[y[i]]]++;
        for (i = 1; i < m; i++)
            c[i] += c[i - 1];
        for (i = n - 1; i >= 0; i--)
            sa[--c[x[y[i]]]] = y[i];
        swap(x, y);
        p = 1;
        x[sa[0]] = 0;
        for (i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
        if (p >= n)
            break;
        m = p;
    }
    int k = 0;
    n--;
    for (i = 0; i <= n; i++)
        rank[sa[i]] = i;
    for (i = 0; i < n; i++)
    {
        if (k)
            k--;
        j = sa[rank[i] - 1];
        while (str[i + k] == str[j + k])
            k++;
        height[rank[i]] = k;
    }
}
int num_rank[MAXN], height[MAXN];
int num[MAXN];
int sa[MAXN];
} // namespace SA

class Solution
{
  public:
    string longestDupSubstring(string S)
    {
        using namespace SA;
        int pos = 0;
        int len = 0;
        int n = S.length();
        for (int i = 0; i <= n; ++i)
        {
            num[i] = S[i]&0x3f;
        }
        num[n] = 0;
        da(num, sa, num_rank, height, n, 256);
        for (int i = 2; i <= n; ++i)
        {
            if (height[i] > len)
            {
                pos = sa[i];
                len = height[i];
            }
        }
        return S.substr(pos, len);
    }
};

相關文章