剑指 offer 第 8 天

2023-05-06,

第 8 天

动态规划(简单)

剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

提示:

0 <= n <= 100

题解思想:动态规划、矩阵快速幂、打表动态规划

动态规划:改写递归即可

class Solution {
int mod = (int)1e9+7;
public int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; i++) {
int c = a + b;
c %= mod;
a = b;
b = c;
}
return b;
}
}

矩阵快速幂:记熟方法

class Solution {
static final int MOD = 1000000007; public int fib(int n) {
if (n < 2) {
return n;
}
int[][] q = {{1, 1}, {1, 0}};
int[][] res = pow(q, n - 1);
return res[0][0];
} public int[][] pow(int[][] a, int n) {
int[][] ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
} public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = (int) (((long) a[i][0] * b[0][j] + (long) a[i][1] * b[1][j]) % MOD);
}
}
return c;
}
}

打表动态规划:

class Solution {
static int mod = (int) 1e9+7;
static int N = 110;
static int[] cache = new int[N]; public int fib(int n) {
if (n == 0) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
if (cache[n] != 0) {
return cache[n];
}
cache[n] = (fib(n - 1) + fib(n - 2)) % mod;
return cache[n];
}
}

剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

提示:

0 <= n <= 100

题解思路:动态规划、矩阵快速幂

动态规划:与斐波拉契一样

class Solution {
public int numWays(int n) {
//递归方法 找通项公式
// 将大问题拆分成小问题 n级台阶有 numWays(n-1)+numWays(n-2)
//出口为 n=1 返回1 n=2 返回2
if(n==0||n==1) return 1;
if(n==2) return 2;
int s1=1,s2=2;
int sum;
//最优雅的方式 动态规划 且空间用的也少
for(int i=3;i<n+1;i++){
sum=(s1+s2)%1000000007;
s1=s2;
s2=sum;
}
return s2;
}
}

矩阵快速幂:

class Solution {
private final static int COS = 1000000007;
private int[][] res ={{1,0},{0,1}};
private int[][] temp ={{1,1},{1,0}};
public int numWays(int n) {
int num = fastPow(n);
return num;
} public int fastPow(int n) {
while (n != 0) {
if((n&1) == 1) res = mul(res,temp);
temp = mul(temp,temp);
n = n>>>1;
}
return res[0][0];
} public int[][] mul(int[][] a1, int[][] a2) {
int row = a1.length;
int col = a2[0].length;
int[][] c = new int[row][col];
for (int i=0; i<row; i++) {
for (int j=0; j<col; j++) {
long fac = 0l;
for (int k=0; k<a1[0].length;k++) {
long l1 = a1[i][k];
long l2 = a2[k][j];
fac += (l1*l2)%COS;
}
c[i][j] = (int)fac%COS;
}
}
return c;
}
}

剑指 Offer 63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

限制:

0 <= 数组长度 <= 10^5

题解思路:单调栈、动态规划

单调栈:每次记录当前最大值

class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
Stack<Integer> stack = new Stack<Integer>();
stack.push(prices[0]);
int max = 0;
for (int i = 0; i < prices.length; i ++) {
if (stack.peek() > prices[i]) {
stack.pop();
stack.push(prices[i]);
} else {
max = Math.max(max, prices[i] - stack.peek());
}
}
return max;
}
}

动态规划:维护一个最低价格 cost,判断当前卖出获利是否大于之前卖出获利

class Solution {
public int maxProfit(int[] prices) {
if (prices.length < 2) return 0; // 没有卖出的可能性
// 定义状态,第i天卖出的最大收益
int[] dp = new int[prices.length];
dp[0] = 0; // 初始边界
int cost = prices[0]; // 成本
for (int i = 1; i < prices.length; i++) {
dp[i] = Math.max(dp[i - 1], prices[i] - cost);
// 选择较小的成本买入
cost = Math.min(cost, prices[i]);
} return dp[prices.length - 1];
}
}

剑指 offer 第 8 天的相关教程结束。

《剑指 offer 第 8 天.doc》

下载本文的Word格式文档,以方便收藏与打印。