Leetcode 565 & 240 題解

近期刷題有一個新的體會,那就是不要想着 pass 就完事了,得想辦法將本身的運行時間提速golang

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 相同時則終止,最後取最大值便可,相應代碼以下bash

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
}

複製代碼

這麼寫代碼的話雖然也經過了,可是看了下耗時仍是很不友好,竟然是千毫秒級別app


後面仔細想一想,仍是拿最初的數組 [5,4,0,3,1,6,2] 作例子,從索引 0 出發獲得的集合爲 [5,6,2,0] ,然而本質上若是從索引值爲 2 5 或 6 出發的話獲得的也是這個集合,由於它們始終湊成一個環。既然如此,那麼咱們能夠**設置一個布爾數組,判斷當前值若是以前已經出如今集合 S 內就不須要再重複計算,具體代碼以下ui

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 毫秒spa


相似的操做還有leetcode 240: 二維有序數組排序,題目大意是給定一個 m*n 的二維數組,從左到右以及從上到下都是有序的,以下面所示:code

[
  [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 則爲 falsecdn

首先最直觀的思惟就是遍歷矩陣的每一行,判斷當前要查找的值是否在當前行第 0 個元素和最後一個元素之間,若是是則對當前行的數組作二叉搜索blog

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 時按列遍歷


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

仍是拿上面的數組爲例,好比我想搜索 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
}
複製代碼

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

相關文章
相關標籤/搜索