70.爬楼梯

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func climbStairs(n int) int {
	cache := make([]int, n+1)
	cache[0], cache[1] = 1, 1

	for i := 2; i <= n; i++ {
		cache[i] = cache[i-1] + cache[i-2]
	}

	return cache[n]
}

最简单的一维 DP 题,遇见属于白给,最标准的没有套路的 DP,完全不需要解释。

198.打家劫舍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func rob(nums []int) int {
	cache := make([]int, len(nums)+1)
	cache[0], cache[1] = 0, nums[0]

	for i := 2; i <= len(nums); i++ {
		cache[i] = max(nums[i-1]+cache[i-2], cache[i-1])
	}

	return cache[len(nums)]
}

经典打家劫舍,难度同样属于白给。

139. 单词拆分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func wordBreak(s string, wordDict []string) bool {
	words := make(map[string]bool, len(wordDict))
	for _, w := range wordDict {
		words[w] = true
	}

	n := len(s)
	cache := make([]bool, n+1)
	cache[0] = true

	for i := 1; i <= n; i++ {
		for j := 0; j < i; j++ {
			if cache[j] && words[s[j:i]] {
				cache[i] = true
				break
			}
		}
	}

	return cache[n]
}

零钱兑换变体,完全背包问题。有一定难度,属于做了多少遍都会忘的题。

别忘记一定会有一个双循环,内层循环用来遍历要求的东西。该题内层循环是遍历 s[j:i] 子串,同时与 words 词表进行比对。当 cache[j] 存在且 words[s[j:i]] 存在,也就意味着这两样东西可以拼接在一起,那此时 cache[i] 就可以存在了。

322. 零钱兑换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func coinChange(coins []int, amount int) int {
	cache := make([]int, amount+1)

	for i := 1; i <= amount; i++ {
		cache[i] = math.MaxInt
		for j := 0; j < len(coins); j++ {
			if i >= coins[j] && cache[i-coins[j]] != math.MaxInt {
				cache[i] = min(cache[i], cache[i-coins[j]]+1)
			}
		}
	}

	if cache[amount] == math.MaxInt {
		return -1
	}

	return cache[amount]
}

标准的完全背包问题,有一定难度。

这里的内层循环是遍历 coins,同时做一个条件判断:当前外层的金额 i 是否不小于硬币面值 && 金额 i - coin 可以被 coins 表示。如果通过判断,则通过状态转移方程 cache[i] = min(cache[i], cache[i-coin]+1) 来进行计算。

300. 最长递增子序列

DP 解法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func lengthOfLIS(nums []int) int {
	n := len(nums)
	cache := make([]int, n)
	for i := range n {
		cache[i] = 1
	}

	for i := 1; i < n; i++ {
		for j := 0; j < i; j++ {
			if nums[i] > nums[j] {
				cache[i] = max(cache[i], cache[j]+1)
			}
		}
	}

	return slices.Max(cache)
}

DP 解法类似上面的 LC139 。

要计算 cache[i] ,需要看 i 之前的位置 j ,如果 nums[i] > nums[j] ,意味着 nums[i] 可以接在以 nums[j] 结尾的那个递增子序列的后面,再根据状态转移方程求解 cache[i] = max(cache[i], cache[j]+1)

更优的二分查找解法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func lengthOfLIS(nums []int) int {
	array := make([]int, 0)

	for _, num := range nums {
		i := binarySearch(array, num)
		if i == len(array) {
			array = append(array, num)
		} else {
			array[i] = num
		}
	}

	return len(array)
}

func binarySearch(nums []int, target int) int {
	left, right := 0, len(nums)-1
	for left <= right {
		mid := left + (right-left)/2
		if nums[mid] < target {
			left = mid + 1
		} else if nums[mid] > target {
			right = mid - 1
		} else {
			return mid
		}
	}
	return left
}

二分查找解法比较好理解,主要是在主函数中的处理:

  • i == len(array) 时,表示有一个新的元素要进入候选序列中,否则只更改 array[i] = num