214.最长[公共/递增]子序列

最长连续递增序列

/*
674. 最长连续递增序列
给定一个未排序的整数数组,找到最长递增子序列的个数。
*/
func findLengthOfLCIS1(nums []int) int {
	n := len(nums)
	if n < 2 {
		return 1
	}
	l, r, ans := 0, 1, 1
	for r < n {
		if nums[r] > nums[r-1] {
			r++
		} else {
			ans = max(r-l, ans)
			l = r
			r++
		}
	}
	ans = max(r-l, ans)
	return ans
}

func findLengthOfLCIS(nums []int) int {
	/*
		f[i]=x表示以下标i结尾的个元素中, 最长连续递增子序列为x
		递归公式:
			result = 1
			if nums[i+1]> nums[i]:
				f[i+1] = f[i] + 1
			else:
				result = max(f[i], result)
		初始化:
			dp[0..n] = 1
	*/
	n := len(nums)
	if n < 2 {
		return 1
	}
	// 题目保证nums最少有一个元素, 因此f[i]初始化为1
	f := make([]int, n)
	for i := 0; i < n; i++ {
		f[i] = 1
	}
	result := 1
	for i := 0; i < n-1; i++ {
		if nums[i+1] > nums[i] {
			f[i+1] = f[i] + 1
		} else {
			result = max(f[i], result)
		}
	}
	result = max(f[len(f)-1], result)
	return result
}

// func main() {
// 	fmt.Println(findLengthOfLCIS([]int{1, 3, 5, 4, 7}))
// }
/*

最长递增子序列

/*
300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。
例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
这道题可以看做是673的降级版
*/
func lengthOfLIS1(nums []int) int {
	/*
		这种方式虽然可以解决但是如果nums的数量级不能超过10**4, 因为进行了两次循环O(n*n)
	*/
	n := len(nums)
	f := make([]int, n)
	for i := 0; i < n; i++ {
		f[i] = 1
	}
	ans := 1
	for i := 0; i < n; i++ {
		for j := 0; j < i; j++ {
			if nums[i] > nums[j] {
				if f[i] < f[j]+1 {
					f[i] = f[j] + 1
				}
			}
		}
		ans = max(ans, f[i])
	}
	return ans
}

func lengthOfLIS2(nums []int) int {
	/*
		我们需要在上面方式的基础上坐下优化, 使用一个g[i]=x,记录长度为i的上升子序列的最小结尾数x
		上面方式我们需要在nums[j] < nums[i]时遍历[0,i)之间的某个索引j, 找出最大的f[j]是的f[i]最大
		如果g数组具有单调新的话, 我们可以直接通过二分找到符合g[idx] < nums[i]的分隔点idx(下标最大)

		证明g具有单调性:
		假设存在某个i和j, i<j 不满足单调性g[i]<g[j]:
		g[i]=g[j]=x:  这意味着x既可以是长度为i的最小结尾, 也可以是长度为j的最小结尾, 而由于i<j, 说明
		我们可以通过删除长度为j的子序列后面的元素(调整出来一个长度为i的子序列g[i]=x2), 这说明存在一个
		比x小的数x2使得g[i]=x2. 这和我们g的定义矛盾, 因此g[i]=g[j]=x, 不成立.

		g[i] > g[j]=x: 如果存在一个长度为j的合法上升子序列[最小结尾元素]为x的话, 由于i<j, 那么必然可以
		通过删除长度为j的子序列后面的元素(调整出来一个长度为i的子序列g[i]=x2),这说明存在一个比x小的数x2
		使得g[i]=x2. 这和我们g的定义矛盾, 因此g[i]>g[j]=x, 不成立.

		根据推论证明g[i]=g[j] 和g[i] > g[j]不成立, 可得g[i] < g[j]恒成立

		f[i]=x表示一个i结尾的最长上升子序列, 长度为x
	*/
	n := len(nums)
	f := make([]int, n+1) //因为f和g都是i结尾, 所以长度n+1
	g := make([]int, n+1)
	for i := 0; i < n+1; i++ {
		f[i] = 1     // f初始化, 就算只有一个元素也是1
		g[i] = 10001 // g不一样, 我们需要通过g二分找到t=nums[i], g[mid] < t的mid, 当长度从小到大更新
		// 时保证g[i]<g[i+1], 而为了保证g一直单调递增就需要g[i]足够大,
	}
	ans := 1 // 记录最大长度
	for i := 0; i < n; i++ {
		t := nums[i]
		l, r := 1, n // 长度最低为1, 最高为n, 二分长度
		for l < r {
			mid := (l + r) >> 1
			if g[mid] >= t {
				r = mid
			} else {
				l = mid + 1
			}
		}
		g[r] = t // g[r] = t既就是长度为r时的最小结尾元素
		f[i] = r // 同时更新f[i]对应的长度
		// fmt.Println(g)
		ans = max(ans, r)
	}
	return ans
}
func lengthOfLIS(nums []int) int {
	/*
		发现上面过程中f可以取消掉
	*/
	n := len(nums)
	g := make([]int, n+1)
	for i := 0; i < n+1; i++ {
		g[i] = 10001
	}
	ans := 1
	for i := 0; i < n; i++ {
		t := nums[i]
		l, r := 1, n
		for l < r {
			mid := (l + r) >> 1
			if g[mid] >= t {
				r = mid
			} else {
				l = mid + 1
			}
		}
		g[r] = t
		// fmt.Println(g)
		ans = max(ans, r)
	}
	return ans
}

// func main() {
// 	fmt.Println(lengthOfLIS([]int{7, 7, 7, 7, 7, 7, 7}))
// 	fmt.Println(lengthOfLIS([]int{0, 1, 0, 3, 2, 3}))
// 	fmt.Println(lengthOfLIS([]int{10, 9, 2, 5, 3, 7, 101, 18}))
// }

最长递增子序列

/*
673. 最长递增子序列的个数
给定一个未排序的整数数组,找到最长递增子序列的个数。
*/
func findNumberOfLIS(nums []int) int {
	/*
		根据674中的最长连续上升子序列, f[i]表示以nums[i]结尾的最长上升子序列长度
		初始化: 单个元素可以构成子序列, 因此f[i] = 1
		递推公式:
			枚举区间[0, i)的所有的nums[i], 如果满足nums[j] < nums[i], 说明nums[i]可以跟在nums[j]
			上面形成上升子序列, f[i] = f[j] + 1
		本题需要最长上升子序列的个数,因此需要额外定义 g[i] 为考虑以 nums[i] 结尾的最长上
		升子序列的个数。

		结合f[i]转移过程, 推导g[i]转义过程
		g[i]初始化1
		枚举区间[0, i)的所有的nums[i], 如果满足nums[j] < nums[i], 说明nums[i]可以跟在nums[j]上面形
		成上升子序列, 需要对f[i]和f[j]+1的大小关系进行分类讨论:
		f[i] < f[j] + 1, 可以直接使用f[j]+1更新f[i], 说明找到了一个更长的公共子序列因此g[i]=g[j]
		f[i] = f[j] + 1, 说明没有找到了一个更长的公共子序列, 而是找到一个符合条件的前驱, 需要将g[i] +=g[j]

		最终结果为: 假设最长上升子串为max, 最终答案为f[i]=max的g[i]累加和
	*/
	n := len(nums)
	f := make([]int, n)
	g := make([]int, n)
	for i := 0; i < n; i++ {
		f[i] = 1
		g[i] = 1
	}
	ans := 1
	for i := 0; i < n-1; i++ {
		for j := 0; j < i; i++ {
			if nums[i] > nums[j] {
				if f[i] < f[j]+1 {
					f[i] = f[j] + 1
					g[i] = g[j]
				} else if f[i] == f[j]+1 {
					g[i] += g[j]
				}
			}
		}
		ans = max(ans, f[i])
	}
	ret := 0
	for i := 0; i < n; i++ {
		if f[i] == ans {
			ret += g[i]
		}
	}
	return ret
}

// func main() {
// 	fmt.Println(findNumberOfLIS([]int{1, 3, 5, 4, 7}))
// }

最长公共子序列(LCS)->最长递增子序列(LIS)

/*
1713. 得到子序列的最少操作次数(最长公共子串问题)
给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。
每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在
中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。
请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。
一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序
得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列。

看三叶如何分析:
假设target长度为n, arr长度为m, target和arr的最长公共子序列长度为max, 那么我们只需要在arr中添加
n-max个数字即可让target成为arr的子序列. 因此可以看出这一道最长公共子序列问题

当朴素求解LCS问题复杂度为O(m*N), 使用状态定义f[i][j]表示a数组的前i个元素和b数组的前j个元素的最长
公共子序列. 因为本题数据范围10**5, 使用朴素LCS解法必定超时.

根据题目条件发现target有一个特点[元素互不相同], 当LCS问题有了一些限制之后,会导致一些奇特的性质.
根据三叶说明: 当其中一个数组元素互不相同时, LCS(最长公共子序列)问题可以转换为LIS(最长上升子序列)
. 最长上升子序列问题使用单调队列+二分进行求解, 复杂度为O(nlogn), 本题可以将LCS->LIS问题.

证明:
1. 为何其中一个数组元素互不相同, LCS问题可以转化为了LIS问题?
当a数组元素互不相同时, 那么首先a元素和其下标具有唯一映射关系. 然后我们思考a, b数组公共元素发生了什么?
每一个公共子序列,自然对应了一个下数组[升序的]. 如果a,b存在公共子序列, 那么其下标数组必定升序. 那么就符
合最长上升子序列问题这样LCS->LIS问题.


2.贪心求解 LIS 问题的正确性证明?
朴素的 LIS 问题求解,我们需要定义一个f[i]数组代表以nums[i]为结尾的最长上升子序列的长度为多少。
对于某个f[i]而言,我们需要往回检查[0, i-1]区间内,所有可以将nums[i]接到后面的位置j,在所有的f[j]+1中取
最大值更新 。因此朴素的 LIS 问题复杂度是O(n*n)的。

我们需要两个数组:
- f动规数组: 与朴素LIS解法一致, f[i]表示以nums[i]为结尾的上升子序列的最大长度
- g贪心数组: g[len]=x代表了上升子序列长度为len的最小结尾元素为x

根据力扣300题中的证明: 我们需要在nums[j] < nums[i]时遍历[0,i)之间的某个索引j, 找出最大的f[j]是的f[i]最大
如果g数组具有单调新的话, 我们可以直接通过二分找到符合g[idx] < nums[i]的分隔点idx(下标最大)

证明g具有单调性:
假设存在某个i和j, i<j 不满足单调性g[i]<g[j]:
g[i]=g[j]=x:  这意味着x既可以是长度为i的最小结尾, 也可以是长度为j的最小结尾, 而由于i<j, 说明
我们可以通过删除长度为j的子序列后面的元素(调整出来一个长度为i的子序列g[i]=x2), 这说明存在一个
比x小的数x2使得g[i]=x2. 这和我们g的定义矛盾, 因此g[i]=g[j]=x, 不成立.

g[i] > g[j]=x: 如果存在一个长度为j的合法上升子序列[最小结尾元素]为x的话, 由于i<j, 那么必然可以
通过删除长度为j的子序列后面的元素(调整出来一个长度为i的子序列g[i]=x2),这说明存在一个比x小的数x2
使得g[i]=x2. 这和我们g的定义矛盾, 因此g[i]>g[j]=x, 不成立.

根据推论证明g[i]=g[j] 和g[i] > g[j]不成立, 可得g[i] < g[j]恒成立

f[i]=x表示一个i结尾的最长上升子序列, 长度为x
*/
func minOperations(t []int, arr []int) int {
	n, m := len(t), len(arr)
	cache := make(map[int]int)
	// 使用map存储t中的键值关系
	for i := 0; i < n; i++ {
		cache[t[i]] = i
	}
	// 按arr中的顺序将与t公共元素的索引加入list, 因为t中的元素唯一, 两数组公共元素对应的索引必定升序
	// 那么够早的新数组list(存放两数组公共元素对应的索引), 其中公共子串索引必定升序, 这下问题转换成了300
	// 题中的最长公共上升子序列问题. 到了这里我才明白了什么叫做LCS->LIS, 直接把最长公共子串问题,转换成了
	// 索引对应的问题, 因为公共元素索引升序, 虽然arr中可能会用重复元素, 但是照样还是升序的.
	list := make([]int, 0)
	for i := 0; i < m; i++ {
		x := arr[i]
		if _, ok := cache[x]; ok {
			list = append(list, cache[x])
		}
	}

	MAX := int(math.Pow(2, 31))
	k := len(list)
	if k == 0 { // list大小为0说明了没用公共元素, 那么需要将t中的所有元素都加入arr中, 直接返回t长度就可以了
		return n
	}
	// f := make([]int, k)
	g := make([]int, k+1)
	for i := 0; i < len(g); i++ {
		g[i] = MAX
	}

	temp := 1
	for i := 0; i < k; i++ {
		l, r := 1, k
		for l < r {
			mid := (l + r) >> 1
			if g[mid] >= list[i] {
				r = mid
			} else {
				l = mid + 1
			}
		}
		// f[i] = r
		g[r] = list[i]
		temp = max(temp, r)
	}
	return n - temp
}

func main() {
	fmt.Println(minOperations([]int{6, 4, 8, 1, 3, 2}, []int{4, 7, 6, 2, 3, 8, 6, 1}))
}

参考:
三叶1713题解

posted @ 2022-01-13 17:00  楠海  阅读(55)  评论(0)    收藏  举报