动态规划
解题思路
将待求解问题分解成若干个不独立的子问题,求出子问题的解并记录在表中,当需要再次求解此子问题时,可以通过查表获得该子问题的解而不用再次求解,从而避免了大量的重复计算。最后结合这些子问题的解即可得到原问题的解。
解题步骤:
①分析问题的最优子结构性质:一个最优策略的子策略总是最优的
②建立递归关系(状态转移方程)
建立状态转移方程:
明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case。
③自下而上计算最优值
④根据计算最优值时得到的信息,构造最优解
斐波那契数列
问题描述
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。
题解
递归
class Solution {
public int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
return fib(n-1) + fib(n-2);
}
}
时间复杂度:O(2^n)
空间复杂度:O(2^n)
带备忘录的递归解法(自顶向下)
递归解法产生了大量的重复计算,时间效率不高,所以可以用一个备忘录来存储每次的结算结果,当计算一个值时先查看备忘录中是否有结果,若没有再计算。
class Solution {
public int fib(int n) {
int memo[] = new int[n+1];
return helper(memo,n);
}
public int helper(int memo[],int n){
if(n == 0 || n == 1) return n;
if(memo[n] != 0) return memo[n];
memo[n] = helper(memo,n-1) + helper(memo,n-2);
return memo[n];
}
}
时间复杂度:O(n)
空间复杂度:O(n)
动态规划(自底向上)
令dp[i]为f(i)的值。
class Solution {
public int fib(int n) {
int dp[] = new int[n+1];
dp[0] = 0;
if(n>0)
dp[1] = 1;
for(int i=2;i<=n;i++)
dp[i] = dp[i-1]+dp[i-2];
return dp[n];
}
}
凑零钱问题
问题描述
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
题解
①因为硬币的数量是无限的,因此每个硬币需要的最少数量问题是相互独立的,且最少需要的硬币数需要每种硬币都是最少的(如果要证明一般用数学归纳法证明),因此具有最优子结构性质,可以用动态规划求解。
②找递归关系,写出状态转移方程:
设dp[i]表示当目标金额为i时至少需要的硬币数为dp[i],当需要选择一种硬币时先进行判断,是选择当前硬币后最优还是不选择最优,做出最优的选择。
public class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount+1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
时间复杂度:O(n^k)(n指要凑的总数,k指硬币的种数)
空间复杂度:O(n)
# 写在最后
浪了两天继续刷题,新年快乐!