需要耐心的中等难度题-华为面试题库刷题整理(四)

写在前面

华为面试题库刷题第四次整理。

1245. 树的直径

给你这棵「无向树」,请你测算并返回它的「直径」:这棵树上最长简单路径的 边数。

我们用一个由所有「边」组成的数组 edges 来表示一棵无向树,其中 edges[i] = [u, v] 表示节点 u 和 v 之间的双向边。

树上的节点都已经用 {0, 1, …, edges.length} 中的数做了标记,每个节点上的标记都是独一无二的。

提示:

  1. 0 <= edges.length < 10^4
  2. edges[i][0] != edges[i][1]
  3. 0 <= edges[i][j] <= edges.length
  4. 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

  1. 随机选定一个点作为根节点,这里我们选0。
  2. 从root出发进行一次dfs,记录最远节点p1。
  3. 从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. 1 <= data.length <= 10^5
  2. 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 并返回该整数所有的因子组合。

注意:

  1. 你可以假定 n 为永远为正数。
  2. 因子必须大于 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;
    }
};
发布了13 篇原创文章 · 获赞 27 · 访问量 2649

猜你喜欢

转载自blog.csdn.net/u011708337/article/details/105007458
今日推荐