赞
踩
爬梯子、跳跃游戏、最小路径和、杨辉三角、接雨水。每题做详细思路梳理,配套Python&Java双语代码, 2024.03.05 可通过leetcode所有测试用例。
目录
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶示例 2:
输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
这个问题是一个经典的动态规划问题,可以通过动态规划的方法来解决。思路如下:
- class Solution:
- def climbStairs(self, n: int) -> int:
- if n <= 1:
- return 1
- dp = [0] * (n + 1)
- dp[0], dp[1] = 1, 1
- for i in range(2, n + 1):
- dp[i] = dp[i - 1] + dp[i - 2]
- return dp[n]
- public class Solution {
- public int climbStairs(int n) {
- if (n <= 1) {
- return 1;
- }
- int[] dp = new int[n + 1];
- dp[0] = 1;
- dp[1] = 1;
- for (int i = 2; i <= n; i++) {
- dp[i] = dp[i - 1] + dp[i - 2];
- }
- return dp[n];
- }
- }
给你一个非负整数数组
nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回
true
;否则,返回false
。示例 1:
输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。示例 2:
输入:nums = [3,2,1,0,4] 输出:false 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。提示:
1 <= nums.length <= 104
0 <= nums[i] <= 105
要使用动态规划解决这个问题,我们可以定义一个状态数组dp,其中dp[i]表示能否到达数组中的第i个位置。动态规划的过程是从前向后逐步构建dp数组的值,直到最后一个元素。
具体步骤如下:
nums.length
的布尔数组dp,初始全部设为false。dp[0] = true,因为起始位置总是可达的。nums.length - 1
),遍历i之前的所有位置j(j从0到i-1),如果位置j是可达的(即dp[j] == true)并且从位置j跳跃的最大长度(nums[j]
)加上j的位置能够达到或超过i(即j + nums[j] >= i
),那么位置i也是可达的,设置dp[i] = true。- class Solution:
- def canJump(self, nums: List[int]) -> bool:
- dp = [False] * len(nums)
- dp[0] = True # 起始位置总是可达的
- for i in range(1, len(nums)):
- for j in range(i):
- # 如果j是可达的,并且从j可以跳到i或更远,则将i标记为可达
- if dp[j] and j + nums[j] >= i:
- dp[i] = True
- break # 找到一个可达的j就足够了,无需继续查找
- return dp[-1] # 返回是否可以到达最后一个位置
这个动态规划解法可通过142个测试用例,有的会因为时间过长失败,可以通过优化来减少其时间复杂度。原始的解法中,我们使用了嵌套循环,导致时间复杂度为O(n^2)。优化的思路是利用贪心算法的原理来更新一个变量,记录当前能够到达的最远距离,这样可以避免内层的循环,将时间复杂度降低到O(n)。
- class Solution:
- def canJump(self, nums: List[int]) -> bool:
- maxReach = 0 # 初始化最远可到达位置
- for i, jump in enumerate(nums):
- if i > maxReach: # 如果当前位置i超出了之前可达的最远距离maxReach,则无法到达i
- return False
- maxReach = max(maxReach, i + jump) # 更新可到达的最远位置
- if maxReach >= len(nums) - 1: # 如果maxReach已经到达或超过最后一个位置,则可以到达
- return True
- return False # 如果遍历结束还没有返回True,则表示不能到达最后一个位置
- public class Solution {
- public boolean canJump(int[] nums) {
- boolean[] dp = new boolean[nums.length];
- dp[0] = true; // 初始化起点为可达
- for (int i = 1; i < nums.length; i++) {
- for (int j = 0; j < i; j++) {
- // 如果j是可达的,并且从j可以跳到i或更远,则将i标记为可达
- if (dp[j] && j + nums[j] >= i) {
- dp[i] = true;
- break; // 找到一个可达的j就足够了,无需继续查找
- }
- }
- }
- return dp[nums.length - 1]; // 返回是否可以到达最后一个位置
- }
- }
给定一个包含非负整数的
m x n
网格grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。示例 2:
输入:grid = [[1,2,3],[4,5,6]] 输出:12提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j] <= 200
对于“最小路径和”这个问题,我们同样可以使用动态规划的方法来解决。这个问题的目标是找到从左上角到右下角的路径,使得路径上的数字总和为最小。
解题思路如下:
dp[i][j]
表示从左上角到达点(i, j)
的最小路径和。(i, j)
的路径可以从上方(i-1, j)
或左方(i, j-1)
来,因此dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
。需要特别注意边界条件,即当i
或j
为0时,只有一条路径可走。dp[0][0] = grid[0][0]
,即起点的最小路径和就是其自身的值。对于第一行和第一列的其他元素,因为它们只能从一个方向来(要么是上边,要么是左边),所以可以直接累加。dp
数组,直到右下角。dp
数组右下角的值,即dp[m-1][n-1]
,代表了从左上角到右下角的最小路径和。- class Solution:
- def minPathSum(self, grid: List[List[int]]) -> int:
- m, n = len(grid), len(grid[0])
- dp = [[0] * n for _ in range(m)]
- dp[0][0] = grid[0][0]
-
- for i in range(1, m):
- dp[i][0] = dp[i-1][0] + grid[i][0]
- for j in range(1, n):
- dp[0][j] = dp[0][j-1] + grid[0][j]
-
- for i in range(1, m):
- for j in range(1, n):
- dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
-
- return dp[-1][-1]
- public class Solution {
- public int minPathSum(int[][] grid) {
- int m = grid.length, n = grid[0].length;
- int[][] dp = new int[m][n];
- dp[0][0] = grid[0][0];
-
- for (int i = 1; i < m; i++) {
- dp[i][0] = dp[i-1][0] + grid[i][0];
- }
- for (int j = 1; j < n; j++) {
- dp[0][j] = dp[0][j-1] + grid[0][j];
- }
-
- for (int i = 1; i < m; i++) {
- for (int j = 1; j < n; j++) {
- dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
- }
- }
-
- return dp[m-1][n-1];
- }
- }
给定一个非负整数
numRows
,生成「杨辉三角」的前numRows
行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2:
输入: numRows = 1 输出: [[1]]提示:
1 <= numRows <= 30
生成杨辉三角的过程可以通过动态规划的方法来实现。每一行的数字是基于上一行的数字计算得来的,具体规则是:每一行的第一个和最后一个数字都是1,对于其中的其他数字,即第i行的第j个数字(行列均从0开始计数),可以通过上一行的第j-1
个数字和第j
个数字之和获得,即triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]
。
解题思路如下:
triangle
来存储整个杨辉三角。numRows-1
行。
row
,首个元素设为1(因为每行的开始都是1)。triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]
的规则计算当前位置的元素,并添加到当前行列表row
中。row
添加到triangle
中。triangle
。- class Solution:
- def generate(self, numRows: int) -> List[List[int]]:
- triangle = []
-
- for i in range(numRows):
- row = [None for _ in range(i + 1)] # 初始化当前行
- row[0], row[-1] = 1, 1 # 每行的开始和结束都是1
-
- for j in range(1, len(row) - 1): # 计算中间的值
- row[j] = triangle[i-1][j-1] + triangle[i-1][j]
-
- triangle.append(row) # 将当前行添加到杨辉三角中
-
- return triangle
- public class Solution {
- public List<List<Integer>> generate(int numRows) {
- List<List<Integer>> triangle = new ArrayList<List<Integer>>();
-
- for (int i = 0; i < numRows; i++) {
- List<Integer> row = new ArrayList<Integer>();
-
- for (int j = 0; j <= i; j++) {
- if (j == 0 || j == i) { // 每行的开始和结束都是1
- row.add(1);
- } else {
- row.add(triangle.get(i-1).get(j-1) + triangle.get(i-1).get(j)); // 计算中间的值
- }
- }
-
- triangle.add(row); // 将当前行添加到杨辉三角中
- }
-
- return triangle;
- }
- }
给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。示例 2:
输入:height = [4,2,0,3,2,5] 输出:9提示:
n == height.length
1 <= n <= 2 * 10^4
0 <= height[i] <= 10^5
接雨水问题可以通过动态规划来解决。核心思想是计算每个柱子上方能接多少雨水,这取决于该柱子左右两侧最高柱子的高度。具体来说,某个位置能接的雨水量,等于该位置左侧最高柱子和右侧最高柱子中较矮的一个的高度减去当前柱子的高度。
动态规划的步骤如下:
height
,计算每个位置左侧的最大高度,存储在数组leftMax
中。height
,但这次是从右向左遍历,计算每个位置右侧的最大高度,存储在数组rightMax
中。min(leftMax[i], rightMax[i]) - height[i]
来计算每个位置上方能接的雨水量。如果这个值是负数,则说明在该位置不会积水,因此将其视为0。- class Solution:
- def trap(self, height: List[int]) -> int:
- if not height:
- return 0
-
- n = len(height)
- leftMax = [0] * n
- rightMax = [0] * n
- water = 0
-
- leftMax[0] = height[0]
- for i in range(1, n):
- leftMax[i] = max(leftMax[i-1], height[i])
-
- rightMax[n-1] = height[n-1]
- for i in range(n-2, -1, -1):
- rightMax[i] = max(rightMax[i+1], height[i])
-
- for i in range(n):
- water += min(leftMax[i], rightMax[i]) - height[i]
-
- return water
- public class Solution {
- public int trap(int[] height) {
- if (height == null || height.length == 0) {
- return 0;
- }
-
- int n = height.length;
- int[] leftMax = new int[n];
- int[] rightMax = new int[n];
- int water = 0;
-
- leftMax[0] = height[0];
- for (int i = 1; i < n; i++) {
- leftMax[i] = Math.max(leftMax[i-1], height[i]);
- }
-
- rightMax[n-1] = height[n-1];
- for (int i = n-2; i >= 0; i--) {
- rightMax[i] = Math.max(rightMax[i+1], height[i]);
- }
-
- for (int i = 0; i < n; i++) {
- water += Math.min(leftMax[i], rightMax[i]) - height[i];
- }
-
- return water;
- }
- }
------------------------------
总结不易。看到这了,觉得有用的话点个赞吧。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。