週日的比賽的時候正在外面辦事,沒有參加。賽後看了下題目,幾道題除了表面要考的內容,還是有些能發散擴充套件的地方。
做題目不是最終目的,通過做題發現知識盲區,去研究學習,才能不斷提高。
理論和實際是有關係的,一些題目也都有現實意義。計算機的一些模擬操作,通過數學演算法,能夠大大減輕程式碼量和演算法複雜度。
第一題是機器人在座標系上直走和轉彎,通過簡單的模擬就能實現。但是仔細思考發現還能通過線性代數,座標變換的方式做,這樣在實際中計算更快。甚至還可以用複數來做。
實際掃地機器人可能就用到了類似的演算法。讓他能夠不至於始終原地打轉。
第四題是典型的字尾樹、字尾陣列應用,找字串最長重複子串。在搜尋引擎,或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);
}
};