寫在前面
華爲面試題庫刷題第四次整理。
1245. 樹的直徑
給你這棵「無向樹」,請你測算並返回它的「直徑」:這棵樹上最長簡單路徑的 邊數。
我們用一個由所有「邊」組成的數組 edges 來表示一棵無向樹,其中 edges[i] = [u, v] 表示節點 u 和 v 之間的雙向邊。
樹上的節點都已經用 {0, 1, …, edges.length} 中的數做了標記,每個節點上的標記都是獨一無二的。
提示:
- 0 <= edges.length < 10^4
- edges[i][0] != edges[i][1]
- 0 <= edges[i][j] <= edges.length
- edges 會形成一棵無向樹
解法:很明顯的dfs題目,但是最關鍵的是如何dfs。
暴力dfs(超時)
我寫了一個很傻的遍歷dfs,對所有隻有一邊相連的點進行一次dfs,其實很明顯就可以發現有很多冗餘的搜索。
代碼:
class Solution {
private:
int res = 0;
int l = 0;
public:
void dfs(vector<vector<int>>& b, int k, int d){
bool f = false;
for(int i=0; i<l; i++){
if(b[k][i]==1){
f = true;
b[k][i] = 0;
b[i][k] = 0;
dfs(b,i,d+1);
b[k][i] = 1;
b[i][k] = 1;
}
}
res = max(res,d);
}
int treeDiameter(vector<vector<int>>& edges) {
if(edges.empty()) return 0;
l = edges.size();
vector<int> a(l+2,0);
vector<vector<int>> b(l+1,vector<int>(l+1,0));
for(int i=0; i<l; i++){
a[edges[i][0]]++;
a[edges[i][1]]++;
b[edges[i][0]][edges[i][1]] = 1;
b[edges[i][1]][edges[i][0]] = 1;
}
/*for(int i=0; i<=l; i++)
cout<<a[i]<<' ';
cout<<endl;*/
for(int i=0; i<=l; i++){
if(a[i]==1){
dfs(b,i,0);
}
}
return res;
}
};
優化的dfs
- 隨機選定一個點作爲根節點,這裏我們選0。
- 從root出發進行一次dfs,記錄最遠節點p1。
- 從p1節點出發進行一次dfs,此時記錄下來的深度就是答案。
代碼:
class Solution {
private:
vector<vector<int>> graph;
vector<bool> visited;
public:
int treeDiameter(vector<vector<int>>& edges) {
// build graph
int l = edges.size();
graph.resize(l+2);
visited.resize(l+2);
for(auto e: edges) {
graph[e[0]].push_back(e[1]);
graph[e[1]].push_back(e[0]);
}
pair<int, int> p1, p2;
p1 = { -1, 0 };
dfs(0, 0, p1);
visited.assign(l+2, false);
p2 = { -1, 0 };
dfs(p1.first, 0, p2);
return p2.second;
}
void dfs(int u, int depth, pair<int, int>& p) {
visited[u] = true;
if(depth > p.second) {
p.second = depth;
p.first = u;
}
for(int v: graph[u])
if(!visited[v])
dfs(v, depth + 1, p);
}
};
1151. 最少交換次數來組合所有的 1
給出一個二進制數組 data,你需要通過交換位置,將數組中 任何位置 上的 1 組合到一起,並返回所有可能中所需 最少的交換次數。
提示:
- 1 <= data.length <= 10^5
- 0 <= data[i] <= 1
解法:滑動窗口很適合這題,算出1的個數爲k,那麼窗口的大小就是p,滑動過程中更新窗口內的1的個數,記錄下最大的個數q,p-q就是答案。
代碼:
class Solution {
public:
int minSwaps(vector<int>& data) {
if(data.empty()) return 0;
int sum = 0;
for(int i=0; i<data.size(); i++)
sum += data[i];
int res(0),c(0);
for(int i=0; i<=sum-1; i++)
c += data[i];
res = max(res,c);
for(int i=sum; i<data.size(); i++){
c += data[i];
c -= data[i-sum];
res = max(res,c);
}
return (sum-res);
}
};
239. 滑動窗口最大值
給定一個數組 nums,有一個大小爲 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口內的 k 個數字。滑動窗口每次只向右移動一位。
返回滑動窗口中的最大值。
示例:
輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7]
上一題就是一道很好的滑動窗口題,我們趁熱打鐵,再練一道滑動窗口題(其實是因爲刷題羣裏有個妹子問了這題 霧)。
解法:一開始羣裏一個熱心大佬說這是一道優先隊列,我一想,很有道理,然後做了半天發現有問題,窗口移動,需要刪除,就不行。這道題還是用多種解法來練習一下。
暴力
代碼:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k==0) return {};
vector<int> res;
for(int i=0; i+k<=nums.size(); i++){
int m = nums[i];
for(int j=i+1; j<=i+k-1; j++)
m = max(m,nums[j]);
res.push_back(m);
}
return res;
}
};
雙端隊列
這是這題的正解,用雙端隊列可以同時維護隊頭和隊尾,隊列裏面保存索引,隊頭永遠是最大的,超出窗口大小就彈出隊頭,每次有新元素就從隊尾刪掉比新元素小的數。
代碼:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k==0) return {};
deque<int> q;
vector<int> res;
for(int i=0; i<k; i++){//初始化隊列
while(!q.empty() && nums[i]>nums[q.back()])
q.pop_back();
q.push_back(i);
}
res.push_back(nums[q.front()]);
for(int i=k; i<nums.size(); i++){
if(!q.empty() && q.front()<=i-k)//超出窗口大小
q.pop_front();
while(!q.empty() && nums[i]>nums[q.back()])//刪除隊尾小的索引
q.pop_back();
q.push_back(i);
res.push_back(nums[q.front()]);
}
return res;
}
};
動態規劃
這道題動態規劃的思路很巧,這裏我貼一下官方的解釋,講的很清楚,我就不多贅述了。 https://leetcode-cn.com/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetcode-3/
代碼:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k==0) return {};
vector<int> res,left;
vector<int> right(nums.size(),0);
int t = 0;
for(int i=0; i<nums.size(); i++){
if(i%k==0){
t = nums[i];
}
t = max(t,nums[i]);
left.push_back(t);
}
t = nums[nums.size()-1];
for(int i=nums.size()-1; i>=0; i--){
if((i+1) % k==0){
t = nums[i];
}
t = max(t,nums[i]);
right[i] = t;
}
for(int i=0; i+k<=nums.size(); i++)
res.push_back(max(left[i+k-1],right[i]));
return res;
}
};
254. 因子的組合
整數可以被看作是其因子的乘積。
例如:
8 = 2 x 2 x 2;
= 2 x 4.
請實現一個函數,該函數接收一個整數 n 並返回該整數所有的因子組合。
注意:
- 你可以假定 n 爲永遠爲正數。
- 因子必須大於 1 並且小於 n。
示例:
輸入: 32
輸出:
[
[2, 16],
[2, 2, 8],
[2, 2, 2, 4],
[2, 2, 2, 2, 2],
[2, 4, 4],
[4, 8]
]
解法:很明顯的dfs,不過難點是存儲,根據示例給出的存儲順序,可以找到dfs的思路,每次拆解最後一位就行了,然後保證序列不下降。
代碼:
class Solution {
private:
vector<vector<int>> res;//存儲答案
public:
void dfs(vector<int>& m){
res.push_back(m);
for(int i=m[m.size()-2]; i<=sqrt(m[m.size()-1]); i++){//拆解最後一位數字,範圍:[倒數第二位數,最後一位數的平方根]
if(m[m.size()-1]%i==0){
vector<int> temp(m.begin(),m.end()-1);
temp.push_back(i);
temp.push_back(m[m.size()-1]/i);
dfs(temp);
}
}
}
vector<vector<int>> getFactors(int n) {
for(int i=2; i<=sqrt(n); i++){
if(n%i==0){
vector<int> temp = {i , n/i};
dfs(temp);
}
}
return res;
}
};
本題小結:這題從思路和算法上來看不難,但是遞歸最怕的其實就是存儲,我這裏參考的題解裏面一位大佬的思路,用一個臨時的一維vector來操作,就比較方便了。
554. 磚牆
你的面前有一堵方形的、由多行磚塊組成的磚牆。 這些磚塊高度相同但是寬度不同。你現在要畫一條自頂向下的、穿過最少磚塊的垂線。
磚牆由行的列表表示。 每一行都是一個代表從左至右每塊磚的寬度的整數列表。
如果你畫的線只是從磚塊的邊緣經過,就不算穿過這塊磚。你需要找出怎樣畫才能使這條線穿過的磚塊數量最少,並且返回穿過的磚塊數量。
你不能沿着牆的兩個垂直邊緣之一畫線,這樣顯然是沒有穿過一塊磚的。
示例:
輸入:
[1,2,2,1],
[3,1,2],
[1,3,2],
[2,4],
[3,1,2],
[1,3,1,1]]
輸出: 2
解法:
哈希表
思路不太難,每一行的磚塊加上前面所有的寬度,然後因爲這些寬度都是線性遞增的,所以加完之後,用hashmap記錄一下相同寬度的個數,最大的就是答案的位置。
代碼:
class Solution {
public:
int leastBricks(vector<vector<int>>& wall) {
int l = wall.size();
for(int i=0; i<wall.size(); i++){
for(int j=1; j<wall[i].size(); j++){
wall[i][j] += wall[i][j-1];
}
}
int m = wall[0][wall[0].size()-1];
unordered_map<int,int> a;
for(int i=0; i<wall.size(); i++)
for(int j=0; j<wall[i].size()-1; j++){
auto pos = a.find(wall[i][j]);
if(pos!=a.end()){
a[wall[i][j]]++;
}
else{
a[wall[i][j]] = 1;
}
}
int res = 0;
for(auto it = a.begin(); it!=a.end(); it++){
res = max(res,it->second);
}
return l-res;
}
};
排序
還是先求和,然後用一維數組存下所有的寬度,然後sort,比上一種方法快一點點。
代碼:
class Solution {
public:
int leastBricks(vector<vector<int>>& wall) {
int l = wall.size();
for(int i=0; i<wall.size(); i++){
for(int j=1; j<wall[i].size()-1; j++){
wall[i][j] += wall[i][j-1];
}
}
vector<int> a;
for(int i=0; i<wall.size(); i++)
for(int j=0; j<wall[i].size()-1; j++)
a.push_back(wall[i][j]);
sort(a.begin(),a.end());
int res = 0;
int c=1;
for(int i=1; i<a.size(); i++)
if(a[i]!=a[i-1]){
res = max(res,c);
c = 1;
}
else
++c;
if(!a.empty())res = max(res,c);
return l-res;
}
};
562. 矩陣中最長的連續1線段
給定一個01矩陣 M,找到矩陣中最長的連續1線段。這條線段可以是水平的、垂直的、對角線的或者反對角線的。
示例:
輸入:
[[0,1,1,0],
[0,1,1,0],
[0,0,0,1]]
輸出: 3
提示: 給定矩陣中的元素數量不會超過 10,000。
解法:dp題目,只不過得四個方向分別做,代碼很長,得注意邊界問題。
代碼:
class Solution {
public:
int longestLine(vector<vector<int>>& a) {
if(a.empty() || a[0].empty()) return 0;
int m = a.size();
int n = a[0].size();
int res = 0;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
res += a[i][j];
if(res==0 || res==1) return res;
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
res = 0;
//橫向
dp[0][0] = a[0][0];
for(int i=1; i<m; i++)
dp[i][0] = a[i][0];//初始化第一列
for(int i=0; i<m; i++)
for(int j=1; j<n; j++)
if(a[i][j])
dp[i][j] = dp[i][j-1] + 1;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
res = max(res,dp[i][j]);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
dp[i][j] = 0;//清零
cout<<1<<endl;
//豎向
dp[0][0] = a[0][0];
for(int i=0; i<n; i++)
dp[0][i] = a[0][i];//初始化第一行
for(int i=1; i<m; i++)
for(int j=0; j<n; j++)
if(a[i][j])
dp[i][j] = dp[i-1][j] + 1;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
res = max(res,dp[i][j]);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
dp[i][j] = 0;//清零
//斜向
dp[0][0] = a[0][0];
for(int i=0; i<n; i++)
dp[0][i] = a[0][i];//初始化第一行
for(int i=0; i<m; i++)
dp[i][0] = a[i][0];//初始化第一列
for(int i=1; i<m; i++)
for(int j=1; j<n; j++)
if(a[i][j])
dp[i][j] = dp[i-1][j-1] + 1;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
res = max(res,dp[i][j]);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
dp[i][j] = 0;//清零
//反斜向
dp[0][0] = a[0][0];
for(int i=0; i<n; i++)
dp[0][i] = a[0][i];//初始化第一行
for(int i=0; i<m; i++)
dp[i][n-1] = a[i][n-1];//初始化最後一列
for(int i=1; i<m; i++)
for(int j=0; j<n-1; j++)
if(a[i][j])
dp[i][j] = dp[i-1][j+1] + 1;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
res = max(res,dp[i][j]);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
dp[i][j] = 0;//清零
return res;
}
};