最长上升子序列的学习

最长上升子序列类的经典解法

(大部分内容都是知乎大神pecco的内容 在此写下博文为了自己更好的消化)
转移函数:dp[i]表示以nums[i]为最后一个元素的上升子序列的长度
d p [ i ] = m a x d p [ j ] + 1 0 ≤ j < i a n d n u m s [ i ] < n u m s [ j ] dp[i] = max\quad dp[j]+1 \qquad 0 \leq j < i \quad and \quad nums[i] < nums[j] dp[i]=maxdp[j]+10j<iandnums[i]<nums[j]

可以写出代码:

int LIS(vector<int>& nums)
{
	//leetcode上的答案
	int n = nums.size();
	if(n ==0)
	{
		return 0;
	}
	
	vector<int> dp(n,0);
	dp[0] = 1;
	
	for(int i =0; i < nums.size(); i++)
	{
		//对于第i个元素 从第0个元素开始到第i-1 个元素
		for(int j = 0; j<i ; j++)
		{
			if(nums[j] < nums[i])
			{
				dp[i] = max(dp[i], dp[j] + 1);//当前的dp[i]中和新找到的dp[j]+1中大的那个
			}
		}
	}
	return *max_element(dp.begin(),dp.end());
}

贪心+二分

如果我们需要使得上升子序列尽可能的长,则我们需要使得上升子序列上升的尽可能的慢。
这次的转移函数是:
dp[len] 表示长度为len的上升子序列的末尾元素的最小值。(pecco的知乎笔记有例子)

dp的性质:

  1. dp[len]是单调递增的。
  2. 如果nums[i] < dp[len] ,在dp[1,2,…,len]中找到 j j j使得 d p [ j − 1 ] < n u m s ≤ d p [ j ] dp[j-1]<nums\leq dp[j] dp[j1]<numsdp[j],将dp[j]更新为nums[i]

leetcode上的解法(手写了二分)

int LIS(vector<int>& nums)
{
	int len = 1, n = nums.size();
	if (n == 0)
		return 0;
	
	vector<int> dp(n+1,0);
	dp[len] = nums[0];
	for(int i = 1; i <n; i++)
	{
		if(nums[i] > dp[len])
		{
			dp[++len] = nums[i]; 
		}
		else
		{
			int l = 1,r = len, pos = 0;
			while(l<=r)
			{
				int mid = (l+r)/2;
				if(d[mid] < nums[i])
				{
					pos = mid;
					l = mid+1;
				}
				else
				{
					r = mid-1;
				}
			}
			dp[pos+1] = nums[i];
		}
	}
	return len;
}

得到的pos是最后一个满足 dp[pos] < nums[i]的index,也就是说 d p [ p o s + 1 ] ≥ n u m s [ i ] dp[pos+1] \geq nums[i] dp[pos+1]nums[i]
其实可以注意到leetcode 写二分的一个模板;

int left = index1, right = index2+1;
int ans = index1 -1;
while(left <= right)
{
	int mid = (left+ right) /2;
	if(func(mid))
	{
		ans = mid;
		left = mid+1;
	}
	else
	{
		right = mid-1;
	}
}

得到的ans是递增序列[left, right-1]中最后一个满足func的index。

pecco的知乎算法笔记中用一个函数来代替了,看起来更加简洁一些:

int len = 0;
for(int i  = 0;  i< nums[i].size(); i++)
{
	if(nums[i] >dp[len])
	{
		dp[++len] = nums[i];
	}
	else
	{
		*lower_bound(dp+1, dp+len, nums[i]) = nums[i];
	}
}
return len;

*lower_bound(dp+1,dp+len, nums[i])就是寻找1 到 len-1之间第一个大于等于 nums[i]的元素

拓展

最长不减子序列

int len = 0;
for(int i = 0; i < n; i++)
{
	if(dp[len] <= nums[i])
	{
		dp[++len] = nums[i]; //等于时允许插入
	}
	else
	{
		*upper_bound(dp+1, dp+len, nums[i]) = nums[i]; //
	}
}

可以用 [4 8 9 5 6 7 2 7 6] 做实验。

最长递减子序列

int len = 0;
for(int i = 0; i < n; i++)
{
	if(dp[len] > nums[i])
	{
		dp[++len] = nums[i]; 
	}
	else
	{
		*lower_bound(dp+1, dp+len, nums[i], greater<int>()) = nums[i]; //
	}
}

最长不增子序列

int len = 0;
for(int i = 0; i < n; i++)
{
	if(dp[len] >= nums[i])
	{
		dp[++len] = nums[i]; //等于时允许插入
	}
	else
	{
		*upper_bound(dp+1, dp+len, nums[i],greater<int>()) = nums[i]; //
	}
}
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值