1. 程式人生 > >leetcode--802. Find Eventual Safe States題解

leetcode--802. Find Eventual Safe States題解

題目

In a directed graph, we start at some node and every turn, walk along a directed edge of the graph. If we reach a node that is terminal (that is, it has no outgoing directed edges), we stop.

Now, say our starting node is eventually safe if and only if we must eventually walk to a terminal node. More specifically, there exists a natural number K so that for any choice of where to walk, we must have stopped at a terminal node in less than K steps.

Which nodes are eventually safe? Return them as an array in sorted order.

The directed graph has N nodes with labels 0, 1, …, N-1, where N is the length of graph. The graph is given in the following form: graph[i] is a list of labels j such that (i, j) is a directed edge of the graph.

Example:

Input: graph =
[[1,2],[2,3],[5],[0],[5],[],[]] Output: [2,4,5,6]

Here is a diagram of the above graph. 在這裡插入圖片描述 Note:

  1. graph will have length at most 10000.
  2. The number of edges in the graph will not exceed 32000.
  3. Each graph[i] will be a sorted list of different integers, chosen within the range [0, graph.length - 1].

思路與解法

如果從一個點S

出發的所有路徑Paths最終都到達某個終點Ts(可以不為同一個終點),則這個點S即為最終安全的點(Eventually Safe)。這道題目的要求是讓我們找出所求滿足這樣條件的點。 此題目與310. Minimum Height Trees方法二解法類似,同樣可以採用剝洋蔥的思想:

  1. 最外圍的點(出度為0)勢必滿足條件,然後將所有與最外圍節點相連的邊刪除,此時次外圍的點可能就會變為最外圍的點(之所以是“可能”,是因為出度減1後仍然不為0或者次外圍的點會和其他點形成環而造成出度永遠不為0);
  2. 然後利用遞迴的思想不斷刪減掉最外圍的點及與其相連的邊,暴露出次外圍的點,直到剩下的點中沒有出度為0的點;
  3. 在計算過程中,不斷刪減掉的點即為滿足條件的點,將其存放到陣列中最終進行排序即可。

程式碼實現

此演算法我使用go語言實現

// nodes 切片存放最終滿足條件的節點
// outDegree 統計每個節點的出度
// prevNodes 存放每個節點的前向邊的節點
// queue 利用佇列來實現“剝洋蔥”
func eventualSafeNodes(graph [][]int) []int {
	nodes := make([]int, 0)
	outDegree := make(map[int]int)
	prevNodes := make(map[int][]int)
	queue := make([]int, 0)
	for u, vs := range graph {
		outDegree[u] = len(vs)	// 節點u的出度即為len(vs)
		if outDegree[u] == 0 {	// 當outDegree[u]的初度為零時,將u加入到nodes、queue中
			queue = append(queue, u)
			nodes = append(nodes, u)
		}

		for _, v := range vs {	// 統計前向邊
			prevNodes[v] = append(prevNodes[v], u)
		}
	}
	// 利用佇列的入隊出隊來實現“遞迴的思想”
	for len(queue) != 0 {
		node := queue[0]		// 獲取佇列頭節點
		queue = queue[1:]	// 出隊
		for _, anotherNode := range prevNodes[node] {	// 遍歷與node相連的前向邊
			outDegree[anotherNode]--
			if outDegree[anotherNode] == 0 {	// 當anotherNode的初度為零時,將anotherNode加入到nodes、queue中
				nodes = append(nodes, anotherNode)
				queue = append(queue, anotherNode)
			}
		}
	}
	sort.Ints(nodes)	// 將nodes進行從大到小排序
	return nodes
}

總結與思考

在演算法分析與設計的過程中,我們首先提取題目的主要資訊,並對題目進行一定的抽象;通常上題目並不會直觀的考察某個知識點。所以,這就需要我們進行一定的類比,將某種演算法應用到實際的題目中。以這道題目為例,“剝洋蔥”是生活中的一個例子,其思想經過一定的抽象即可應用的該題目中;另外,這也是一種遞迴的想法,每個子問題都和父問題除了規模外都一致;最後,實現遞迴的思想不一定非要用遞迴函式,佇列的入隊出隊也可以實現,寫法同樣很簡便。