基础经典题目
动态规划解题思路可以参考文章[动态规划问题]
T509-斐波那契数
见LeetCode第509题[斐波那契数]
题目描述
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
我的思路
- 注意到公式中,当前斐波那契数只是和前两个数有关,因此可以准备3个变量
fn | fn_1 | fn_2 - 根据状态转移方程,自底向上遍历求解
我的题解
public int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int fn_1 = 1;
int fn_2 = 0;
int fn = 1;
for (int i = 3; i <= n; i++) {
fn_2 = fn_1;
fn_1 = fn;
fn = fn_1 + fn_2;
}
return fn;
}
计算复杂度分析
- 时间复杂度:,需要遍历到来更新
fn - 空间复杂度:,仅需常数个变量存储
fn_1和fn_2
T70-爬楼梯
见LeetCode第70题[爬楼梯]
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
我的思路
- 这题本质上就是斐波那契数列,因此解题思路一直
- 如何爬到第
n个台阶上?- 可以从第
n - 1阶楼梯跨一步上去 - 可以从第
n - 2阶楼梯两步跨上去
- 可以从第
- 状态转移方程如何写出?
我的题解
public int climbStairs(int n) {
if (n <= 3) return n;
int f_1 = 2;
int f_2 = 1;
int fn = 0;
for (int i = 3; i <= n; i++) {
fn = f_1 + f_2;
f_2 = f_1;
f_1 = fn;
}
return fn;
}
T746-使用最小代价爬楼梯
见LeetCode第746题[使用最小代价爬楼梯]
题目描述
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第i个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。
示例 2:
输入: cost = [1,100,1,1,1,100,1,1,100,1]
输出: 6
解释: 你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
我的思路
- 这一题有点像是贪心,上一层或者上两层是根据代价来的
- 每一步都选择局部最优解,可以保证全局最优解
- 状态转移方程可以为:
- 初始边界条件为:
- 循环结束条件为:
i > cost.length
我的题解
public int minCostClimbingStairs(int[] cost) {
if (cost.length == 2) {
return Math.min(cost[0], cost[1]);
}
int total = 0;
int total_1 = 0;
int total_2 = 0;
for (int i = 2; i <= cost.length; i++) {
total = Math.min(total_1 + cost[i - 1], total_2 + cost[i - 2]);
total_2 = total_1;
total_1 = total;
}
return total;
}
计算复杂度分析
- 时间复杂度:,需要遍历到来更新
fn - 空间复杂度:,仅需常数个变量存储
fn_1和fn_2
T62-不同路径
见LeetCode第62题[不同路径]
题目描述
一个机器人位于一个 m x n **网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例
输入: m = 3, n = 7
输出: 28
我的思路
- 对于当前位置,其只能从上面或者左边过来,因此状态转移方程为:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - 初始化第一行
dp[0][j]和第一列dp[i][0]为 1 - 根据状态转移方程更新当前的路径数即可
我的题解
/**
* 不同路径数量
* @param m
* @param n
* @return
*/
public int uniquePaths(int m, int n) {
if (m == 1 || n == 1) return 1;
// 初始化 一维DP数组
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
// 将二维DP数组压缩到一维,当前的值只和上边的值dp[j] 和 左边的值 dp[j - 1] 相关
for (int j = 1; j < n; j++) {
dp[j] = dp[j] + dp[j - 1];
}
}
return dp[n - 1];
}
复杂度分析
- 时间复杂度: ,双层循环更新
dp[]数组 - 空间复杂度: ,用来存储
dp中间值
T63-不同路径II
见LeetCode第63题[不同路径II]
题目描述
给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角(即 grid[0][0])。机器人尝试移动到 右下角(即 grid[m - 1][n - 1])。机器人每次只能向下或者向右移动一步。 网格中的障碍物和空位置分别用 1 和 0 来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。 返回机器人能够到达右下角的不同路径数量。
示例
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
我的思路
- 这题本质上和上题没什么区别,只是需要判断障碍物
- 如果当前的
grid[i][j] == 0那么就将dp置为 0
- 如果当前的
- 同样可以考虑压缩
dp数组为 一维数组
我的题解
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[] dp = new int[n];
Arrays.fill(dp, 0);
// 初始化第一行
for (int i = 0; i < n; i++) {
if (obstacleGrid[0][i] == 1) {
dp[i] = 0;
} else {
dp[i] = i == 0 ? 1 : dp[i - 1];
}
}
for (int i = 1; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
} else {
dp[j] = j == 0 ? dp[j] : dp[j] + dp[j - 1];
}
}
}
return dp[n - 1];
}
复杂度分析
- 时间复杂度: ,双层循环更新
dp[]数组 - 空间复杂度: ,用来存储
dp中间值
T343-整数拆分
见LeetCode第343题[整数拆分]
题目描述
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
示例 2:
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
我的思路
- 这题实际就是求函数的极值问题
- 不难看出,函数的极大值点在处,但是显然是非整数
- 注意到,因此可以划分足够多的 3即可
我的题解
public int integerBreak(int n) {
if (n <= 3) return n - 1;
int quotient = n / 3;
int remainder = n % 3;
switch (remainder) {
case 0:
return (int) Math.pow(3, quotient);
case 1:
return (int) Math.pow(3, quotient - 1) * 4;
default:
return (int) Math.pow(3, quotient) * remainder;
}
}
T96-不同的二叉搜索树
见LeetCode第96题[不同的二叉搜索树]
题目描述
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例
输入:n = 3
输出:5
我的思路
- 对于一个有序数组:1 2 3 4
- 1 为根节点
- 2 3 4 为右孩子,有几种可能? 5种
- 共有 1 * 5 种可能
- 2 为根节点
- 1 为左孩子,1 种可能
- 3 4 为右孩子,2 种可能
- 共有 1 * 2 种可能
- 3 为根节点,同2
- 4 为根节点,同1
我的题解
/**
* 不同的二叉搜索树
* @param n
* @return
*/
public int numTrees(int n) {
int[] dp = new int[n + 1];
// 初始化某些情况
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
// 根节点的选取
for (int j = 1; j <= i; j++) {
// 左孩子的可能数量 * 右孩子的可能数量
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
计算复杂度分析
- 时间复杂度:,更新
dp数组需要嵌套一层循环 - 空间复杂度:,用来存储
dp数组