题目来源:
来源:力扣(LeetCode)
链接:https
主要思想:将问题拆分成多个子问题,然后找到最优的子结构;
自顶向下:递归解题思路,可以优化为记忆化搜索;
自底向上:动态规划思路;
第2次
题目链接:爬楼梯
题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
解题思路:
满足f(n) = f(n-1)+f(n-2),举例来说明,比如要爬8阶楼梯,可以先爬上7阶楼梯,然后再爬一阶楼梯,这种方式实现的数目与爬7阶楼梯一致;也可以先爬上6阶楼梯,然后再一次性爬2阶楼梯,这种方式实现的数目与爬6阶楼梯一致;所以,f(8) = f(7)+f(6);
解法一:建立长度为n的动态数组,将得到的方法数量填到对应的数组中,最后返回nums[n-1];
解法二:不用建立数组,创建两个变量pre1和pre2,在for循环里创建一个变量cur = pre1 + pre2,然后让pre1 =cur,pre2 = pre1,巧妙地实现;
解法三:递归;
代码:
解法一:
public int climbStairs(int n) {
int[] nums = new int [n];
if(n < 2){
return n;
}
nums[0] = 1;
nums[1] = 2;
for(int i=2; i < n; i++){
nums[i] = nums[i-1] + nums[i-2];
}
return nums[n-1];
}
解法二:
public int climbStairs(int n) {
if (n <= 2) {
return n;
}
int pre2 = 1, pre1 = 2;
for (int i = 2; i < n; i++) {
int cur = pre1 + pre2;
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
第4次
题目链接:打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
解题思路:
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户,所以得到如下公式:
代码:
递归解法:
public int rob(int[] nums) {
return fun(nums,0);
}
public int fun(int[] nums,int x){
if(x >= nums.length)
return 0;
int temp = 0;
for(int i = x; i < nums.length; i++){
temp = Math.max(temp,nums[i] + fun(nums,i+2));
}
return temp;
}
记忆化搜索:
public int rob(int[] nums) {
int[] dp = new int[nums.length+1];
Arrays.fill(dp,-1);
return fun(nums,0,dp);
}
public int fun(int[] nums,int x,int[] dp){
if(x >= nums.length)
return 0;
int temp = 0;
if(dp[x] != -1)
return dp[x];
for(int i = x; i < nums.length; i++){
temp = Math.max(temp,nums[i] + fun(nums,i+2,dp));
}
dp[x] = temp;
return temp;
}
动态规划:
这里定义的pre2(也即dp[i-2])为截止到上上一个住户盗窃的最大值,pre1(也即dp[i-1])为截止到上一个用户盗窃的最大值;
参考代码:
public int rob(int[] nums) {
int pre2 = 0, pre1 = 0;
for (int i = 0; i < nums.length; i++) {
int cur = Math.max(pre2 + nums[i], pre1);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
我的代码1:从后向前考虑
public int rob(int[] nums) { //从后向前考虑
int n = nums.length;
if(n == 0)
return 0;
else if(n == 1)
return nums[0];
else if(n == 2){
return Math.max(nums[0],nums[1]);
}
int[] dp = new int[n+1];
dp[n-1] = nums[n-1];
dp[n-2] = Math.max(nums[n-1],nums[n-2]);
for(int i = n-3; i >= 0; i--){
dp[i] = Math.max(nums[i] + dp[i+2],nums[i+1] + dp[i+3]);
}
return dp[0];
}
我的代码2:从前向后考虑(稍微改进一下,将dp[i-1]和dp[i-2]分别用pre1和pre2表示,则变成参考代码)
public int rob(int[] nums) { //从前向后考虑
int n = nums.length;
if(n == 0)
return 0;
else if(n == 1)
return nums[0];
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2; i < nums.length; i++){
dp[i] = Math.max(nums[i] + dp[i-2],dp[i-1]);
}
return dp[n-1];
}
第2次
题目链接:打家劫舍 II
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
解题思路:由于数组的第一个数和最后一个相邻,所以只能取其中一个,因此考虑数组拆分成两种情况,即从0 ~ nums.length - 2 与从1 ~ nums.length - 1,然后这两部分可以看成是两条直的街道盗窃,可以采用(2. 打家劫舍)中的方法进行求解;
代码:
class Solution {
public int rob(int[] nums) {
if(nums.length == 1){
return nums[0];
}
return Math.max(robfind(nums,1,nums.length),robfind(nums,0,nums.length-1));
}
public int robfind(int[] nums, int a, int b){
int pre2 = 0;
int pre1 = 0;
for(int i = a; i < b; i++){
int cur = Math.max(pre2 + nums[i],pre1);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
}
第0次
题目链接:来源未知
第0次
题目链接:来源未知
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后(即达到3岁)成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。(假设:第2年小母牛可以生小母牛,每年的年初开始生小母牛,到该年结束,小母牛1岁);
解题思路:
dp[i] 表示第i年时的牛数量,dp[i-3]表示在第i年能生育的小母牛,dp[i-1]表示上一年的小母牛数量;
代码:暂无
注意:千万不要陷入思维旋涡!!!
第1次
题目链接:最小路径和
题目描述:
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
解题思路:
解法一:使用动态规划的解法,定义一个动态数组dp[]长度大小为n,dp[i]表示走到最后一行的第i列所走的最短路径和,使用双重for循环,第一层循环指的是走第i行,先走第一行(即i=0),得到一组dp[],然后再走第二行,更新dp[],在走第二行第一列的时候,dp[0]不需要跟别的数比较,直接拿自己加上grid[i][j]就完事,之后dp[j]需要跟dp[j-1]比较(如下图所示),来决定用哪个去加上grid[i][j],最后返回dp数组的最后一个元素dp[n-1]。
解法二:递归,时间超限制
代码:
解法一:
public int minPathSum(int[][] grid) {
if(grid == null || grid[0] == null){
return 0;
}
int m = grid.length;
int n = grid[0].length;
int[] dp = new int[n];
for(int i= 0; i < m; i++){
for(int j = 0; j < n; j++){
if(j == 0){ //在第一列中,从某行走到相邻的下一行
dp[j] = dp[j] + grid[i][j];
}
else if(i == 0){ //走第一行时执行的代码
dp[j] = dp[j-1] + grid[i][j];
}
else {
dp[j] = Math.min(dp[j],dp[j-1]) + grid[i][j];
}
}
}
return dp[n-1];
}
解法二:时间超限制,不推荐
class Solution {
public int minPathSum(int[][] grid) {
if(grid == null){
return 0;
}
return find(grid,0,0);
}
public int find(int[][] grid,int i,int j){
if(i == grid.length - 1 && j == grid[0].length - 1){
return grid[i][j];
}
if(i == grid.length - 1)
return grid[i][j]+find(grid,i,j+1);
else if(j == grid[0].length - 1)
return grid[i][j]+find(grid,i+1,j);
return Math.min(grid[i][j]+find(grid,i+1,j),grid[i][j]+find(grid,i,j+1));
}
}
第0次
题目链接:不同路径
题目描述:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
解题思路:
解法一:递归
解法二:记忆化搜索,递归的改进
解法三:动态规划,机器人每到一个方格的总路径数取决于该方格的左侧与上侧方格的路径数量,所以动态函数为dp[i][j] = dp[i][j-1] + dp[i-1][j],可以简写为:dp[j] = dp[j]+dp[j-1]。动态规划的思想:记住前面的结果来得到后面的结果。
代码:
解法一:递归
public int uniquePaths(int m, int n) {
if(m == 1 || n == 1)
return 1;
if(m == 2 && n == 2)
return 2;
return uniquePaths(m,n-1)+uniquePaths(m-1,n);
}
解法二:记忆化搜索
public int uniquePaths(int m, int n) {
int[][] dp = new int[m+1][n+1];
return quePaths(m,n,dp);
}
public int quePaths(int m, int n,int[][] dp) {
if(m == 1 || n == 1)
return 1;
if(m == 2 && n == 2)
return 2;
if(dp[m][n] != 0)
return dp[m][n];
dp[m][n-1] = quePaths(m,n-1,dp);
dp[m-1][n] = quePaths(m-1,n,dp);
return dp[m][n-1]+dp[m-1][n];
}
解法三:动态规划
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] = dp[j] + dp[j - 1];
}
}
return dp[n - 1];
}
第1次
题目链接:不同路径 II
题目描述:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
解题思路:
代码:
动态规划:自己动手,丰衣足食
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
if(obstacleGrid == null || obstacleGrid[0][0] == 1)
return 0;
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if(obstacleGrid[m-1][n-1] == 1)
return 0;
int[] dp = new int[n];
for(int i=0; i < m; i++){
for(int j=0; j < n; j++){
if(i == 0){
if(obstacleGrid[0][j] == 1)
break;
else
dp[j] = 1;
}
else if(j == 0){
dp[j] = obstacleGrid[i-1][j] == 1 ? 0 : dp[j];
}
else
dp[j] = (obstacleGrid[i-1][j] == 1 ? 0 : dp[j]) + (obstacleGrid[i][j-1] == 1 ? 0 : dp[j-1]);
}
}
return dp[n-1];
}
第0次
题目链接:三角形最小路径和
题目描述:
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
解题思路:从顶向下地进行考虑,第一行2所对应的可以认为是dp[0],3所对应的为上一层的dp[0](也即到上一层3为止的最短路径和),4所对应的是上一层的dp[1](也即到上一层4为止的最短路径和),求其较小值加上2则得到顶层的dp[0]。
代码:
public int minimumTotal(List<List<Integer>> triangle) {
int l = triangle.size();
int[] dp = new int[triangle.size()+1];
for(int i = l - 1; i >= 0 ;i--){
List<Integer> temp = triangle.get(i);
for(int j = 0; j < temp.size(); j++){
dp[j] = Math.min(dp[j],dp[j+1]) + temp.get(j);
}
}
return dp[0];
}
第0次
题目链接:区域和检索 - 数组不可变
题目描述:
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点
解题思路:
代码:
我的垃圾代码:击败了<10%的用户
class NumArray {
private int[] array;
public NumArray(int[] nums) {
array = nums;
}
public int sumRange(int i, int j) {
int result = 0;
while(i <= j){
result = result + array[i];
i++;
}
return result;
}
参考代码:击败了50%+的用户
class NumArray {
private int[] sums;
public NumArray(int[] nums) {
sums = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
}
public int sumRange(int i, int j) {
return sums[j + 1] - sums[i];
}
}
第0次
题目链接:等差数列划分
题目描述:
等差数列定义:如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
函数要返回数组 A 中所有为等差数组的子数组个数。注:要求子数组元素下标连续。
解题思路:
代码:
第0次
题目链接:整数拆分
题目描述:
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
解题思路:
解法一:记忆化搜索,递归方法的改进,自顶向下的解决方案;
解法二:动态规划,自底向上;
代码:
解法一:
public int integerBreak(int n) { //记忆化搜索
int[] dp = new int[n + 1];
return Break(n,dp);
}
public int Break(int n, int[] dp) {
if(n == 1)
return 1;
if(dp[n] != 0)
return dp[n];
int res = 0;
for (int j = 1; j <= n - 1; j++) {
res = Math.max(res, Math.max(j * Break(n-j,dp), j * (n - j)));
}
dp[n] = res;
return res;
}
解法二:
public int integerBreak(int n) { //动态规划
int[] dp = new int[n + 1];
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i - 1; j++) {
dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j)));
}
}
return dp[n];
}
第0次
题目链接:完全平方数
题目描述:
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
解题思路:
解法一:递归,超时;
解法二:记忆化搜索,自上而下;
解法三:动态规划,自下而上;
代码:
解法一:递归
public int numSquares(int n){
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
int count = Integer.MAX_VALUE;
for(int i = 1; i*i <= n; i++){
count = Math.min(count,numSquares(n - i*i) + 1);
}
return count;
}
解法二:记忆化搜索
public int numSquares(int n) {
int[] dp = new int[n+1];
return generate(n,dp);
}
public int generate(int n,int[] dp){
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
int count = Integer.MAX_VALUE;
if(dp[n] != 0)
return dp[n];
for(int i = 1; i*i <= n; i++){
count = Math.min(count,generate(n - i*i,dp) + 1);
}
dp[n] = count;
return count;
}
解法三:动态规划
public int numSquares(int n){
int[] dp = new int[n+1];
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
for(int i = 1; i <= n; i++){
int min = Integer.MAX_VALUE;
for(int j = 1; j*j <= i; j++){
min = Math.min(min,dp[i - j*j] );
}
dp[i] = min + 1;
}
return dp[n];
}
第0次
题目链接:解码方法
题目描述:
解题思路:
代码:
题目描述:有一个容量为C的背包,现在有n中不同的物品,编号为0…n-1,其中每一件物品的重量为w(i),价值为v(i),问可以向这个背包中放哪些物品,使得在不超过背包容量的基础上,物品的总价值最大?
解题思路:
递归:
状态函数为:F(i,c) = max(F(i-1 , c) , v(i) + F(i-1, c - w(i)))
记忆化搜索:定义二维记忆化数组dp[n][C+1],数组中每个元素都赋值为-1,然后记忆化,遍历到输入参数n,C对应的dp数组元素值不是-1,则表示已经求得结果,不需要重复求结果,直接返回dp[n][C]即可;
动态规划:记忆化搜索方法的改进,定义动态数组dp[n][C+1],dp[i][j]表示在背包容量为C时,数组w中截止到索引为i的物品(一共 i+1个物品)判定是否放进背包获得的最大值,最后返回dp[n-1][C]表示背包容量为C,截止到索引为n-1的物品是否放进背包获得的最大值。
代码:
递归:
public static void main(String[] args) {
int[] v = {6,10,12};
int[] w = {1,2,3};
System.out.print(function(v,w,0,5));
}
public static int function(int[] v,int[] w, int n, int C) {
if(C <= 0 || n >= w.length)
return 0;
int res = Math.max(function(v,w,n+1,C) , (C-w[n] >= 0) ? v[n]+function(v,w,n+1,C-w[n]) : 0);
return res;
}
记忆化搜索:
public static int function(int[] v,int[] w, int n, int C,int [][] dp) {
if(C <= 0 || n >= w.length)
return 0;
if(dp[n][C] != -1)
return dp[n][C];
int res = Math.max(function(v,w,n+1,C,dp) , (C-w[n] >= 0) ? v[n]+function(v,w,n+1,C-w[n],dp) : 0);
dp[n][C] = res;
return res;
}
动态规划:
二维数组-动态规划:从前往后遍历
public static int function(int[] v,int[] w, int C) { //二维数组-动态规划
int n = w.length;
int[][] dp = new int[n][C+1];
for(int i=0; i < n; i++) {
for(int j=0; j <=C; j++) {
if(i == 0) {
dp[i][j] = (w[i] <= j) ? v[i] : 0;
}
else if(j == 0) {
dp[i][j] = 0;
}
else
dp[i][j] = Math.max(dp[i-1][j], ((j-w[i] >= 0) ? v[i] +dp[i-1][j-w[i]] : 0));
}
}
return dp[n-1][C];
}
一维数组-动态规划:从后往前遍历
public static int function(int[] v,int[] w, int C) { //一维数组-动态规划
int n = w.length;
int[] dp = new int[C+1];
for(int i=0; i < n; i++) {
for(int j=C; j >= w[i]; j--) {
if(i == 0) {
dp[j] = (w[i] <= j) ? v[i] : 0;
}
else
dp[j] = Math.max(dp[j], v[i] +dp[j-w[i]]);
}
}
return dp[C];
}
第2次
题目链接:分割等和子集
题目描述:
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解题思路:
递归解法:
记忆化搜索:定义一个n*(c+1)的二维矩阵,初始值都赋值为-1,然后利用递归的思路,记忆化地将dp二维矩阵填满,最终返回dp[n][c],其值为1则返回true,为0则返回false;
动态规划(二维矩阵):记忆化搜索方案的改进,这时不需要定义int型的二维数组(为了记忆,要标记为-1),可以直接定义为boolean类型的数组dp,dp[1,3]表示nums数组中的前2个数字装入背包容量为3的背包中,是否刚好装满不浪费空间,为采用双层for循环,动态函数为:
动态规划(一维矩阵):在二维数组的基础上进行修改,这里第二层for循环需要从j = c往前遍历,因为使用一维数组时需要用到之前的dp[j],这时如果从0往后遍历的话,会用到当前外层所得到的dp[j],也就是说此时变成用当前层较小的dp[j]得到较大的dp[j],则没有意义,如果从后往前遍历,则使用的是上一层的dp[j],才能代替二维矩阵,其动态函数为:
代码:
递归解法:
记忆化搜索:
public boolean canPartition(int[] nums) {
int n = nums.length;
if(n == 0 || n == 1 )
return false;
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum = sum + nums[i];
}
if(sum%2 != 0)
return false;
int[][] dp = new int[n][sum/2+1];
for(int i=0 ; i < n; i++)
Arrays.fill(dp[i],-1);
return Partition(nums,n-1,sum/2,dp);
}
public boolean Partition(int[] nums,int n,int c,int[][] dp){
if(n < 0 || c <=0 )
return false;
if(nums[n] == c)
return true;
if(dp[n][c] != -1)
return dp[n][c] == 1 ? true : false;
boolean result = Partition(nums,n-1,c,dp);
if(c >= nums[n])
result = (Partition(nums,n-1,c-nums[n],dp) || result);
dp[n][c] = result == true ? 1 : 0;
return result;
}
动态规划(二维矩阵):
public boolean canPartition(int[] nums) {
int n = nums.length;
if(n <= 1)
return false;
int sum = 0;
for(int i=0; i < n; i++)
sum = sum + nums[i];
if(sum%2 != 0)
return false;
int c = sum/2;
boolean[][] dp = new boolean[n][c+1];
for(int i=0; i < n; i++){
for(int j=0; j <= c; j++){
if(i == 0)
dp[i][j] = (nums[i] == j);
else if(j >= nums[i])
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[n-1][c];
}
动态规划(一维矩阵):
public boolean canPartition(int[] nums) {
int n = nums.length;
if(n <= 1)
return false;
int sum = 0;
for(int i=0; i < n; i++)
sum = sum + nums[i];
if(sum%2 != 0)
return false;
int c = sum/2;
boolean[] dp = new boolean[c+1];
for(int i=0; i < n; i++){
for(int j=c; j > 0; j--){
if(i == 0)
dp[j] = (nums[i] == j);
else if(j >= nums[i])
dp[j] = dp[j] || dp[j-nums[i]];
else
dp[j] = dp[j];
}
}
return dp[c];
}
第0次
题目链接:目标和
题目描述:
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
解题思路:
递归:暴力解法,竟然能通;
动态规划:
二维数组:
一维数组:
代码:
递归:
public int findTargetSumWays(int[] nums, int S) {
if(nums == null)
return 0;
return TargetSumWays(nums,0,S);
}
public int TargetSumWays(int[] nums,int i, int S){
if(i == nums.length && S == 0)
return 1;
else if(i == nums.length && S != 0)
return 0;
return TargetSumWays(nums,i+1,S-nums[i]) + TargetSumWays(nums,i+1,S+nums[i]);
}
动态规划:
二维数组:
public int findTargetSumWays(int[] nums, int S) {
if(nums == null)
return 0;
int n = nums.length;
int sum = 0;
for(int i=0; i < nums.length; i++){
sum = sum + nums[i];
}
int c = (sum + S)/2;
if(S > sum || (sum + S)%2 == 1)
return 0;
int[][] dp = new int[n][c+1];
if(nums[0] == 0)
dp[0][0] = 2;
else
dp[0][0] = 1;
for(int i=1;i<=c;i++){
if(nums[0]==i){
dp[0][i]=1;
break;
}
}
for(int i=1; i < n; i++){
for(int j=0;j <= c ; j++){
if(i == 0){
dp[i][j] = (j == 0 || j == nums[i]) ? 1 : 0;
}
if(j >= nums[i])
dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[n-1][c];
}
一维数组:
public int findTargetSumWays(int[] nums, int S) {
if(nums == null)
return 0;
int n = nums.length;
int sum = 0;
for(int i=0; i < nums.length; i++){
sum = sum + nums[i];
}
int c = (sum + S)/2;
if(S > sum || (sum + S)%2 == 1)
return 0;
int[][] dp = new int[n][c+1];
dp[0] = 1;
for(int i=1; i < n; i++){
for(int j=c;j >= nusm[i]; j--){
dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j];
}
}
return dp[c];
}
第1次
题目链接:一和零
题目描述:
在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
解题思路:
动态规划:s定义类似于三维矩阵的二维矩阵,作为动态数组,橘黄色表示的是将原始数组中第i个物品放入背包的总数,其与上一个dp[j][k](即未放入背包的总数)进行比较,取较大者
代码:
动态规划:
public int findMaxForm(String[] strs, int m, int n) {
int l = strs.length;
int[][] dp = new int[m+1][n+1];
/*冗余代码
int a = count(strs[0],'0');
int b = count(strs[0],'1');
for(int i=0; i <= m; i++){
for(int j=0; j <= n;j++){
if(i >= a && j >= b){
dp[i][j] = 1;
}
}
}
*/
for(int i = 0; i < l; i++){
int m1 = count(strs[i],'0');
int n1 = count(strs[i],'1');
for(int j = m; j >= m1; j--){
for(int k = n; k >= n1; k--)
dp[j][k] = Math.max(dp[j-m1][k-n1] + 1,dp[j][k]);
}
}
return dp[m][n];
}
public int count(String s, char x){
int cnt = 0;
for(int i=0; i < s.length(); i++){
if(s.charAt(i) == x)
cnt++;
}
return cnt;
}
第0次
题目链接:零钱兑换
题目描述:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
解题思路:
代码:
动态规划:
public int coinChange(int[] coins, int amount) {
if (amount == 0 || coins == null || coins.length == 0) {
return 0;
}
int[] dp = new int[amount+1];
for(int coin : coins){
for(int j = 0; j <= amount; j++){
if(j < coin)
dp[j] = dp[j];
else if(j == coin)
dp[j] = 1;
else if(dp[j] != 0 && dp[j - coin] != 0)
dp[j] = Math.min(dp[j],dp[j - coin]+1);
else if(dp[j - coin] != 0)
dp[j] = dp[j - coin]+1;
}
}
return dp[amount] == 0 ? -1 : dp[amount];
}
代码优化:可以直接让i = coin然后就不需要执行if(j < coin)了
第0次
题目链接:零钱兑换 II
题目描述:
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
解题思路:
代码:
动态规划:
从后往前(我的代码):垃圾
public int change(int amount, int[] coins) {
if(amount == 0)
return 1;
int[] dp = new int[amount+1];
for(int i=0; i < coins.length; i++){
for(int j = amount; j >= coins[i]; j--){
if(i == 0)
dp[j] = (j % coins[i] == 0 ? 1 : 0);
else {
int k = j;
int temp = 0;
while(k >= coins[i]){
k = k - coins[i];
temp +=dp[k];
if(k == 0)
temp++;
}
dp[j] = dp[j] + temp;
}
}
}
return dp[amount];
}
从前往后(参考代码):暂时不通
public int change(int amount, int[] coins) {
if (amount == 0 || coins == null || coins.length == 0) {
return 0;
}
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
第1次
题目链接:最长上升子序列
题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
解题思路:
代码:
第0次
题目链接:最长数对链
题目描述:
给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。
给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
解题思路:先按照数对的第二个数字进行升序排序,得到的一个数对序列,最后进行查找最长字数对时,基本的顺序不会改变,只会要某一个数对或者不要某个数对,而不会做顺序的调整,这样就可以转化为一个背包问题,利用上题(1.最长上升子序列)来进行处理即可;
代码:
public int findLongestChain(int[][] pairs) {
if(pairs == null || pairs.length == 0)
return 0;
int n = pairs.length;
int[] dp = new int[n];
Arrays.sort(pairs, (a, b) -> (a[0] - b[0])); //按照数对的第1个数进行排序,若改成(a[1] - b[1]),则按照数对的第二个数进行升序排序
Arrays.fill(dp,1);
for(int i=0; i < n; i++){
for(int j=0; j < n ; j++){
if(pairs[i][0] > pairs[j][1] && j != i){
dp[i] = Math.max(dp[i],dp[j] + 1);
}
}
}
int temp = dp[0];
for(int i=0; i < n; i++){
if(temp < dp[i])
temp = dp[i];
}
return temp;
}
第1次
题目链接:摆动序列
题目描述:
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
解题思路:摆动序列也就是后一个数减去前一个数的结果的符号一直是正负交替出现,解该题的关键在于在遇到连续上升或者连续下降的部分时,dp结果保持不变,也就是dp[i] = dp[i-1],这里需要使用标记变量fp和fn,利用for循环,从数组第二个数开始遍历,最后返回dp[n-1];也可以采用两个变量up和down代替动态数组,遇到连续上升或者下降部分时,所得到的的up和down的值保持不变,此时不需要再使用标记fp和fn了。
代码:
我的代码:
public int wiggleMaxLength(int[] nums) {
if(nums.length == 0)
return 0;
int n = nums.length;
boolean fp = true;
boolean fn = true;
int[] dp = new int[n];
dp[0] = 1;
for(int i=1; i < n; i++){
if(nums[i] - nums[i-1] > 0 && fp){
dp[i] = dp[i-1] + 1;
fp = false;
fn = true;
}
else if(nums[i] - nums[i-1] < 0 && fn){
dp[i] = dp[i-1] + 1;
fp = true;
fn = false;
}
else {
dp[i] = dp[i-1];
}
}
return dp[n-1];
}
参考的优秀代码:
public int wiggleMaxLength(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int up = 1, down = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1]) {
up = down + 1;
} else if (nums[i] < nums[i - 1]) {
down = up + 1;
}
}
return Math.max(up, down);
}