Leetcode 565 & 240 題解

HipHope發表於2019-02-26

近期刷題有一個新的體會,那就是不要想著 pass 就完事了,得想辦法將自己的執行時間提速

leetcode 565 為例,這題大意如下:一個長度為 n 的整形陣列 A ,每個數的範圍都在 0 到 n-1 以內,假設從陣列的某個索引 i 開始,依次執行 A[i] 求值操作,將得到的數加入到集合 S 中,直到集合 S 出現重複元素為止,即中止運算。例如陣列 [5,4,0,3,1,6,2] ,我們從 0 開始,依次執行求值操作,有:

A[0]=5 -> A[5]=6 ->
A[6]=2 -> A[2]=0
-x-> A[0]=5
複製程式碼

在上個例子中我們的 S 集合為 {5,6,2,0}。現在給定一個陣列,我們要求出這個集合的最長長度。

剛開始看這題我感覺很容易啊,直接模擬不就完事了嗎,遍歷陣列的每個值,將索引 i 作為起點並依次執行 A[i] 操作(計數當前的操作次數),當某次求值操作與起點 i 相同時則終止,最後取最大值即可,相應程式碼如下

func arrayNesting(nums []int) int {
	n := len(nums)
	if n <= 1 {
		return 1
	}
	result := 0
	for i := 0; i < n; i++ {
		s, current, tmpl := i, nums[i], 1
		for current != s {
			current = nums[current]
			tmpl++
		}
		result = maxValue(result, tmpl)
	}
	return result
}

func maxValue(a, b int) int {
	if a > b {
		return a
	}
	return b
}

複製程式碼

這麼寫程式碼的話雖然也通過了,但是看了下耗時還是很不友好,居然是千毫秒級別

Leetcode 565 & 240 題解

後面仔細想想,還是拿最初的陣列 [5,4,0,3,1,6,2] 做例子,從索引 0 出發得到的集合為 [5,6,2,0] ,然而本質上如果從索引值為 2 5 或 6 出發的話得到的也是這個集合,因為它們始終湊成一個環。既然如此,那麼我們可以**設定一個布林陣列,判斷當前值如果之前已經出現在集合 S 內就不需要再重複計算,具體程式碼如下

func arrayNesting(nums []int) int {
	n := len(nums)
	if n <= 1 {
		return 1
	}
	visited := []bool{}
	for i := 0; i < n; i++ {
		visited = append(visited, false)
	}
	result := 1
	for i := 0; i < n; i++ {
		s, current, tmpl := i, nums[i], 1
		visited[s] = true
		if visited[current] {
			continue
		}
		for current != s {
			current = nums[current]
			visited[current] = true
			tmpl++
		}
		result = maxValue(result, tmpl)
	}
	return result
}

func maxValue(a, b int) int {
	if a > b {
		return a
	}
	return b
}
複製程式碼

結果這個執行時間就比之前好多了,直接 20 毫秒

Leetcode 565 & 240 題解

類似的操作還有leetcode 240: 二維有序陣列排序,題目大意是給定一個 m*n 的二維陣列,從左到右以及從上到下都是有序的,如下面所示:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30],
]
複製程式碼

現在就讓你在這個矩陣內快速查詢某個值,找到則返回 true ,找不到則返回 false,上述例子中搜尋 5 則為 true,搜尋 20 則為 false

首先最直觀的思維就是遍歷矩陣的每一行,判斷當前要查詢的值是否在當前行第 0 個元素和最後一個元素之間,如果是則對當前行的陣列做二叉搜尋

func searchMatrix(matrix [][]int, target int) bool {
	m := len(matrix)
	if m == 0 {
		return false
	}
	n := len(matrix[0])
	if n == 0 {
		return false
	}
	result := false
	for _, row := range matrix {
		if target >= row[0] && target <= row[n-1] {
			result = binarySearch(row, n, target)
		}
		if result == true {
			return true
		}
	}
	return false
}

func binarySearch(row []int, n, target int) bool {
	left, right := 0, n-1
	for left <= right {
		m := left + (right-left)/2
		if row[m] == target {
			return true
		} else if target < row[m] {
			right = m - 1
		} else {
			left = m + 1
		}
	}
	return false
}
複製程式碼

這樣的效果雖然能通過,且時間也不算慢,但在此題執行時間的排名裡只超過了 31.25% 的 golang coder,那麼就說明了 O(m*log2(n)) 並不是這個演算法的最優時間複雜度。另外理論上 m 要小於 n 才能算是比較好的演算法,所以實際上我剛才的解法也忽視了分類討論的情況:當 m < n 時按行遍歷,當 m > n 時按列遍歷

Leetcode 565 & 240 題解

像我上面最初的想法,本質上只利用了從左到右有序這個性質,並沒有充分利用從上到下有序這個性質。從這個思維點出發,怎麼樣才能讓這兩個性質都一起用起來,從而加快速度?

還是拿上面的陣列為例,比如我想搜尋 6 ,我可以從最右上角的數字 15 出發,因為 15 在最右上角,結合矩陣的性質, 15 左邊的元素都比 15 小(對應行), 15 下邊的元素都比 15 大(對應列)

[
  [1,   4,  7, 11, 15]<-
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30],
]
複製程式碼

那麼 6 比 15 小,說明 6 不可能與 15 同列,於是我們陣列的範圍變為:

[
  [1,   4,  7, 11]<-
  [2,   5,  8, 12]
  [3,   6,  9, 16]
  [10, 13, 14, 17]
  [18, 21, 23, 26]
]
複製程式碼

同理 6 比 11 和 7 小,所以陣列的搜尋範圍為:

[
  [1,   4,  7]<-
  [2,   5,  8]
  [3,   6,  9]
  [10, 13, 14]
  [18, 21, 23]
]

[
  [1,   4]<-
  [2,   5]
  [3,   6]
  [10, 13]
  [18, 21]
]
複製程式碼

繼續看最右上角的數,這次 6 比 4 和 5 都大,說明 6 不可能跟 4 或 5 同行,所以搜尋範圍又縮小為:

[
  [2,   5]<-
  [3,   6]
  [10, 13]
  [18, 21]
]

[
  [3,   6]<-
  [10, 13]
  [18, 21]
]
複製程式碼

現在我們找到 6 了,可以看到這個演算法的時間複雜度最壞情況也是 O(m+n) ,比剛才有了一定的提升

func searchMatrix(matrix [][]int, target int) bool {
	m := len(matrix)
	if m == 0 {
		return false
	}
	n := len(matrix[0])
	if n == 0 {
		return false
	}
	rightUp, currentRow, currentColumn := -1, 0, n-1
    for currentRow >= 0 && currentRow < m
    && currentColumn >= 0 && currentColumn < n {
		rightUp = matrix[currentRow][currentColumn]
		if rightUp == target {
			return true
		} else if rightUp > target {
			currentColumn--
		} else {
			currentRow++
		}
	}
	return false
}
複製程式碼

提交上去後,執行時間很美滿

Leetcode 565 & 240 題解


相關文章