LeetCode第178場周賽(Weekly Contest 178)解題報告

這周狀態不錯,AK了,可喜可賀。但還是速度慢,同時代碼實現能力還需要繼續加強。加油吧,騷年。

比賽重要的是速度,而不是最優解,要注意,和平時自己刷題不同,自己刷題,重點是要考慮多種解法,找出最優解。但是比賽的時候,只要在時間複雜度允許的情況下,暴力是最快的。

第一題:暴力枚舉 或者 哈希表 + 排序。

第二題:排序(重寫 cmp 排序依據)。

第三題:樹的DFS。

第四題:0 / 1 最短路(使用了SPFA)或者 雙端隊列BFS。

詳細題解如下。


1.有多少小於當前數字的數字(How Many Numbers Are Smaller Than The Current Number)

          AC代碼(方法一、暴力枚舉  C++)

          AC代碼(方法二、哈希 + 排序  C++)

2. 通過投票對團隊排名(Rank Teams By Votes)

          AC代碼(C++)

3.二叉樹中的列表(Linked List In Binary Tree)

          AC代碼(C++)

4.使網格圖至少有一條有效路徑的最小代價(Minimum Cost To Make At Least One Valid Path In A Grid)

          AC代碼(方法一、SPFA  C++)

          AC代碼(方法二、雙端隊列BFS  C++)

 


LeetCode第178場周賽地址:

https://leetcode-cn.com/contest/weekly-contest-178/


1.有多少小於當前數字的數字(How Many Numbers Are Smaller Than The Current Number)

題目鏈接

https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/

題意

給你一個數組 nums,對於其中每個元素 nums[i],請你統計數組中比它小的所有數字的數目。

換而言之,對於每個 nums[i] 你必須計算出有效的 j 的數量,其中 j 滿足 j != i 且 nums[j] < nums[i] 。

以數組形式返回答案。

示例 1:

輸入:nums = [8,1,2,2,3]
輸出:[4,0,1,1,3]
解釋: 
對於 nums[0]=8 存在四個比它小的數字:(1,2,2 和 3)。 
對於 nums[1]=1 不存在比它小的數字。
對於 nums[2]=2 存在一個比它小的數字:(1)。 
對於 nums[3]=2 存在一個比它小的數字:(1)。 
對於 nums[4]=3 存在三個比它小的數字:(1,2 和 2)。

示例 3:

輸入:nums = [7,7,7,7]
輸出:[0,0,0,0]

提示:

  • 2 <= nums.length <= 500
  • 0 <= nums[i] <= 100

解題思路

思路一、暴力枚舉

看到題目,想到的就是,對於每一個數,枚舉其他所有數,進行大小比較。這樣子時間複雜度是 O(N ^ 2), 空間複雜度是 O(1)。

根據數據範圍,不會超時,所以直接暴力枚舉

 

思路二、哈希表 + 排序

我們可以先對原數組排序(由於這裏原數組不能改動,所以要複製一個數組),進行從小到大排序(這樣子對於 第 i 個數 ,前面就有 最多 i 個數比該數小),根據 1 2 2,當我們計算 2 的排序,應該是 1, 當判斷到 第二個  2 的時候,由於此時 2 已經有順序了,那麼就不重新記。

對於每一個數,我們要快速查找到這個數,前面有幾個比它小的,那麼就用 哈希表 來記錄。

接着遍歷每一個數,然後用哈希表就可以快速知道這個數,有幾個數比它小了。

時間複雜度是 O(N * logN), 空間複雜度是 O(N),用空間換時間

 

AC代碼(方法一、暴力枚舉  C++)

class Solution {
public:
    vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
        vector<int> ans;
        int n = nums.size();
        for(int i = 0;i < n; ++i)
        {
            int cnt = 0;
            for(int j = 0;j < n; ++j)
            {
                if(j == i) continue;
                if(nums[i] > nums[j]) ++cnt;
            }
            ans.push_back(cnt);
        }
        return ans;
    }
};

 

AC代碼(方法二、哈希 + 排序  C++)

class Solution {
public:
    unordered_map<int, int> cnt;
    vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
        vector<int> ans = nums;
        int n = nums.size();
        sort(ans.begin(), ans.end());
        for(int i = 0;i < n; ++i)
        {
            if(cnt[ans[i]] != 0) continue;
            cnt[ans[i]] = i + 1;   // 本來數量 第 0 個是 0,但是這個可能會被重新覆蓋,所以就 都加 1,最後數量再 - 1 回來
        }
        for(int i = 0;i < n; ++i)
        {
            ans[i] = cnt[nums[i]] - 1;
        }
        return ans;
    }
};

2. 通過投票對團隊排名(Rank Teams By Votes)

題目鏈接

https://leetcode-cn.com/problems/rank-teams-by-votes/

題意

現在有一個特殊的排名系統,依據參賽團隊在投票人心中的次序進行排名,每個投票者都需要按從高到低的順序對參與排名的所有團隊進行排位。

排名規則如下:

參賽團隊的排名次序依照其所獲「排位第一」的票的多少決定。如果存在多個團隊並列的情況,將繼續考慮其「排位第二」的票的數量。以此類推,直到不再存在並列的情況。
如果在考慮完所有投票情況後仍然出現並列現象,則根據團隊字母的字母順序進行排名。
給你一個字符串數組 votes 代表全體投票者給出的排位情況,請你根據上述排名規則對所有參賽團隊進行排名。

請你返回能表示按排名系統 排序後 的所有團隊排名的字符串。

示例 1:

輸入:votes = ["ABC","ACB","ABC","ACB","ACB"]
輸出:"ACB"
解釋:A 隊獲得五票「排位第一」,沒有其他隊獲得「排位第一」,所以 A 隊排名第一。
B 隊獲得兩票「排位第二」,三票「排位第三」。
C 隊獲得三票「排位第二」,兩票「排位第三」。
由於 C 隊「排位第二」的票數較多,所以 C 隊排第二,B 隊排第三。

示例 2:

輸入:votes = ["WXYZ","XYZW"]
輸出:"XWYZ"
解釋:X 隊在並列僵局打破後成爲排名第一的團隊。X 隊和 W 隊的「排位第一」票數一樣,但是 X 隊有一票「排位第二」,而 W 沒有獲得「排位第二」。

提示:

  • 1 <= votes.length <= 1000
  • 1 <= votes[i].length <= 26
  • votes[i].length == votes[j].length for 0 <= i, j < votes.length
  • votes[i][j] 是英文 大寫 字母
  • votes[i] 中的所有字母都是唯一的
  • votes[0] 中出現的所有字母 同樣也 出現在 votes[j] 中,其中 1 <= j < votes.lengt

 

解題思路

這道題,主要就是排序(也就是對各個字母進行排序),排序依據是,根據【排位第1】的數量進行排序;如果相同,就依據【排位第2】的數量進行排序 .... 依此到最後。如果最後【排位】的數量都相同,那麼此時,對字母的字典序升序進行排序。

因此,我們就要統計,【排位第幾】時,【這個字母出現的次數(也就是被投了幾票)】,那麼我們用一個二維數組即可(26 * 26),因爲排位最多有 26 (因爲最多 26 個字母),然後有 26 個字母。

然後重寫 sort 的 cmp 即可

 

AC代碼(C++)

int cnt[30][30];
int N;

bool cmp(char a, char b)  // 排序依據
{
    for(int i = 1;i <= N; ++i)   // 從排位第 1  .. 一直到  排位 第 N
    {
        if(cnt[i][a - 'A'] != cnt[i][b - 'A']) 
            return cnt[i][a - 'A'] > cnt[i][b - 'A'];
    }
    return a < b;  // 都相同,就按字母字典序升序排
}

class Solution {
public:
    string rankTeams(vector<string>& votes) {
        memset(cnt, 0, sizeof(cnt));
        N = votes[0].size();
        for(auto v : votes)   // 統計次數
        {
            for(int i = 0;i < v.size(); ++i)
            {
                ++cnt[i + 1][v[i] - 'A'];
            }
        }
        
        vector<char> zimu;  // 出現的所有字母情況
        for(int i = 0;i < votes[0].size(); ++i)
        {
            zimu.push_back(votes[0][i]);
        }
        
        sort(zimu.begin(), zimu.end(), cmp);  // 對這個字母數組進行排序
        
        string ans = "";
        for(int i = 0;i < zimu.size(); ++i)   // 得到順序後,變爲 string 輸出
            ans += zimu[i];
        
        return ans;
        
    }
};

 


3.二叉樹中的列表(Linked List In Binary Tree)

題目鏈接

https://leetcode-cn.com/problems/linked-list-in-binary-tree/

題意

給你一棵以 root 爲根的二叉樹和一個 head 爲第一個節點的鏈表。

如果在二叉樹中,存在一條一直向下的路徑,且每個點的數值恰好一一對應以 head 爲首的鏈表中每個節點的值,那麼請你返回 True ,否則返回 False 。

一直向下的路徑的意思是:從樹中某個節點開始,一直連續向下的路徑。

示例 有圖,具體看鏈接

提示:

  • 二叉樹和鏈表中的每個節點的值都滿足 1 <= node.val <= 100 。
  • 鏈表包含的節點數目在 1 到 100 之間。
  • 二叉樹包含的節點數目在 1 到 2500 之間。

 

 

解題分析

首先使用dfs遍歷二叉樹,找到與鏈表頭節點值相同的節點。

然後在用第二個dfs,開始逐個匹配該鏈表在不在二叉樹中。

如果有滿足,那就是答案時 true(對於所有 dfs,只要發現,flag == true,說明已經存在了,那麼剩下的 dfs 都不用再遍歷了)

 

AC代碼(C++)

class Solution {
public:
    vector<int> nums;
    int n;
    bool flag = false;

    void dfsCheck(TreeNode* root, int cnt)  // 檢查從這個起點開始,是不是有完整的
    {
        if(root == NULL) return;
        // 對於 cnt,是計算,此時已經連續有相等的幾個了
        // 如果對於 cnt == n - 1 了,說明樹中有和鏈表一樣的樹了
        if(root->val == nums[cnt] && cnt == n - 1)   
        {
            flag = true;   // 置爲 true
            return;
        }

        if(root->val == nums[cnt])   // 如果對於這個數相等,那麼繼續往下考慮,下一個節點,是不是和 cnt + 1,相等。
        {
            dfsCheck(root->left, cnt + 1);
            dfsCheck(root->right, cnt + 1);
        }
    }

    void dfsStart(TreeNode* root)
    {
        if(flag == true)  // 如果已經是 true了,那就所有DFS都停止
            return;
        if(root == NULL) return;
        if(root->val == nums[0])  // 找到了這個起點,那麼根據這個起點,開始第二層DFS,開始逐個匹配該鏈表在不在二叉樹中。
        {
            dfsCheck(root, 0);
        }
        dfsStart(root->left);  
        dfsStart(root->right);
    }

    bool isSubPath(ListNode* head, TreeNode* root) {
        // 爲了方便,我把鏈表中的數,都保存在 vector 中
        while(head != NULL)
        {
            nums.push_back(head->val);
            // cout << head->val << endl;
            head = head->next;
        }
        n = nums.size();

        dfsStart(root);  // 第一層 dfs,找到與鏈表頭節點值相同的節點
        
        return flag;
    }
};

 


4.使網格圖至少有一條有效路徑的最小代價(Minimum Cost To Make At Least One Valid Path In A Grid)

題目鏈接

https://leetcode-cn.com/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/

題意

給你一個 m x n 的網格圖 grid 。 grid 中每個格子都有一個數字,對應着從該格子出發下一步走的方向。 grid[i][j] 中的數字可能爲以下幾種情況:

  • 1 ,下一步往右走,也就是你會從 grid[i][j] 走到 grid[i][j + 1]
  • 2 ,下一步往左走,也就是你會從 grid[i][j] 走到 grid[i][j - 1]
  • 3 ,下一步往下走,也就是你會從 grid[i][j] 走到 grid[i + 1][j]
  • 4 ,下一步往上走,也就是你會從 grid[i][j] 走到 grid[i - 1][j]

注意網格圖中可能會有 無效數字 ,因爲它們可能指向 grid 以外的區域。

一開始,你會從最左上角的格子 (0,0) 出發。我們定義一條 有效路徑 爲從格子 (0,0) 出發,每一步都順着數字對應方向走,最終在最右下角的格子 (m - 1, n - 1) 結束的路徑。有效路徑 不需要是最短路徑 。

你可以花費 cost = 1 的代價修改一個格子中的數字,但每個格子中的數字 只能修改一次 。

請你返回讓網格圖至少有一條有效路徑的最小代價。

示例 1:

示例有圖,具體看鏈接

輸入:grid = [[1,1,1,1],[2,2,2,2],[1,1,1,1],[2,2,2,2]]
輸出:3
解釋:你將從點 (0, 0) 出發。
到達 (3, 3) 的路徑爲: (0, 0) --> (0, 1) --> (0, 2) --> (0, 3) 花費代價 cost = 1 使方向向下 --> (1, 3) --> (1, 2) --> (1, 1) --> (1, 0) 花費代價 cost = 1 使方向向下 --> (2, 0) --> (2, 1) --> (2, 2) --> (2, 3) 花費代價 cost = 1 使方向向下 --> (3, 3)
總花費爲 cost = 3.

示例 2:

示例有圖,具體看鏈接

輸入:grid = [[1,1,3],[3,2,2],[1,1,4]]
輸出:0
解釋:不修改任何數字你就可以從 (0, 0) 到達 (2, 2) 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100

解題分析

由於對於一個位置,可能有四個方向可以走,因此考慮 DFS 的話,那麼時間複雜度大概是 4 ^ (m * n) 就會超時

思路一、最短路SPFA

所以要好好思路,對於此時的一個位置,它可以走到四個方向,其中一個方向的cost = 0,其他四個方向的 cost = 1,那麼如果我們記錄 dist[ x ][ y ] 表示 (0, 0) 到 (x, y) 的最小 cost,那麼當我們從一個位置,走到其他位置的時候,更新此時的 dist,這就有點,類似,最短路的問題。

如果我們把 cost 看成是 路徑,那麼 dist[ x ][ y ] 表示 (0, 0) 到 (x, y) 的最小 距離。

對於每一個位置 (x, y) ,我們走的四個方向中,一個方向是路徑爲0,其他三個方向路徑爲 1。可以從 (x, y) 走到了 (tx, ty),那麼對於  dist[ tx ][ ty ] 的距離,就要取,是這個距離短,還是 dsit[ x ][ y ] + cost 更短。

因此就是一個最短路的問題,我使用的是,SPFA求最短路(正權 或 0 都可以用)。

那麼最後的輸出就是 dist[ n - 1][ m - 1 ]

 

思路二、雙端隊列BFS

對於SPFA方法,數據範圍不會卡,但是如果數據範圍大點,那麼對於SPFA,可能會超時(因爲SPFA,每一個點,可能不止走一次,可能會多次)。

那麼我們對於 0 / 1 最短路,本來最快的應該是 BFS,那麼對於這道題,也是可以使用的,因爲BFS,對於每一個點,只會走一次(那麼時間複雜度不會太大)。

也就是當我們 考慮某一個點(如果這個點前面已經考慮過了,就不考慮)的時候,它會有兩種情況:

1)不需要 cost 的轉移

2)需要 cost = 1 的轉移

轉移過後,我們更新最短距離,那麼從 (x, y) 到 (tx, ty),對於用 0 cost 轉移的,說明這個點,能更快到,優先考慮最短的,這樣子才能去優先更新其他點,說明對於這個點,要優先考慮。對於 1 cost 轉移的,就是正常的順序即可。

因此,這裏對 BFS 的順序,會有兩種情況,因此考慮使用 雙端列隊的 BFS

當發現是 0 cost的情況,那麼 (tx, ty)這個點,放到隊列前面,表示這個點,要優先考慮(因爲這個點是 0

cost 過來的,優先更短的)。其他的情況,就按正常順序,放在後面即可

 

正常的BFS,一定是權值爲 1 的(相當於是樹的邊連),那麼此時用隊列,存的時候

x x x ... x (x + 1) (x + 1) (x + 1) (x + 1)

我們從隊頭,拿出了最短的 x,更新其他的距離,由於權值爲 1,所以後面的距離是 (x + 1),那麼放到 隊尾 的 時候,還是保證了整個隊列是滿足,距離從小到大的。

但是由於這道題,是 0 / 1 ,也就是說,當我們拿到 x 的時候,可能更新的距離是 x,也可能是 (x + 1),如果直接都放到隊尾,就導致了,隊列不能滿足,距離從小到大(最短路)。

所以我們用 雙端隊列 來解決,也就是說,當我們從隊列頭 拿到 x 的時候,去更新距離。當發現還是 x ,那我們就放到隊列頭。如果發現是 (x+ 1) 就放到隊列尾。

這樣子就保證了整個隊列還是滿足,從小到大的。

 

AC代碼(方法一、SPFA  C++)

const int MAXN = 150;
#define INF 0x3f3f3f3f

class Solution {
public:
    
    int dist[MAXN][MAXN];  // 從 0,0 到 i, j 的最短路
    int inQue[MAXN][MAXN];  // SPFA模板
    
    int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
    
    int minCost(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        queue<pair<int, int>> que;  // 存入的是,一個點座標
        while(!que.empty()) que.pop();
        
        for(int i = 0;i < n;++i)
            for(int j = 0;j < m; ++j)
                dist[i][j] = INF;
            
        dist[0][0] = 0;  // 初始化
        
        inQue[0][0] = 1;   // 表示這個點,在隊列中
        que.push(make_pair(0, 0));
        
        while(!que.empty())
        {
            int x = que.front().first, y = que.front().second;
            que.pop();
            inQue[x][y] = 0;
            
            for(int i = 1;i <= 4; ++i)   // 四個方向
            {
                
                int tx = x + dx[i - 1], ty = y + dy[i - 1];  // 我們的 dx 是從 0 到 3,所以是 i - 1
                if(tx < 0 || tx >= n || ty < 0 || ty >= m) continue;
                
                if(i == grid[x][y])   // 其中一個方向的 cost = 0
                {
                    if(dist[tx][ty] > dist[x][y])  // 要更新距離
                    {
                        dist[tx][ty] = dist[x][y];
                        if(!inQue[tx][ty])   // 這個點不在隊列,因此,纔要重新加進去考慮,不然已經在隊列了,後面自然會考慮上
                        {
                            inQue[tx][ty] = 1;
                            que.push(make_pair(tx, ty));
                        }
                    }
                }
                else
                {
                    // 另外三個方向的  cost = 1
                    if(dist[tx][ty] > dist[x][y] + 1)
                    {
                        dist[tx][ty] = dist[x][y] + 1;
                        if(!inQue[tx][ty])
                        {
                            inQue[tx][ty] = 1;
                            que.push(make_pair(tx, ty));
                        }
                    }
                }
            }
  
        }      
        return dist[n - 1][m - 1];
    }
};

 

AC代碼(方法二、雙端隊列BFS  C++)

const int MAXN = 150;
#define INF 0x3f3f3f3f
 
class Solution {
public:
    
    int dist[MAXN][MAXN];  // 從 0,0 到 i, j 的最短路
    int vis[MAXN][MAXN];  // SPFA模板
    
    int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
    
    int minCost(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        deque<pair<int, int>> que;  // 存入的是,一個點座標

        while(!que.empty()) que.pop_front();
        
        for(int i = 0;i < n;++i)
            for(int j = 0;j < m; ++j)
                dist[i][j] = INF;
            
        dist[0][0] = 0;  // 初始化
        que.push_back(make_pair(0, 0));
        
        while(!que.empty())
        {
            int x = que.front().first, y = que.front().second;
            que.pop_front();
            // 如果這個點已經判斷過了,那就不用判斷了
            if(vis[x][y] == 1) continue;
            vis[x][y] = 1;
            
            for(int i = 1;i <= 4; ++i)   // 四個方向
            {
                
                int tx = x + dx[i - 1], ty = y + dy[i - 1];  // 我們的 dx 是從 0 到 3,所以是 i - 1
                if(tx < 0 || tx >= n || ty < 0 || ty >= m) continue;
                
                if(i == grid[x][y])   // 其中一個方向的 cost = 0
                {
                    if(dist[tx][ty] > dist[x][y])  // 要更新距離
                    {
                        dist[tx][ty] = dist[x][y];

                        que.push_front(make_pair(tx, ty));  // 因爲這個點,是直接 0 cost 過來的,所以這個點,要優先考慮,所以放在隊列前(優先判斷)
                    }
                }
                else
                {
                    // 另外三個方向的  cost = 1
                    if(dist[tx][ty] > dist[x][y] + 1)
                    {
                        dist[tx][ty] = dist[x][y] + 1;

                        que.push_back(make_pair(tx, ty));  // 這個點,就和正常的BFS一樣即可
                    }
                }
            }
  
        }      
        return dist[n - 1][m - 1];
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章