Leetcode 1203. 項目管理

Leetcode 1203. 項目管理

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是對的,但是之後就廢棄了或修改了是常有的事。所以我們需要跟蹤源代碼。這只是一個小小的問題,如果沒有前輩的無私奉獻,很難想象我們自己一天能學到多少內容。感謝各位前輩的辛勤付出,讓我們少走了很多的彎路!

點個贊再走唄!歡迎留言哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章