文章目录
矩阵路径
64 最小路径和
https://leetcode-cn.com/problems/minimum-path-sum/
1处先判断j==0
是因为如果判断先判断i==0
时若j
也为0,在dp[j]=dp[j-1];
会出现数组越界。
class Solution {
public int minPathSum(int[][] grid) {
if(grid==null || grid.length==0)
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){//1
dp[j]=dp[j];
}else if(i==0){
dp[j]=dp[j-1];
}else{
dp[j]=Math.min(dp[j-1],dp[j]);
}
dp[j]=dp[j]+grid[i][j];
}
}
return dp[n-1];
}
}
62 不同路径
https://leetcode-cn.com/problems/unique-paths/
方法一:此处从1开始是因为当i
为0或当j
为0时为1
class Solution {
public int uniquePaths(int m, int n) {
int dp[]=new int[n]; Arrays.fill(dp, 1);
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];
}
}
方法二:可以看做是m+n-2次中选择m-1次往下。
class Solution {
public int uniquePaths(int m, int n) {
int S=m+n-2;
int D=m-1;
long result=1;
for(int i=1;i<=D;i++){
result=result*(S-D+i)/i;
}
return (int)result;
}
}
数组区间
303 区域和检索 - 数组不可变
https://leetcode-cn.com/problems/range-sum-query-immutable/
class NumArray {
int sums[];
public NumArray(int[] nums) {
sums=new int[nums.length+1];
for(int i=0;i<nums.length;i++){
sums[i+1]=sums[i]+nums[i];
}
}
public int sumRange(int i, int j) {
return sums[j+1]-sums[i];
}
}
分割整数
343 整数拆分
https://leetcode-cn.com/problems/integer-break/
方法一:
class Solution {
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(dp[i-j]*j,j*(i-j)));
}
}
return dp[n];
}
}
方法二:
根据题目设定的条件整数n的取值范围为: 2<=n<=58
分析一下:
当n=2时: n=1+1; result = 1*1=1
当n=3时: 可以拆分为: 1+2 或者 1+1+1,但是显然拆分为 1+2,所获得的乘积最大
当n=4时:可以拆分为: 1+3 或者 2+2,但是显然拆分为 2+2,所获得的乘积最大
当n=5时:可以拆分为:2+3,所获得乘积最大
当n=6时:可以拆分为:3+3,所获得乘积最大
当n=7时:可以拆分为:3+4,所获得乘积最大
当n=8时:可以拆分为:3+3+2,所获得乘积最大
当n=9时:可以拆分为:3+3+3,所获得乘积最大
当n=10时:可以拆分为:3+3+4,所获得乘积最大
通过观察上述内容,我们可以发现从n=5开始,拆分的结果都有数字3。
之后的数字,例如11,可以先拆出来1个3,然后再看余下的8如何拆分。
class Solution {
public int integerBreak(int n) {
if(n==1 || n==4)
return n;
if(n==2 || n==3)
return n-1;
int result=1;
while(n>4){
result*=3;
n-=3;
}
return result*n;
}
}
91. 解码方法
https://leetcode-cn.com/problems/decode-ways/
class Solution {
public int numDecodings(String s) {
if(s==null||s.length()==0||s.charAt(0)=='0')
return 0;
int dp[]=new int[s.length()+1];
dp[0]= 1;
for(int i=1;i<s.length();i++){
int cur=s.charAt(i)-'0';
int pre=s.charAt(i-1)-'0';
if(cur==0){
if(pre<1 || pre>2){
return 0;
}
dp[i]=i>=2?dp[i-2]:1;
}else{
if (pre==1 || pre==2 && cur<=6) {
dp[i] = i>=2?dp[i - 2]+dp[i-1]:2;
}else{
dp[i]=dp[i-1];
}
}
}
return dp[s.length()-1];
}
}
最长递增子序列
300 最长上升子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
tails[i]:以i结尾的最长递增子序列。
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
int tails[]=new int[nums.length];
tails[0]=nums[0];
int len=1;
for(int i=1;i<nums.length;i++){
if(nums[i]>tails[len-1]){
tails[len++]=nums[i];
}else{
int temp=findFirst(tails,len,nums[i]);
tails[temp]=nums[i];
}
}
return len;
}
//二分查找,第一个大于等于des的数组索引
public int findFirst(int []nums,int len, int des){
int l=0;
int h=len-1;
while(l<=h){
int m=l+(h-l)/2;
if(nums[m]>=des){
h=m-1;
}else{
l=m+1;
}
}
return l;
}
}
376 摆动序列
https://leetcode-cn.com/problems/wiggle-subsequence/
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums==null || nums.length==0)
return 0;
int up=1;
int down=1;
for(int i=1;i<nums.length;i++){
if(nums[i]-nums[i-1]>0){
up=down+1;
}else if(nums[i]-nums[i-1]<0){
down=up+1;
}
}
return Math.max(down,up);
}
}
0-1 背包
416 分割等和子集
https://leetcode-cn.com/problems/partition-equal-subset-sum/
可以看成一个背包大小为 sum/2 的 0-1 背包问题。dp[0]=true;//初始条件
class Solution {
public boolean canPartition(int[] nums) {
if(nums==null || nums.length==0)
return false;
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
if(sum%2!=0)
return false;
int des=sum/2;
boolean dp[]=new boolean[des+1];
dp[0]=true;//初始条件
for(int num:nums){
for(int i=des;i>=num;i--){//0-1 背包一个物品只能用一次,故从后往前,先计算dp[i]再计算 dp[i-num]
dp[i]=dp[i] || dp[i-num];
}
}
return dp[des];
}
}
494 目标和
https://leetcode-cn.com/problems/target-sum/
该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
2 * sum(P) = target + sum(nums)
class Solution {
public int findTargetSumWays(int[] nums, int S) {
if(nums==null || nums.length==0)
return 0;
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
if (sum < S || (sum + S) % 2 == 1) {
return 0;
}
int des=(S+sum)/2;
int dp[]=new int[des+1];
dp[0]=1;
for(int num:nums){
for(int i=des;i>=num;i--){
dp[i]+=dp[i-num];
}
}
return dp[des];
}
}
474 一和零
https://leetcode-cn.com/problems/
这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
if(strs==null || strs.length==0)
return 0;
int dp[][]=new int[m+1][n+1];
for(String str:strs){
char ch[]=str.toCharArray();
int count_0=0;
int count_1=0;
for(int i=0;i<ch.length;i++){
if(ch[i]-'0'==0){
count_0++;
}else if(ch[i]-'0'==1){
count_1++;
}
}
for(int i=m;i>=count_0;i--){
for(int j=n;j>=count_1;j--){
dp[i][j]=Math.max(dp[i][j],dp[i-count_0][j-count_1]+1);
}
}
}
return dp[m][n];
}
}
322 零钱兑换
https://leetcode-cn.com/problems/coin-change/
方法一:因为硬币可以重复使用,因此这是一个完全背包问题。完全背包只需要将 0-1 背包中逆序遍历 dp 数组改为正序遍历即可。
class Solution {
public int coinChange(int[] coins, int amount) {
if(coins==null || coins.length==0 ){
return -1;
}
if(amount==0)
return 0;
int dp[]=new int[amount+1];
for(int coin:coins){
for(int i=coin;i<=amount;i++){
if(i==coin){
dp[i]=1;
}else if(dp[i]==0 && dp[i-coin]!=0){
dp[i]=dp[i-coin]+1;
}else if(dp[i-coin]!=0){
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
}
return dp[amount]==0?-1:dp[amount];
}
}
方法二:dfs+剪枝,比dp效果好
class Solution {
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
recursion(coins, amount, 0, coins.length - 1);
return minCount == Integer.MAX_VALUE ? -1 : minCount;
}
int minCount = Integer.MAX_VALUE;
/**
* 1、按金额从大到小,从多到少(排序,用余数一步到位)
* 2、count + amount / coins[index] >= minCount 预判低于最优解,终止递归(可以返回最优解,不过提升有限,意义不大)
* 3、能整除即可返回
*/
void recursion(int[] coins, int amount, int count, int index) {
if (index < 0 || count + amount / coins[index] >= minCount) return;
if (amount % coins[index] == 0) {
minCount = Math.min(minCount, count + amount / coins[index]);
return;
}
for (int i = amount / coins[index]; i >= 0; i--) {
recursion(coins, amount - i * coins[index], count + i, index - 1);
}
}
}
类似完全背包问题还有 518 零钱兑换 II
139 单词拆分
https://leetcode-cn.com/problems/word-break/
dict 中的单词没有使用次数的限制,因此这是一个完全背包问题。该问题涉及到字典中单词的使用顺序,因此可理解为涉及顺序的完全背包问题。
dp[i]表示字符串s的前i个字符能否拆分成wordDict
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 可以类比于背包问题
int n = s.length();
// memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分
boolean[] memo = new boolean[n + 1];
memo[0] = true;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (memo[j] && wordDict.contains(s.substring(j, i))) {
memo[i] = true;
break;
}
}
}
return memo[n];
}
}
377 组合总和 Ⅳ
https://leetcode-cn.com/problems/combination-sum-iv/
顺序的完全背包。
class Solution {
public int combinationSum4(int[] nums, int target) {
if(nums==null||nums.length==0)
return 0;
int dp[]=new int[target+1];
Arrays.sort(nums);
dp[0]=1;
for(int i=1;i<=target;i++){
for(int num:nums){
if(i>=num){
dp[i]=dp[i]+dp[i-num];
}else{
break;
}
}
}
return dp[target];
}
}
股票交易
309 最佳买卖股票时机含冷冻期
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
class Solution {
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0)
return 0;
int len=prices.length;
int s0[]=new int[len];
int s1[]=new int[len];
int s2[]=new int[len];
s0[0]=0;
s1[0]=-prices[0];
s2[0] = 0;
for(int i=1;i<prices.length;i++){
s0[i]=Math.max(s0[i-1],s2[i-1]);
s1[i]=Math.max(s1[i-1],s0[i-1]-prices[i]);
s2[i]=s1[i-1]+prices[i];
}
return Math.max(s0[len-1],s2[len-1]);
}
}
714 买卖股票的最佳时机含手续费
https://leetcode-cn.com/problems/coin-change/
class Solution {
public int maxProfit(int[] prices, int fee) {
if(prices==null||prices.length==0)
return 0;
int len=prices.length;
int s0[]=new int[len];
int s1[]=new int[len];
s0[0]=0;
s1[0]=-prices[0];
for(int i=1;i<len;i++){
s0[i]=Math.max(s0[i-1],s1[i-1]+prices[i]-fee);
s1[i]=Math.max(s1[i-1],s0[i-1]-prices[i]);
}
return Math.max(s0[len-1],s1[len-1]);
}
}
字符串编码
583 两个字符串的删除操作
https://leetcode-cn.com/problems/delete-operation-for-two-strings/
可以转换为求两个字符串的最长公共子序列问题。
class Solution {
public int minDistance(String word1, String word2) {
int m=word1.length();
int n=word2.length();
int dp[][]=new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return m+n-2*dp[m][n];
}
}
72 编辑距离
https://leetcode-cn.com/problems/edit-distance/
思想可参考:https://www.jianshu.com/p/a617d20162cf
假设序列S和T的长度分别为m和n, 两者的编辑距离表示为dp[m][n]. 则对序列进行操作时存在以下几种情况:
- a, 当S和T的末尾字符相等时, 对末尾字符不需要进行上述定义操作中(亦即"编辑")的任何一个, 也就是不需要增加计数. 则满足条件:
dp[m][n] = dp[m - 1][n - 1]
- b, 当S和T的末尾字符不相等时, 则需要对两者之一的末尾进行编辑, 相应的计数会增加1.
- b1, 对S的末尾进行修改, 以使之与T相等, 则此时
dp[m][n] = dp[m - 1][n - 1] + 1
- b2, 删除S末尾的元素, 使S与T相等, 则此时
dp[m][n] = dp[m - 1][n] + 1
- b3, 在S的末尾添加T的尾元素, 使S和T相等, 则此时S的长度变为
m+1
, 但是此时S和T的末尾元素已经相等, 只需要比较S的前m个元素与T的前n-1个元素, 所以满足dp[m][n] = dp[m][n - 1] + 1
;
public int minDistance(String word1, String word2) {
if (word1 == null || word2 == null) {
return 0;
}
int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= n; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
}
}
}
return dp[m][n];
}