摆动序列
题目描述
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
分析
这道题和之前所做的合唱队形有点像,但是这不是求解最大增序列,而是增减交替的序列,首选还是一个一个分析,如果所给序列的长度只有一,那么返回的子序列的长度也是一,如果序列的长度超过一个,就可以使用两个变量up,down来计算上升和下降的个数,这里的增加数目是在对方的基础上进行的,因为要计算最长的子序列而不是最长增序列或者减序列。代码如下。
int wiggleMaxLength(int* nums, int numsSize){
if(numsSize<2) return numsSize;
int up=1;
int down=1;
for(int i=1;i<numsSize;i++){
if(nums[i]>nums[i-1]){
up=down+1;
}
else if(nums[i]<nums[i-1]){
down=up+1;
}
}
return (up>down)?up:down;
}
零钱兑换
题目描述
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。
分析
这是一个很经典的背包问题,背包是固定的,物品是多个的,我们只要求把背包填满就好了,这里使用动态规划,或者DFS加剪枝都可以,我使用的是DP,至于为什么不用DFS,因为不会我写完了看题解才发现可以使用这种方法,这里可以使用一个数组dp来接收局部最优解。dp[0]初始化0,其他初始化足够大的数,因为求最少的数目,我这里初始化为100000,只需要满足测试用例的要求即可。代码如下。
int coinChange(int* coins, int coinsSize, int amount){
int* dp=(int *)malloc((amount+1)*sizeof(int));
dp[0]=0;
for(int i=1;i<=amount;i++){
dp[i]=100000;
}
for(int i=1;i<=amount;i++){
for(int j=0;j<coinsSize;j++){
if(coins[j]<=i){
dp[i]=dp[i]<dp[i-coins[j]]+1?dp[i]:dp[i-coins[j]]+1;
}
}
}
return dp[amount]>amount?-1:dp[amount];
}
这里也copy一下其他人的题解,关于使用DFS和BFS来求解的。实属强者,LeetCode的算法大佬太多了,膜拜。
以下为DFS
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
coins.sort(reverse=True)
self.res = float("inf")
def dfs(i, num, amount):
if amount == 0:
self.res = min(self.res, num)
return
for j in range(i, len(coins)):
# 剩下的最大值都不够凑出来了
if (self.res - num) * coins[j] < amount:
break
if coins[j] > amount:
continue
dfs(j, num + 1, amount - coins[j])
for i in range(len(coins)):
dfs(i, 0, amount)
return self.res if self.res != float("inf") else -1
以下为BFS
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
from collections import deque
queue = deque([amount])
step = 0
visited = set()
while queue:
n = len(queue)
for _ in range(n):
tmp = queue.pop()
if tmp == 0:
return step
for coin in coins:
if tmp >= coin and tmp - coin not in visited:
visited.add(tmp - coin)
queue.appendleft(tmp - coin)
step += 1
return -1
整数拆分
题目描述
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
分析
这里需要注意审题, 我刚开始读题不仔细,以为是分解成两个正整数,兴致勃勃的开始做题,不到两分钟代码就写好了,心里不禁想到,中等题就这,结果不小心瞟到了题目的要求,至少,顿时麻了,赶紧删掉了自己刚写好的代码,并且内心说了句,私密马赛。是我愚蠢了,那么重新分析,既然是至少,那么就不限制分解成2个了,但是想了想,不太好解,如果自底向上,往上也不好分析,因为这个不像之前的蜜蜂和走楼梯,后一个的问题只依赖于前一个和前两个问题的最优解。这里看了一下别人的思路,发现宝藏,一个人的思路是这样,根据数学的规律来解这道题,可以大大简化题目的复杂度,求解y=(n/x)^x的最大解,并且最大解的时候x的值为多少,最后算出来x=e的时候y最大,所以只要多分解出e得到的成绩就会越大,但是题目要求为正整数,所以从2和3中选择,这里随便取一个n实验一下,比如12=2+2+2+2+2+2=3+3+3+3,2^6=64,3^4=81,很显然,3比2具有更大的乘积,所以就将所给的n尽量分解成3,能够得到最大的乘积。代码如下。
int integerBreak(int n){
int a=1;
if(n==2){
return 1;
}else if(n==3){
return 2;
}else if(n==4){
return 4;
}else{
while(n>4){
n-=3;
a*=3;
}
return a*n;
}
}
总结
dp的思想还是局部最优解的推进,但是可以使用数学知识来简化问题。