需要耐心的中等難度題-華爲面試題庫刷題整理(四)

寫在前面

華爲面試題庫刷題第四次整理。

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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章