1. 程式人生 > 其它 >LeetCode 1631. 最小體力消耗路徑【並查集】

LeetCode 1631. 最小體力消耗路徑【並查集】

技術標籤:演算法演算法圖論golang並查集leetcode

1631. 最小體力消耗路徑

你準備參加一場遠足活動。給你一個二維rows x columns的地圖heights,其中heights[row][col]表示格子(row, col)的高度。一開始你在最左上角的格子(0, 0),且你希望去最右下角的格子(rows-1, columns-1)(注意下標從 0 開始編號)。你每次可以往 上,下,左,右四個方向之一移動,你想要找到耗費 體力 最小的一條路徑。

一條路徑耗費的 體力值是路徑上相鄰格子之間 高度差絕對值的 最大值決定的。

請你返回從左上角走到右下角的最小體力消耗值 。

示例 1:

輸入:heights = [[1,2,2],[3,8,2],[5,3,5]]
輸出:2
解釋:路徑 [1,3,5,3,5] 連續格子的差值絕對值最大為 2 。
這條路徑比路徑 [1,2,2,2,5] 更優,因為另一條路徑差值最大值為 3 。

示例 2:

輸入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
輸出:0
解釋:上圖所示路徑不需要消耗任何體力。

解法:

首先想要求解這種最短路徑會想到動態規劃或Dijkstra演算法

但這題可以上下左右移動,推不出遞推公式;最短路徑演算法的證明過程理解起來又比較繁瑣,所以選擇了並查集來解:

1、二維陣列的點集可以轉換為有權無向圖的邊,邊的權重就是相鄰兩個點的abs(高度差)、每個點的唯一編號可以通過 i*len(arr[0])+j來表示

舉例:與(0, 0)聯通兩個頂點和邊權分別是(0,1) abs(heights[0][0] -heights[0][1]),(1,0)abs(heights[0][0] -heights[1][0])

遍歷二維陣列得到edge陣列

2、根據邊權升序排序edge陣列

3、遍歷edge陣列,依次向並查集新增邊,直到(0,0)點與(len,len)點聯通:最後新增的這條邊的權重,即為整個通路的最小權重

這裡的2、3兩步有沒有克魯斯卡演算法的影子?哈哈

上程式碼:

func minimumEffortPath(heights [][]int) int {
	if len(heights) <= 0 {
		return 0
	}
	var edges [][]int
	// 構造邊和權值
	n, m := len(heights), len(heights[0])
	for i := range heights {
		for j, h := range heights[i] {
			id := i * m + j
			if i > 0 {
				edges = append(edges, []int{id - m, id, abs(h - heights[i-1][j])})
			}
			if j > 0 {
				edges = append(edges, []int{id - 1, id, abs(h - heights[i][j-1])})
			}
		}
	}
	// 根據權值排序
	sort.Slice(edges, func(i,j int) bool {
		return edges[i][2] < edges[j][2]
	})
	// 使用並查集,新增權值並維護max,只要0和i * len(heights) + j聯通,則證明是最短路徑了
	u := NewUnion()
	s :=0
	e := (n-1) * m + m-1
	for _, edge := range edges {
		u.merge(edge[0], edge[1])
		// 因為權值是升序,所以可認為最後一次加入的邊,肯定是需要用到的最大權值
		if u.search(s) == u.search(e) {
			return edge[2]
		}
	}
	fmt.Println(u.m,e)
	return 0
}

type union struct {
	m map[int]int
}

func NewUnion() *union {
	return &union{m: make(map[int]int)}
}

func (u *union) search(i int) int {
	p, ok := u.m[i]
	if !ok {
		return i
	}
	if u.m[p] != p {
		u.m[i] = u.search(p)
	}
	return u.m[i]
}

func (u *union) merge(i,j int) {
	pI, pJ := u.search(i), u.search(j)
	if pI == pJ {
		return
	}
	u.m[pI] = pJ
}

func abs(i int) int {
	if i < 0 {
		i = -i
	}
	return i
}