文章目录
70. 爬楼梯
https://leetcode.cn/problems/climbing-stairs/
思路:对于3阶及以上的台阶n, 有两种选择可以到达台阶n, 一是从n-1级爬1阶到达,二是从n-2级爬2阶到达, 因此dp[n]=dp[n-1]+dp[n-2]
, 为了减小空间复杂度,可以使用两个变量代替dp数组
class Solution {
public int climbStairs(int n) {
if(n==1){
return 1;
}
if(n==2){
return 2;
}
int dp1=1;
int dp2=2;
for(int i=3;i<=n;i++){
int tmp=dp2;
dp2=dp1+dp2;
dp1=tmp;
}
return dp2;
}
}
//O(n)
//O(1)
198. 打家劫舍
https://leetcode.cn/problems/house-robber/
思路:
dp[i][0]:
表示第i间房子没有被偷可以获得的最大利润
dp[i][1]:
表示第i间房子被偷可以获得的最大利润
dp[i][0]=max(dp[i-1][0],dp[i-1][1]
: 第i间房子没有被偷可以有两个状态转移过来:1. 第i-1间房子被偷 2. 第i-1间房子没有被偷
dp[i][1]=max(dp[i-1][0]+nums[i],dp[i-1][1]
: 第i间房子被偷可以有两个状态转移过来:1. 第i-1间房子没有被偷,此时加上当前房屋的金额 2. 第i-1间房子没有被偷
class Solution {
public int rob(int[] nums) {
int n=nums.length;
int[][] dp=new int[n][2];
dp[0][0]=0;
dp[0][1]=nums[0];
for(int i=1;i<n;i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
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]);
}
}
//O(n)
//O(n)
可以使用变量来代替dp数组,使得空间复杂度为O(1)
class Solution {
public int rob(int[] nums) {
int n=nums.length;
int a=0;
int b=nums[0];
for(int i=1;i<n;i++){
int tmpA=a;
a=Math.max(a,b);
b=Math.max(tmpA+nums[i],b);
}
return Math.max(a,b);
}
}
//O(n)
//O(1)
213. 打家劫舍 II
https://leetcode.cn/problems/house-robber-ii/
思路:第一间房子和最后一间房子是相邻的,因此两间房子只能偷一间。第一个和最后一个不能同时抢。 所以:要么不抢第一个,要么不抢最后一个。 注意,不抢第一个的时候,最后一个可抢可不抢;另一种情况同理,因此可以看成两个问题,在区间[0,n-2]内的最大金额和区间[1,n-1]最大金额(转化为198. 打家劫舍中的问题),取二者中的最大值即可
class Solution {
public int rob(int[] nums) {
int n=nums.length;
if(n==0){
return 0;
}
if(n==1){
return nums[0];
}
//Arrays.copyOfRange 这里包括下标from,不包括上标to。[from,to)
int maxVal1=rob2(Arrays.copyOfRange(nums, 0, n - 1));//[0,n-1]区间的房子
int maxVal2=rob2(Arrays.copyOfRange(nums, 1, n));[1,n]区间的房子
return Math.max(maxVal1,maxVal2);
}
//下面的代码分析见198. 打家劫舍
public int rob2(int[] nums) {
int n=nums.length;
int a=0;
int b=nums[0];
for(int i=1;i<n;i++){
int tmpA=a;
a=Math.max(a,b);
b=Math.max(tmpA+nums[i],b);
}
return Math.max(a,b);
}
}
//O(n)
//O(1)
337. 打家劫舍 III
https://leetcode.cn/problems/house-robber-iii/
思路:记作f(o)为选取了以节点o作为根节点的子树中可以获取的最大价值,g(o)为没选取了以节点o作为根节点的子树中可以获取的最大价值,
则
f(o)=o.value+g(o.left)+g(o.right)
: 选择了节点o, o的左右子节点就不能再被选择了
g(o)=max(f(o.left),g(o.left))+max(f(o.right),g(o.right))
: 选择了节点o, o的左右子节点可以被选择也可以不被选择,取其中的最大值相加
class Solution {
HashMap<TreeNode,Integer> f=new HashMap<>();
HashMap<TreeNode,Integer> g=new HashMap<>();
public int rob(TreeNode root) {
dfs(root);
return Math.max(f.getOrDefault(root,0),g.getOrDefault(root,0));
}
public void dfs(TreeNode node){
if(node==null){
return;
}
dfs(node.left);
dfs(node.right);
f.put(node,node.val+g.getOrDefault(node.left,0)+g.getOrDefault(node.right,0));
int leftMax=Math.max(f.getOrDefault(node.left,0),g.getOrDefault(node.left,0));
int rightMax=Math.max(f.getOrDefault(node.right,0),g.getOrDefault(node.right,0));
g.put(node,leftMax+rightMax);
}
}
可以使用一个大小为2的数组返回来代替哈希表,减小空间的开销和操作的时间
class Solution {
public int rob(TreeNode root) {
int[] ans=dfs(root);
return Math.max(ans[0],ans[1]);
}
public int[] dfs(TreeNode node){
if(node==null){
return new int[]{0,0};
}
int[] leftVal=dfs(node.left);
int[] rightVal=dfs(node.right);
int selected=node.val+leftVal[1]+rightVal[1];
int notSelected=Math.max(leftVal[0],leftVal[1])+Math.max(rightVal[0],rightVal[1]);
return new int[]{selected,notSelected};
}
}
//O(n)
//o(n)
118. 杨辉三角
https://leetcode.cn/problems/pascals-triangle/
思路:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
下一行的第一个元素和最后一个元素是1,其他元素dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
dp[i][j]
表示第i行的第j个元素,不是第一个元素的话,该元素等于上一行同列的元素和上一行左边一列的元素之和
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ans=new ArrayList<>();
for(int i=0;i<numRows;i++){//[0,numsRows-1] 一共numRows行
List<Integer> tmp=new ArrayList<>();
for(int j=0;j<=i;j++){//第i行有i+1个元素(i从0开始) [0,i]
if(j==0||j==i){//第一个元素和最后一个元素
tmp.add(1);
}else{
//dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
tmp.add(ans.get(i-1).get(j-1)+ans.get(i-1).get(j));
}
}
ans.add(tmp);
}
return ans;
}
}
//O(numsRows^2)
//O(1) 返回值不计入空间
119. 杨辉三角 II
https://leetcode.cn/problems/pascals-triangle-ii/
思路:和118. 杨辉三角思路相同,118是给定numRows, 生成numRows行的数据,本题给出rowIndex, 实际上是要生成roeIndex+1行数据,返回第roeIndex+1行即可
class Solution {
public List<Integer> getRow(int rowIndex) {
List<List<Integer>> ans=new ArrayList<>();
for(int i=0;i<=rowIndex;i++){//[0,rowIndex] 一共rowIndex+1行
List<Integer> tmp=new ArrayList<>();
for(int j=0;j<=i;j++){//第i行有i+1个元素(i从0开始) [0,i]
if(j==0||j==i){//第一个元素和最后一个元素
tmp.add(1);
}else{
//dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
tmp.add(ans.get(i-1).get(j-1)+ans.get(i-1).get(j));
}
}
ans.add(tmp);
}
return ans.get(rowIndex);
}
}
//O(rowIndex^2)
//O(1)
1143. 最长公共子序列
[https://leetcode.cn/problems/longest-common-subsequence/](https://leetcode.cn/problems/longest-common-subsequence/
思路:记作dp[i][j]
为text1[0:i-1]和text2[0:j-1]区间内的最长公共子序列的长度,当i=0或j=0时表示一方是空串,这是base情况,此时dp[0]j]
或dp[i][0]
的值为0, 当i>=1和j>=1时,如果text1[i]=text2[j], 说明公共部分长度可以加1,不相等的话,取dp[i][j-1]和dp[i-1][j]
中的较大值
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m=text1.length(),n=text2.length();
int[][] dp=new int[m+1][n+1];
//dp[i][j]表示text1[0:i-1] text1[0:j-1] 的LCS长度
//当i=0时 dp[0][j]=0 表示当text1为空串时 LCS长度为0
//当j=0时 dp[i][0]=0 表示当text2为空串时 LCS长度为0
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(text1.charAt(i-1)==text2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
}else if(dp[i-1][j]>dp[i][j-1]){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=dp[i][j-1];
}
}
}
return dp[m][n];
}
}
//O(mn)
//O(mn)
221. 最大正方形
https://leetcode.cn/problems/maximal-square/
思路1:暴力法,当遇到某个位置(i,j)
是1时,判断以该点为正方形左上角点的正方形的最大边长,记为k, 然后判断以左上角(i,j)—>右下角(i+k-1,j+k-1)区间内的元素是否都是1, 在判断的时候我们可以同时判断某一行和某一列,而不是使用二重for循环取逐个元素进行判断
class Solution {
public int maximalSquare(char[][] matrix) {
int maxEdge=0;
if(matrix==null||matrix.length==0||matrix[0].length==0){
return maxEdge;
}
int m=matrix.length,n=matrix[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(matrix[i][j]=='1'){
int limit=Math.min(m-i,n-j);
for(int k=1;k<=limit;k++){
boolean flag=true;
for(int h=0;h<k;h++){
//matrix[i+k-1][j+h]=='0' 判断第k-1行是否都是1
//matrix[i+h][j+k-1]=='0' 判断第k-1列是否都是1
if(matrix[i+k-1][j+h]=='0'||matrix[i+h][j+k-1]=='0'){
flag=false;
break;
}
}
if(flag){
maxEdge=Math.max(maxEdge,k);
}else{
break;
}
}
}
}
}
return maxEdge*maxEdge;
}
}
//O(mn*min(m,n)^2)
//O(1)
思路2: 动态规划,用dp[i][j]
表示以(i,j)
为右下角的满足正方形的最大边长
class Solution {
public int maximalSquare(char[][] matrix) {
int maxEdge=0;
if(matrix==null||matrix.length==0||matrix[0].length==0){
return maxEdge;
}
int m=matrix.length,n=matrix[0].length;
int[][] dp=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(matrix[i][j]=='1'){
if(i==0||j==0){
dp[i][j]=1;
}else{
dp[i][j]=Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
}
}
maxEdge=Math.max(maxEdge,dp[i][j]);
}
}
return maxEdge*maxEdge;
}
}
//O(mn)
//O(mn)
300. 最长递增子序列
https://leetcode.cn/problems/longest-increasing-subsequence/
思路:
记作dp[i]
为以nums[i]
结尾的的最长上升子序列的长度,在计算dp[i]
之前,dp[0]---dp[i-1]
已经被计算过,j在区间[0–i-1]内,如果nums[i]>nums[j]
, 则元素nums[i]
可以连在以nums[j]
结尾的子序列后面,此时长度加一,得到一个更长的上升子序列
之后统计以每个nums[i]结尾的上升子序列的长度,取最大值
dp[i]=max(dp[i],dp[j]+1) 0<=j<i&nums[i]>nums[j]
class Solution {
public int lengthOfLIS(int[] nums) {
int n=nums.length;
int maxLen=0;
int[] dp=new int[n];
for(int i=0;i<n;i++){
dp[i]=1;//单个数字形成的序列长度为1
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
maxLen=Math.max(maxLen,dp[i]);
}
return maxLen;
}
}
//O(n^2)
//O(n)
873. 最长的斐波那契子序列的长度
https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/
思路:
记作dp[i][j] i<j
表示以arr[i] arr[j]结尾的斐波那契数列的长度,如果在区间[0,i-1]内有k满足arr[k]+arr[i]=arr[j] 则以arr[i] arr[j]结尾的斐波那契数列的长度可以在arr[k] arr[i]结尾的斐波那契数列的长度的基础上加一
因此dp[i][j]=max(dp[k][i]+1,3) 0<=k<i
这里和3比较是因为当dp[k][i]<3
时,arr[k] arr[j]结尾的数列虽然不是斐波那契数列,但是加上arr[j]就是一个斐波那契数列了。 另外注意一个细节,因为数组是递增的,当arr[i]+arr[i]<=arr[j]时,可以停止j的往后搜索了,因为不可能找到一个k, 使得arr[k]+arr[i]=arr[j]
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n=arr.length;
int[][] dp=new int[n][n];
HashMap<Integer,Integer> cnt=new HashMap<>();
for(int i=0;i<n;i++){
cnt.put(arr[i],i);
}
int maxLen=0;
for(int i=1;i<n-1;i++){//i取值[1,n-2]
for(int j=i+1;j<n;j++){
if(2*arr[i]<=arr[j]){//arr[index]<arr[i] 如果arr[i]+arr[i]<=arr[j]
//一定有arr[index]+arr[i]<arr[j] 此时不需要往后寻找了 不会有满足条件的j 提前结束内循环
break;
}
int index=cnt.getOrDefault(arr[j]-arr[i],-1);
//index>=0 说明存在arr[j]-arr[i]=arr[index]-->arr[j]=arr[index]+arr[i]
//index<i 因为要求arr[index]+arr[i]=arr[j] index<i<j
if(index>=0&&index<i){
//若dp[index][i]<3--->dp[i][j]=3
//若dp[index][i]>=3--->dp[i][j]=dp[index][i]+1
dp[i][j]=Math.max(3,dp[index][i]+1);//这里不需要dp[i][j]=max(dp[i][j],xxx)
//因为dp[i][j]只会出现一次
}
maxLen=Math.max(maxLen,dp[i][j]);
}
}
return maxLen;
}
}
//O(n^2)
//O(n^2)