1、問題分析
題目鏈接:https://leetcode-cn.com/problems/sort-items-by-groups-respecting-dependencies/
整體思路就是先建立 m+1 個分組集和,然後根據每個節點的分組,完成分組集和的初始化。包括 該分組有哪些節點、該分組的前置分組是什麼。最後遍歷該分組 ----> 遍歷分組又分爲遍歷節點。代碼我已經進行了詳細的註釋,理解應該沒有問題,讀者可以作爲參考,如果看不懂(可以多看幾遍),歡迎留言哦!我看到會解答一下。
這是我花半天的時間才 AC
的。中途一直沒有思路,一直想放棄,還好堅持了下來。願各位小夥伴都能持之以恆,取得理想的結果!
2、問題解決
筆者以C++
方式解決。
#include "iostream"
using namespace std;
#include "algorithm"
#include "vector"
#include "queue"
#include "set"
#include "map"
#include "cstring"
#include "stack"
/**
* 整體思路就是先建立 m+1 個分組集和,然後根據每個節點的分組,完成分組集和的初始化。
* 包括 該分組有哪些節點、該分組的前置分組是什麼。
* 最後遍歷該分組 ----> 遍歷分組又分爲遍歷節點
*/
class Solution {
private:
// 定義分組結構體
struct GroupChen {
// 分組id,編號
int belong_group = -1;
// 該分組有哪些數據 在示例 1 中 如分組 0 ,有 3,4,6 元素
// 這裏使用 set 是爲了防止數據重複
set<int> dataChens;
// 該分組的前置分組,即訪問該分組時,必須訪問所有的前置分組
// 示例1 中,0號分組的前置分組爲 0 (包括自身)
set<GroupChen *> beforeGroupChens;
};
// 標記每個節點是否訪問過 0 未訪問 1 正在訪問 2 已經訪問
// 1 正在訪問 的目的是防止圖中有環 及時退出
vector<int> vis;
// 標記每個分組是否訪問過 0 未訪問 1 正在訪問 2 已經訪問
// 1 正在訪問 的目的是防止圖中有環 及時退出
vector<int> visGroup;
// 存儲最終結果
vector<int> result;
// 剪枝 快速退出
int flag = 0;
// 共用節點數
int n_all;
public:
vector<int> sortItems(int n, int m, vector<int> &group, vector<vector<int>> &beforeItems) {
// 定義分組集和
vector<GroupChen *> groups;
// 動態初始化訪問數組
vis.resize(n + 1);
visGroup.resize(m + 1);
//初始化節點數目
n_all = n;
// 初始化分組,注意這裏 i <= m; 有個等於號,
// 在算法實現過程中,我們將沒有分組的編號
// 即 group==-1的分組 放到 group=m 分組中
for (int i = 0; i <= m; ++i) {
//新建一個節點
GroupChen *pChen = new GroupChen;
//設值節點編號
pChen->belong_group = i;
//將節點加入分組集和中
groups.push_back(pChen);
}
// 設置分組集和中 beforeGroupChens 和 dataChens 數據
for (int i = 0; i < n; ++i) {
// 設置 dataChens 數據
// 這裏注意分組是 -1 的情況,如果是 -1 則自動放入 m 號分組
if (group[i] != -1) {
groups[group[i]]->dataChens.insert(i);
}
else {
groups[m]->dataChens.insert(i);
}
// 設置 beforeGroupChens 數據
// 如果 beforeItems不爲空纔有設置的意義
if (!beforeItems[i].empty()) {
// 遍歷 beforeItems 前置節點 數組
for (int j = 0; j < beforeItems[i].size(); ++j) {
// 這裏仍然是對 group 爲 -1 的情況進行處理
// 如果是 -1 則自動放入 m 號分組
// 否則仍然放在 group[?] 分組中
// beforeItems[i][j] 代表第 i 個節點, 第 j 個前驅
// group[beforeItems[i][j]] 代表該前驅的分組
int i1 = group[beforeItems[i][j]];
i1 = i1 == -1 ? m : i1;
// 這裏仍然是對 group 爲 -1 的情況進行處理
// 如果是 -1 則自動放入 m 號分組
if (group[i] != -1) {
// 插入 group[i] 號分組的前驅分組
groups[group[i]]->beforeGroupChens.insert(groups[i1]);
}
else if (group[i] == -1) {
groups[m]->beforeGroupChens.insert(groups[i1]);
}
}
}
}
// 遍歷 m+1 個分組集和
for (int i = 0; i <= m; ++i) {
// 沒有解決方案,快速結束
if (flag == 1) {
// 根據題目意思 返回空數組
result.clear();
return result;
}
// 如果某個 分組沒有訪問 則訪問該 分組
// i 代表第 i 個分組
if (visGroup[i] == 0) {
findAllGroups(groups, m, beforeItems, i);
}
}
// 沒有解決方案,快速結束
if (flag == 1) {
// 根據題目意思 返回空數組
result.clear();
return result;
}
return result;
}
/**
* 訪問 第 i 個分組
* @param groups 分組集和
* @param m 分組個數
* @param beforeItems
* @param i 代表第 i 個分組
*/
void findAllGroups(vector<GroupChen *> &groups, int m, vector<vector<int>> &beforeItems, int i) {
// 說明有環,直接返回false
if (visGroup[i] == 1) {
// 設置標誌位
flag = 1;
return;
}
// 正在訪問該節點
visGroup[i] = 1;
try {
// 沒有解決方案
if (flag == 1) {
return;
}
// 如果該分組沒有前驅分組 說明可以直接訪問
if (groups[i]->beforeGroupChens.empty()) {
for (auto it = groups[i]->dataChens.begin(); it != groups[i]->dataChens.end(); it++) {
// 如果該分組中的 *it 節點沒有被訪問,則訪問該節點,
if (vis[*it] == 0) {
result.push_back(*it);
// 同時將訪問狀態設置成 2
vis[*it] = 2;
// 由於沒有對重複元素,缺少元素等做判斷,這裏直接使用最簡單直接的方式
// 只要結果數組中的值的個數大於 節點數量,說明肯定沒有解決方案,提前結束
if (result.size() > n_all) {
flag = 1;
return;
}
}
}
}
// 只有一個前驅分組狀況
else if (groups[i]->beforeGroupChens.size() == 1) {
for (auto it = groups[i]->beforeGroupChens.begin();
it != groups[i]->beforeGroupChens.end(); it++) {
if ((*it)->belong_group == i) {
// 如果正好是 自己本身 則遍歷該分組
dfs(groups, i, beforeItems);
}
else {
// 如果是其他分組,則先遍歷該分組,即先遍歷前置分組
findAllGroups(groups, m, beforeItems, (*it)->belong_group);
// 如果在遍歷過程中,出現沒有解決方案的情況,直接結束
if (flag == 1) {
return;
}
// 注意訪問完前置分組,一定要再次訪問自身分組
dfs(groups, i, beforeItems);
}
}
}
// 如果前置分組有多個
else {
for (auto it = groups[i]->beforeGroupChens.begin();
it != groups[i]->beforeGroupChens.end(); it++) {
// 快速結束
if (flag == 1) {
return;
}
// 因爲上面我們定義前置分組裏面會有自己分組,其實是不嚴格的
// 這裏遇到是自身分組的時候 先跳過自身分組的遍歷
if ((*it)->belong_group == i) {
continue;
}
else {
// 如果是其他分組,直接訪問該分組
findAllGroups(groups, m, beforeItems, (*it)->belong_group);
}
}
// 注意訪問完前置分組,一定要再次訪問自身分組
dfs(groups, i, beforeItems);
}
//訪問分組完成,訪問狀態變成2,即訪問完成
visGroup[i] = 2;
}
catch (exception e) {
// 出現異常直接 沒有解決方案 退出
flag = 1;
return;
}
}
/**
* 遍歷第 index 個分組
* @param groups
* @param index 第 index 個分組
* @param beforeItems
*/
void dfs(vector<GroupChen *> &groups, int index, vector<vector<int>> &beforeItems) {
// 取出該分組
GroupChen *pChen = groups[index];
// 遍歷該分組中的所有節點
for (auto it = pChen->dataChens.begin(); it != pChen->dataChens.end(); it++) {
// 快速結束
if (flag == 1) {
return;
}
// 訪問該節點
dfs2(*it, beforeItems);
}
}
/**
* 訪問同一組內元素
* @param u 訪問的是節點 u
* @param beforeItems
*/
void dfs2(int u, vector<vector<int>> &beforeItems) {
// 如果該節點已經訪問過 或者該已經是 沒有解決方案狀態 直接退出
if (vis[u] == 2 || flag == 1) {
return;
}
// 說明圖有環 沒有解決方案 直接退出
if (vis[u] == 1) {
flag = 1;
return;
}
// 第一次訪問,還沒有結束
vis[u] = 1;
// 如果該節點沒有前置節點,直接訪問
if (beforeItems[u].empty()) {
result.push_back(u);
//設置節點訪問狀態爲完成
vis[u] = 2;
// 由於沒有對重複元素,缺少元素等做判斷,這裏直接使用最簡單直接的方式
// 只要結果數組中的值的個數大於 節點數量,說明肯定沒有解決方案,提前結束
if (result.size() > n_all) {
flag = 1;
return;
}
}
// 如果節點有前置節點
else {
for (int i = 0; i < beforeItems[u].size(); ++i) {
// 遞歸 先訪問前置節點
dfs2(beforeItems[u][i], beforeItems);
}
// 由於沒有對重複元素,缺少元素等做判斷,這裏直接使用最簡單直接的方式
// 只要結果數組中的值的個數大於 節點數量,說明肯定沒有解決方案,提前結束
if (result.size() > n_all) {
flag = 1;
return;
}
// 訪問完前置節點之後,再訪問 u 號節點
else {
result.push_back(u);
// 同時將節點訪問狀態設置成 2 ,即該節點訪問完成
vis[u] = 2;
// 由於沒有對重複元素,缺少元素等做判斷,這裏直接使用最簡單直接的方式
// 只要結果數組中的值的個數大於 節點數量,說明肯定沒有解決方案,提前結束
if (result.size() > n_all) {
flag = 1;
return;
}
}
}
}
};
int main() {
// int n = 8, m = 2;
int n = 5, m = 3;
// vector<int> group = { -1, -1, 1, 0, 0, 1, 0, -1 };
// vector<vector<int>> beforeItems = { {},
// { 6 },
// { 5 },
// { 6 },
// { 3, 6 },
// {},
// {},
// {} };
// vector<int> group = {-1, -1, 1, 0, 0, 1, 0, -1};
// vector<vector<int>> beforeItems = {{},
// {6},
// {5},
// {6},
// {3},
// {},
// {4},
// {}};
// vector<int> group = { -1, 0, 0, -1 };
// vector<vector<int>> beforeItems = { {},
// { 0 },
// { 1, 3 },
// { 2 } };
vector<int> group = { 0,
0,
2,
1,
0 };
vector<vector<int>> beforeItems = { { 3 },
{},
{},
{},
{ 1, 3, 2 } };
Solution *pSolution = new Solution;
const vector<int> &vector1 = pSolution->sortItems(n, m, group, beforeItems);
for (int i = 0; i < vector1.size(); ++i) {
cout << vector1[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
運行結果
有點菜,有時間再優化一下。
3、總結
書上的代碼直接運行絕大部分是對的,但是總有一些軟件的更新使得作者無能爲力。之前的API是對的,但是之後就廢棄了或修改了是常有的事。所以我們需要跟蹤源代碼。這只是一個小小的問題,如果沒有前輩的無私奉獻,很難想象我們自己一天能學到多少內容。感謝各位前輩的辛勤付出,讓我們少走了很多的彎路!
點個贊再走唄!歡迎留言哦!