写在前面
华为面试题库刷题第四次整理。
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;
}
};