leecode更新

This commit is contained in:
markilue 2022-12-11 15:44:41 +08:00
parent 5f1ccb4541
commit 620b260e4a
5 changed files with 451 additions and 6 deletions

View File

@ -0,0 +1,128 @@
package com.markilue.leecode.dynamic;
import org.junit.Test;
/**
* @BelongsProject: Leecode
* @BelongsPackage: com.markilue.leecode.dynamic
* @Author: dingjiawen
* @CreateTime: 2022-12-11 10:04
* @Description: TODO 力扣198题 打家劫舍
* 你是一个专业的小偷计划偷窃沿街的房屋每间房内都藏有一定的现金影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统
* 如果两间相邻的房屋在同一晚上被小偷闯入系统会自动报警
* 给定一个代表每个房屋存放金额的非负整数数组计算你 不触动警报装置的情况下 一夜之内能够偷窃到的最高金额
* @Version: 1.0
*/
public class T18_Rob {
@Test
public void test() {
int[] nums = {1, 2, 3, 1};
System.out.println(rob(nums));
}
@Test
public void test1() {
int[] nums = {2,7,9,3,1};
System.out.println(rob1(nums));
}
@Test
public void test2() {
int[] nums = {4,1,2,3};
System.out.println(rob1(nums));
}
/**
* 思路本质上就是只能隔一个取甚至可以不用动态规划直接奇偶分别相加这里先试试直接奇偶行不行?
* 不行因为只是相邻不能进事实上隔两个就可以如果两个数恰好隔两个使用奇偶就错开了,尝试动态规划法
* TODO 动态规划五部曲:
* (1)dp定义:dp[i]表示在i这个位置所能得到的最大金钱
* (2)dp状态转移方程:dp[i]=max(dp[i-3]+nums[i],dp[i-2]+nums[i],dp[i-1]),即当前最大可能是上一个最大或者隔一个的加上自己或者隔两个的加上自己
* (3)dp初始化:dp[0]=nums[0];dp[1]=nums[1]>num[0]?nums[1]:nums[0]
* (4)dp的遍历顺序:从前往后
* (4)dp举例推导:若nums={2,7,9,3,1},则dp={2,7,11,11,12}
* 速度击败100%内存击败43.6%
* @param nums
* @return
*/
public int rob(int[] nums) {
if(nums.length==1){
return nums[0];
}
if(nums.length==2){
return nums[0]>nums[1]?nums[0]:nums[1];
}
int[] dp = new int[nums.length];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
dp[2]=Math.max(nums[1],nums[0]+nums[2]);
for (int i = 3; i < dp.length; i++) {
dp[i]=Math.max(Math.max(dp[i-3]+nums[i],dp[i-2]+nums[i]),dp[i-1]);
}
return dp[nums.length-1];
}
/**
* 代码随想录动态规划法本人想复杂了状态转换没有那么麻烦
* 因为由于有Math.max(dp[i-2]+nums[i],dp[i-1]);
* 所以就算dp[i-3]>dp[i-2],也会因为这个转移方程将dp[i-2]赋值为dp[i-3],所以本人的有所多余
* dp举例推导:若nums={2,7,9,3,1},则dp={2,7,11,11,12}
* 速度击败100%内存击败54.93%
* @param nums
* @return
*/
public int rob1(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
if(nums.length==1){
return nums[0];
}
int[] dp = new int[nums.length];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for (int i = 2; i < dp.length; i++) {
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.length-1];
}
/**
* 官方动态规划法:将空间复杂度降到O(1)
* 速度击败100%内存击败55.39%
* @param nums
* @return
*/
public int rob2(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}

View File

@ -0,0 +1,111 @@
package com.markilue.leecode.dynamic;
import org.junit.Test;
import java.util.*;
/**
* @BelongsProject: Leecode
* @BelongsPackage: com.markilue.leecode.dynamic
* @Author: dingjiawen
* @CreateTime: 2022-12-11 10:58
* @Description:
* TODO 力扣213题 打家劫舍II
* 你是一个专业的小偷计划偷窃沿街的房屋每间房内都藏有一定的现金这个地方所有的房屋都 围成一圈
* 这意味着第一个房屋和最后一个房屋是紧挨着的
* 同时相邻的房屋装有相互连通的防盗系统如果两间相邻的房屋在同一晚上被小偷闯入系统会自动报警
* 给定一个代表每个房屋存放金额的非负整数数组计算你 在不触动警报装置的情况下 今晚能够偷窃到的最高金额
* @Version: 1.0
*/
public class T19_Rob {
@Test
public void test(){
int[] nums = {1, 2, 3, 1};
System.out.println(rob2(nums));
}
@Test
public void test1(){
int[] nums = {0,0};
System.out.println(rob1(nums));
}
/**
* 思路与打家劫舍类似只是边界条件不同最后一个与第一个连起来了
* 那么状态转移方程之类的都不变
* 用了最后一个有什么特点dp[last]!=dp[last-1]
* 但是没法记录中间使用过的数就算记录了好像也没办法增加或者删掉,这里浅试一下
* @param nums
* @return
*/
public int rob(int[] nums) {
if(nums==null||nums.length==0)return 0;
if(nums.length==1)return nums[0];
int[][] dp = new int[nums.length][nums.length/2]; //dp数组[max][使用过的下标]这里不对没有想到什么好的数据结构
// dp[0]=nums[0];
// dp[1]=Math.max(nums[0],nums[1]);
//
// for (int i = 2; i < nums.length; i++) {
// dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
// }
// int last= nums.length-1;
//
// return dp[last]-nums[last]>dp[last-1]?dp[last]-nums[last]:dp[last-1];
return 0;
}
/**
* 代码随想录思路与打家劫舍类似只是边界条件不同最后一个与第一个连起来了
* 那么只需要分别计算两次只有头和只有尾的dp,取最大即可
* 速度击败100%内存击败10%
* @param nums
* @return
*/
public int rob1(int[] nums){
if(nums==null||nums.length==0)return 0;
if(nums.length==1)return nums[0];
int dpHead = dp(nums,0, nums.length-1);
int dpTail=dp(nums,1, nums.length);
return Math.max(dpHead,dpTail);
}
private int dp(int[] nums,int start,int end) {
if(start==end-1)return nums[start];
int length=end-start;
int[] dp = new int[length]; //dp数组[max][使用过的下标]这里不对没有想到什么好的数据结构
dp[0]=nums[start];
dp[1]=Math.max(nums[start],nums[start+1]);
for (int i = 2; i < length; i++) {
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[start+i]);
}
return dp[length-1];
}
/**
* 评论区一次dp完成法分两种情况要么不抢0位置要么不抢n-1位置这样就可以包括所有的情况了
* 而不是抢0或者抢n-1
* 速度击败100%内存击败90%
* @param nums
* @return
*/
public int rob2(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
int[] dp1 = new int[n], dp2 = new int[n]; // dp1不抢n-1,dp2不抢0
dp1[0] = nums[0];
dp1[1] = Math.max(nums[1], nums[0]);
dp2[1] = nums[1];
for (int i = 2; i < n; ++i) {
dp1[i] = Math.max(dp1[i - 1], dp1[i - 2] + nums[i]);
dp2[i] = Math.max(dp2[i - 1], dp2[i - 2] + nums[i]);
}
return Math.max(dp1[n - 2], dp2[n - 1]);
}
}

View File

@ -0,0 +1,150 @@
package com.markilue.leecode.dynamic;
import com.markilue.leecode.tree.TreeNode;
import com.markilue.leecode.tree.TreeUtils;
import org.junit.Test;
import java.util.*;
/**
* @BelongsProject: Leecode
* @BelongsPackage: com.markilue.leecode.dynamic
* @Author: dingjiawen
* @CreateTime: 2022-12-11 12:15
* @Description: TODO 力扣337题 打家劫舍III:
* 小偷又发现了一个新的可行窃的地区这个地区只有一个入口我们称之为 root
* 除了 root 之外每栋房子有且只有一个房子与之相连一番侦察之后聪明的小偷意识到这个地方的所有房屋的排列类似于一棵二叉树
* 如果 两个直接相连的房子在同一天晚上被打劫 房屋将自动报警
* 给定二叉树的 root 返回 在不触动警报的情况下 小偷能够盗取的最高金额
* @Version: 1.0
*/
public class T20_Rob {
@Test
public void test() {
List<Integer> valList = new ArrayList<>(Arrays.asList(3, 2, 3, null, 3, null, 1));
TreeNode root = TreeUtils.structureTree(valList,0);
System.out.println(rob1(root));
}
@Test
public void test1() {
List<Integer> valList = new ArrayList<>(Arrays.asList(4,1,null,2,null,null,null,3));
TreeNode root = TreeUtils.structureTree(valList,0);
System.out.println(rob1(root));
}
/**
* 本质上就是遍历二叉树但是必须是后续遍历根据后序遍历再来决定
* 这里是错的使用了层序遍历误以为一层使用了一个那么下面所有层都用不了但实际上只有其左右节点不能用
* @param root
* @return
*/
public int rob(TreeNode root) {
if(root==null)return 0;
if(root.left==null&&root.right==null){
return root.val;
}
int first=root.val;
int second=0;
LinkedList<TreeNode> queue = new LinkedList<>();
TreeNode left = root.left;
TreeNode right = root.right;
if(left!=null){
second+=left.val;
if(left.left!=null)queue.addLast(left.left);
if(left.right!=null)queue.addLast(left.right);
}
if(right!=null){
second+=right.val;
if(right.left!=null)queue.addLast(right.left);
if(right.right!=null)queue.addLast(right.right);
}
second=Math.max(first,second);
while (!queue.isEmpty()){
int size = queue.size();
int add=0;
for (int i = 0; i <size; i++) {
TreeNode node = queue.poll();
add+=node.val;
if(node.left!=null)queue.addLast(node.left);
if(node.right!=null)queue.addLast(node.right);
}
int temp=second;
second=Math.max(second,first+add);
first=temp;
}
return second;
}
/**
* 动态规划法尝试
* 速度击败100%内存击败31%
* @param root
* @return
*/
public int rob1(TreeNode root) {
int[] dfs = dfs(root);
return Math.max(dfs[0],dfs[1]);
}
/**
* 偷不偷当前节点 {偷当前节点1,不偷当前节点}
* @param node
* @return
*/
private int[] dfs(TreeNode node){
if(node==null) return new int[2];
int[] leftSteal = dfs(node.left);
int[] rightSteal = dfs(node.right);
//偷当前节点:不偷左边+不偷右边+偷当前
int steal = node.val + leftSteal[1]+ rightSteal[1];
//不偷当前节点:偷不偷左边最大的+偷不偷右边最大的
int noSteal = Math.max(leftSteal[1],leftSteal[0])+Math.max(rightSteal[1],rightSteal[0]);
return new int[]{steal,noSteal};
}
/**
* 代码随想录记忆搜索递归法
* 速度击败39.67%内存击败6.1%
* @param root
* @return
*/
// 2.递归去偷记录状态
// 执行用时3 ms , 在所有 Java 提交中击败了 56.24% 的用户
public int rob2(TreeNode root) {
Map<TreeNode, Integer> memo = new HashMap<>();
return robAction(root, memo);
}
int robAction(TreeNode root, Map<TreeNode, Integer> memo) {
if (root == null)
return 0;
if (memo.containsKey(root))
return memo.get(root);
int money = root.val;
//计算不要下一层的
if (root.left != null) {
money += robAction(root.left.left, memo) + robAction(root.left.right, memo);
}
if (root.right != null) {
money += robAction(root.right.left, memo) + robAction(root.right.right, memo);
}
//后面是要下一层的
int res = Math.max(money, robAction(root.left, memo) + robAction(root.right, memo));
memo.put(root, res);
return res;
}
}

View File

@ -1,22 +1,25 @@
package com.markilue.leecode.tree;
import lombok.Data;
import org.junit.Test;
import java.util.*;
@Data
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public int val;
public TreeNode left;
public TreeNode right;
TreeNode() {
public TreeNode() {
}
TreeNode(int val) {
public TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
@ -31,4 +34,6 @@ public class TreeNode {
'}';
}
}

View File

@ -0,0 +1,51 @@
package com.markilue.leecode.tree;
import java.util.LinkedList;
import java.util.List;
/**
* @BelongsProject: Leecode
* @BelongsPackage: com.markilue.leecode.tree
* @Author: dingjiawen
* @CreateTime: 2022-12-11 12:42
* @Description: TODO 二叉树的实用工具
* @Version: 1.0
*/
public class TreeUtils {
public static TreeNode structureTree(List<Integer> valList, int index){
if(index>=valList.size())return null;
TreeNode root;
Integer val = valList.get(index);
if(val!=null){
root = new TreeNode(val);
}else {
return null;
}
//构造左子树
root.left=structureTree(valList,2*index+1);
//构造右子树
root.right=structureTree(valList,2*(index+1));
return root;
}
public static void printTreeByLevel(TreeNode root){
LinkedList<TreeNode> treeNodes = new LinkedList<>();
treeNodes.addFirst(root);
while (!treeNodes.isEmpty()){
int size = treeNodes.size();
for (int i = 0; i < size; i++) {
TreeNode node = treeNodes.poll();
if(node!=null){
System.out.print(node.val+" ");
treeNodes.addLast(node.left);
treeNodes.addLast(node.right);
}else {
System.out.print("null"+" ");
}
}
System.out.println();
}
}
}