hot100-一刷-15动态规划(共10道题)
70. 爬楼梯
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
提示:
1 <= n <= 45
代码实现
分析:
代码:
class Solution {
public int climbStairs(int n) {
// dp数组的含义:登上i层台阶的方法数量
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];
}
public int climbStairs2(int n) {
// 压缩dp
int f0 = 1;
int f1 = 1;
for(int i = 2; i <=n; i++){
int newF = f0 + f1;
f0 = f1;
f1 = newF;
}
return f1;
}
}
118. 杨辉三角
题目描述
给定一个非负整数 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
代码实现
分析:
代码:
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ans = new ArrayList<>(numRows);
ans.add(List.of(1));
for(int i = 1; i < numRows; i++){
List<Integer> row = new ArrayList<>(i+1);
row.add(1);
for (int j = 1; j < i; j++){
row.add(ans.get(i-1).get(j-1) + ans.get(i-1).get(j));
}
row.add(1);
ans.add(row);
}
return ans;
}
}
198. 打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
代码实现
分析:
代码:
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if (nums == null || n == 0) return 0;
if (n == 1) return nums[0];
int dp[] = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[1], dp[0]);
for (int i = 2; i < n; i++){
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
}
return dp[n-1];
}
}
class Solution {
public int rob(int[] nums) {
int n = nums.length;
int dp[][] = new int[n+1][2];
// 0没偷, 1 偷
dp[0][0] = 0;
dp[0][1] = nums[0];
for(int i = 1; i < n; i++){
dp[i][0] = Math.max(dp[i-1][1], dp[i-1][0]);
dp[i][1] = Math.max(dp[i-1][0] + nums[i], dp[i-1][1]);
}
return Math.max(dp[n-1][0],dp[n-1][1]);
}
}
279. 完全平方数
题目描述
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9
提示:
1 <= n <= 10^4
代码实现
分析:
代码:
class Solution {
public int numSquares(int n) {
//
int[] dp = new int[n+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
// 先遍历物品,再遍历背包
for (int i = 1; i * i <= n; i++){
for(int j = i*i; j <=n; j++){
dp[j] = Math.min(dp[j - i*i]+1, dp[j]);
}
}
return dp[n];
}
}
322. 零钱兑换
题目描述
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
代码实现
分析:
代码:
class Solution {
public int coinChange(int[] coins, int amount) {
// 凑成i金额所需最少的硬币个数
int dp[] = new int[amount+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
// 先遍历物品,再遍历背包,完全背包
for(int i = 0; i < coins.length; i++){
for(int j = coins[i]; j <= amount; j++){
// 只有减去当前硬币,不等于初始化值,才说明可以用当前硬币凑成amount
if (dp[j - coins[i]] != Integer.MAX_VALUE)
dp[j] = Math.min(dp[j-coins[i]]+1, dp[j]);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
139. 单词拆分
题目描述
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅由小写英文字母组成
wordDict 中的所有字符串 互不相同
代码实现
分析:
代码:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// s:背包 wordDict:物品
// 完全背包 排列类型
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
Set<String> set = new HashSet<>(wordDict);
// 先遍历背包, 再遍历物品
for(int i = 1; i<=s.length(); i++){
for(int j = 0; j<i; j++){
String subS = s.substring(j, i);
if(dp[j] && set.contains(subS)) {
dp[i] = true;
}
}
}
return dp[s.length()];
}
}
300. 最长递增子序列
题目描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
你能将算法的时间复杂度降低到 O(n log(n)) 吗?
代码实现
分析:
代码:
class Solution {
public int lengthOfLIS(int[] nums) {
// dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
int[] dp = new int[nums.length];
int ans = 0;
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < i; j++){
if(nums[i] > nums[j]) {
// 新增一个nums[i],就依次比较它和之前0 - i-1 的数, 如果比这些数大,就+1,并比较和历史中的所有数量,取最大的
dp[i] = Math.max(dp[i], dp[j]+1);
}
}
// 可能不是最后一个数取到最长的,比如最后一个数很小,那显然就不是它。
ans = Math.max(ans, dp[i]);
System.out.print(ans);
}
return ans;
}
}
53. 最大子数组和
题目描述
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
代码实现
分析:
代码:
class Solution {
public int maxSubArray(int[] nums) {
int dp[] = new int[nums.length];
dp[0] = nums[0];
int ans = nums[0];
for (int i = 1; i < nums.length; i++){
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
// 不一定是最后的元素相加最大,中间的连续数组可能就是最大的
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
152. 乘积最大子数组
题目描述
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2 * 104
-10 <= nums[i] <= 10
nums 的任何子数组的乘积都 保证 是一个 32-位 整数
代码实现
分析:
代码:
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
// 同时记录最大和最小, 最小的是负数,可能下一个数是负数, 最小的 负负得正 就能变成最大的了
int[] fMax = new int[n];
int[] fMin = new int[n];
fMax[0] = fMin[0] = nums[0];
// int ans = nums[0];
for (int i = 1; i < n; i++){
// 1. 要么是当前数自己成一个子数组
// 2. 要么是最小值或最大值乘当前数后的最大值
// 1、2选最大的
int x = nums[i];
fMax[i] = Math.max(x,Math.max(fMax[i-1]*x, fMin[i-1]*x));
fMin[i] = Math.min(x,Math.min(fMax[i-1]*x, fMin[i-1]*x));
// ans = Math.max(ans, fMax[i]);
}
// return ans;
return Arrays.stream(fMax).max().getAsInt();
}
}
416. 分割等和子集
题目描述
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
代码实现
分析:
代码:
class Solution {
public boolean canPartition(int[] nums) {
// 背包 sum/2 物品nums
int s = 0;
for (int x : nums) {
s += x;
}
if (s % 2 != 0) {
return false;
}
int target = s/2;
// 容量为j的背包,所背的物品价值最大可以为dp[j]。
int[] dp = new int[target+1];
for(int i = 0; i < nums.length; i++){
// 先遍历物品,再遍历背包
// 只有j>= nums[i] 才可以放入
for (int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
32. 最长有效括号
题目描述
代码实现
分析:
题解
代码:
class Solution {
public int longestValidParentheses(String s) {
Deque<Integer> st = new ArrayDeque<>();
char[] chars = s.toCharArray();
int n = chars.length;
int[] status = new int[n];
for (int i = 0; i < n; i++){
if(chars[i] == '('){
st.push(i);
}else if(chars[i] == ')' && !st.isEmpty()){
// 栈不为空 说明栈里有( 并且当前是) 则可以出栈,并设置标志位。
int j = st.pop();
status[i] = status[j] = 1;
}
}
// 统计连续1的个数
int ans = 0;
int cnt = 0;
for(int sta : status){
if(sta==1){
cnt++;
ans = Math.max(ans, cnt);
}else{
cnt = 0;
}
}
return ans;
}
}
本文来自博客园,作者:chendsome,转载请注明原文链接:https://www.cnblogs.com/chendsome/p/18644568