贪心算法(2)

题一.摆动序列(LeetCode)

题目描述

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

  • 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

示例 1:

输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

示例 2:

输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8)。

示例 3:

输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2

题目分析

因为一段相邻的相同的相同元素中我们最多只能选择其中的一个,所以我们可以忽略相邻的相同元素。现在我们可以假定序列中任意两个相邻元素都不相同,即要么左边大于右边,要么右边大于左边。

利用示例2分析,解释中长度为7的序列有 [1, 17, 10, 13, 10, 16, 8] 。对应着下图可以发现,拐点或者极值点许多在示例的序列中。自然想到,将非极值点的替换成极值点 [1, 17, 5, 15, 5, 16, 8],可以发现仍是摆动序列,且摆动幅度更大。

由此,我们可以考虑采用贪心策略—在上升和下降的某一段中尽量的选择最大的和最小的。因为,其中的过度元素可以换成邻近的极值点。

如何转化成代码呢?我们可以考虑设两个值left 和 right。left的值可以通过循环中将上一个循环中的right赋值给下一个循环中的left。

left=nums[i]-nums[i-1]
right=nums[i+1]-nums[i]

还需要考虑一下中间出现无波动的情况,即连续几个值相等。

考虑到重复的值对摆动序列的长度没有影响,则可以直接忽略。即,若是其差值为0,则continue跳转到下一次循环。

考虑一下初始化问题,对于 i = 0的情况,除非nums[0] = nums[1] = ...,别的时候nums[0]都可以作为序列的一元,即ret=1。可以将left赋值为0,则在第一个循环的时候,通过条件判断对ret赋值。

题解 

class Solution {
public:
	int wiggleMaxLength(vector<int>& nums) {
		int left = 0, right = 0;
		int n = nums.size();
		if (n < 2) return n;
		int ret = 0;
		for (int i = 0; i < n - 1; i++)
		{
			right = nums[i + 1] - nums[i];
			if (right == 0) continue;
			if (left*right <= 0) ret++;
			left = right;
		}
		return ret + 1;
	}
};

题二.最长递增子序列(LeetCode)

题目描述

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

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

题目分析 

因为我们最后需要的是子序列的长度,因此我们可以采用贪心策略:我们不关心这个最长子序列长什么样,我们仅关心“最后一个元素”是谁,跟nums[i]是否能拼在一起。且我们注意到拼的一起的元素在不影响序列长度的条件下,越小越好。

我们可以采用逐步交换的思路,初始化子序列第一个元素,然后比较下一个元素。

  1. 若是下一个元素X小于前一个位置的元素,则可以考虑替换。
  2. 若是下一个元素X大于前一个位置的元素,则可以将其与下一个位置的元素比较。

持续如上操作,直至序列结束。 

优化时间复杂度:可以用二分查找的方法确定X的插入位置。 

题解 

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		int n = nums.size();
		vector<int> ret;
		ret.push_back(nums[0]);
		for (int i = 1; i < n; i++)
		{
			if (nums[i] > ret.back())
			{
				ret.push_back(nums[i]);
			}
			else
			{
				int left = 0, right = ret.size() - 1;
				while (left < right)
				{
					int mid = (left + right) >> 1;
					if (nums[i] > ret[mid]) left = mid + 1;
					else right = mid;
				}
				ret[left] = nums[i];
			}
		}
		return ret.size();
	}
};

题三.递增三元子序列(LeetCode)

题目描述 

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i,j,k) 且满足i<j<k,使得 nums[i]<nums[j]<nums[k] ,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

题目分析 

简化版的题二,但是可以简化一下空间,优化一下思路:只需要满足三元组,就可以return true。

题解 

class Solution {
public:
	bool increasingTriplet(vector<int>& nums) {
		int n = nums.size();
		vector<int> ret;
		ret.push_back(nums[0]);
		ret.push_back(INT_MAX);
		for (int i = 1; i < n; i++)
		{
			if (nums[i] > ret[1]) return true;
			else if (nums[i] > ret[0]) ret[1] = nums[i];
			else ret[0] = nums[i];
		}
		return false;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值