Thanks to visit codestin.com
Credit goes to fx-zpy.github.io

LeetCode几道dp中等题


摆动序列

题目描述

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。例如, [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的思想还是局部最优解的推进,但是可以使用数学知识来简化问题。


文章作者: fx-zpy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 fx-zpy !
评论
  目录