diff --git a/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/TCPServer.java b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/TCPServer.java new file mode 100644 index 0000000..0a98638 --- /dev/null +++ b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/TCPServer.java @@ -0,0 +1,84 @@ +package com.markilue.java_learning.network; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + *@BelongsProject: java_learning + *@BelongsPackage: com.markilue.java_learning.network + *@Author: dingjiawen + *@CreateTime: 2022-12-22 18:54 + *@Description: TODO TCP套接字通信编程实例 + *@Version: 1.0 + */ +public class TCPServer { + + //服务端代码 + public static void main(String[] args) throws IOException { + //创建一个ServerSocket,用于监听客户端的连接请求 + ServerSocket serverSocket = new ServerSocket(); + //绑定一个地址和端口,此后通过这个地址和端口监听客户端的请求 + serverSocket.bind(new InetSocketAddress("127.0.0.1",8000)); + + //循环不断接受来自客户端的连接 + while (true){ + System.out.println("创建好了连接,等待客户端"); + //接收到了客户端的连接 + Socket socket = serverSocket.accept(); + System.out.println("收到客户端请求并接受"); + //io流交互通信 + PrintStream printStream = new PrintStream(socket.getOutputStream(), true, "UTF-8"); + printStream.println("服务器说:"+socket.getInetAddress()+",来了老弟"); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + System.out.println("收到来自客户端的信息:"+in.readLine()); + System.out.println("接受完毕,准备关闭监听"); + //关闭 + printStream.close(); + in.close(); + socket.close(); +// break; + + } + + /* + 打印结果: + 创建好了连接,等待客户端 + 收到客户端请求并接受 + 收到来自客户端的信息:客户端向您问好 + 接受完毕,准备关闭监听 + 创建好了连接,等待客户端 + */ + + } + +} + +//TCP客户端 +class TCPClient{ + + + public static void main(String[] args) throws IOException { + //创建一个Socket,向服务器发出连接请求 + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("127.0.0.1",8000));//连接指定端口 + + //IO流交互通信 + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + System.out.println("来自服务器端的信息:"+reader.readLine()); + //向服务器写数据 + PrintStream ps = new PrintStream(socket.getOutputStream(), true, "UTF-8"); + ps.println("客户端向您问好"); + + //关闭连接 + reader.close(); + socket.close(); + //打印结果:来自服务器端的信息:服务器说:/127.0.0.1,来了老弟 + + } + +} diff --git a/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/UDPServer.java b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/UDPServer.java new file mode 100644 index 0000000..acaaff5 --- /dev/null +++ b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/network/UDPServer.java @@ -0,0 +1,76 @@ +package com.markilue.java_learning.network; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; + +/** + *@BelongsProject: java_learning + *@BelongsPackage: com.markilue.java_learning.network + *@Author: dingjiawen + *@CreateTime: 2022-12-22 19:15 + *@Description: TODO UDP套接字通信 + *@Version: 1.0 + */ +public class UDPServer { + + public static void main(String[] args) throws IOException { + System.out.println("---接收方启动中---"); + //1.使用DatagramSocket指定端口 创建接收端 + DatagramSocket server = new DatagramSocket(8000); + //2.准备容器 封装成DatagramPacket包裹 + byte[] container = new byte[1024 * 60]; + DatagramPacket packet = new DatagramPacket(container, 0, container.length); + //3.阻塞式接受包裹receive(DatagramPacket p) + server.receive(packet); + //4.分析数据 byte[] getData() getLength() + byte[] data = packet.getData(); + int length = packet.getLength(); + //操作获取到的数据 + System.out.println(new String(data,0,length)); + //返回给客户端信息 + packet.setData("服务器收到了".getBytes()); + server.send(packet); + //5.释放资源 + server.close(); + + /* + ---接收方启动中--- + 客户端发送请求 + */ + } +} + + +class UDPClient{ + + public static void main(String[] args) throws IOException { + System.out.println("---发送方启动中---"); + //1.使用DatagramSocket指定端口 创建发送端 + DatagramSocket client = new DatagramSocket(8888); + //2.准备一定数据,并转为字节数组 + String data="客户端发送请求"; + byte[] datas = data.getBytes(); + //3.封装成DatagramPacket包裹,需要指定目的地 + //传入参数为 (数据集,数据初始位置即0,,数据长度,接收端对象(接收端地址,接收端端口)) + DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 8000)); + + //4.发送send(DatagramPacket p) + client.send(packet); + //接受服务器返回的消息 + client.receive(packet); + //操作获取到的数据 + System.out.println(new String(packet.getData(),0,packet.getLength())); + //5.释放资源 + client.close(); + + /* + ---发送方启动中--- + 服务器收到了 + */ + + } +} diff --git a/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/thread/MemoryBarrier.java b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/thread/MemoryBarrier.java new file mode 100644 index 0000000..249fa4e --- /dev/null +++ b/Big_data_example/java_learning/src/main/java/com/markilue/java_learning/thread/MemoryBarrier.java @@ -0,0 +1,73 @@ +package com.markilue.java_learning.thread; + +import lombok.Getter; +import org.junit.Test; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +/** + *@BelongsProject: java_learning + *@BelongsPackage: com.markilue.java_learning.thread + *@Author: dingjiawen + *@CreateTime: 2023-01-08 17:12 + *@Description: + * TODO 测试内存屏障: + * 使用unsafe类中的loadFence增加读屏障,保证内存中的数据一致性 + *@Version: 1.0 + */ +public class MemoryBarrier { + + @Test + public void test() throws NoSuchFieldException, IllegalAccessException { + String s="dingjiawen"; + System.out.println("first s:"+s); + Field value = s.getClass().getDeclaredField("value"); + value.setAccessible(true); + char[] o = (char[])value.get(s); + o[1]='b'; +// value.set(s,"dingjin"); + System.out.println("last s:"+s); + } + + public static void main(String[] args){ + Unsafe unsafe=null; + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe) field.get(null); + } catch (Exception e) { + } + ChangeThread changeThread = new ChangeThread(); + new Thread(changeThread).start(); + while (true) { + + boolean flag = changeThread.isFlag(); +// System.out.println(flag);//奇怪的是,只要加上这句,好像就可以检测到flag的变化 +// unsafe.loadFence(); //加入读内存屏障 + if (flag){ + System.out.println("detected flag changed"); + break; + } + } + System.out.println("main thread end"); + } + +} + +@Getter +class ChangeThread implements Runnable{ + /**volatile**/ boolean flag=false; + @Override + public void run() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("subThread change flag to:" + flag); + flag = true; + System.out.println("subThread change flag to:" + flag); + } +} + diff --git a/Leecode/src/main/java/com/markilue/leecode/array/BinarySearch.java b/Leecode/src/main/java/com/markilue/leecode/array/T01_BinarySearch.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/array/BinarySearch.java rename to Leecode/src/main/java/com/markilue/leecode/array/T01_BinarySearch.java index 277a04c..c25f4ef 100644 --- a/Leecode/src/main/java/com/markilue/leecode/array/BinarySearch.java +++ b/Leecode/src/main/java/com/markilue/leecode/array/T01_BinarySearch.java @@ -5,7 +5,7 @@ package com.markilue.leecode.array; * 描述:给定一个n个元素有序的(升序)整型数组nums 和一个目标值target ,写一个函数搜索nums中的 target,如果目标值存在返回下标,否则返回 -1。 * */ -public class BinarySearch { +public class T01_BinarySearch { public static void main(String[] args) { int[] nums = {-1,0,3,5,9,12}; diff --git a/Leecode/src/main/java/com/markilue/leecode/array/RemoveElement.java b/Leecode/src/main/java/com/markilue/leecode/array/T02_RemoveElement.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/array/RemoveElement.java rename to Leecode/src/main/java/com/markilue/leecode/array/T02_RemoveElement.java index eb6cdc8..9a1e2b4 100644 --- a/Leecode/src/main/java/com/markilue/leecode/array/RemoveElement.java +++ b/Leecode/src/main/java/com/markilue/leecode/array/T02_RemoveElement.java @@ -11,7 +11,7 @@ import java.util.Arrays; * 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 * 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 */ -public class RemoveElement { +public class T02_RemoveElement { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/array/MinSubArrayLen.java b/Leecode/src/main/java/com/markilue/leecode/array/T03_MinSubArrayLen.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/array/MinSubArrayLen.java rename to Leecode/src/main/java/com/markilue/leecode/array/T03_MinSubArrayLen.java index 65daa65..edc9fbb 100644 --- a/Leecode/src/main/java/com/markilue/leecode/array/MinSubArrayLen.java +++ b/Leecode/src/main/java/com/markilue/leecode/array/T03_MinSubArrayLen.java @@ -7,7 +7,7 @@ import javax.management.remote.rmi._RMIConnection_Stub; /** * 寻找长度最小的子数组 */ -public class MinSubArrayLen { +public class T03_MinSubArrayLen { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/array/generateMatrix.java b/Leecode/src/main/java/com/markilue/leecode/array/T04_generateMatrix.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/array/generateMatrix.java rename to Leecode/src/main/java/com/markilue/leecode/array/T04_generateMatrix.java index ada702b..35eee09 100644 --- a/Leecode/src/main/java/com/markilue/leecode/array/generateMatrix.java +++ b/Leecode/src/main/java/com/markilue/leecode/array/T04_generateMatrix.java @@ -8,7 +8,7 @@ import java.util.Arrays; * 螺旋矩阵: * 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix */ -public class generateMatrix { +public class T04_generateMatrix { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/array/T05_SortedSquares.java b/Leecode/src/main/java/com/markilue/leecode/array/T05_SortedSquares.java new file mode 100644 index 0000000..c4f9b88 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/array/T05_SortedSquares.java @@ -0,0 +1,105 @@ +package com.markilue.leecode.array; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.array + *@Author: dingjiawen + *@CreateTime: 2022-12-22 11:02 + *@Description: + * TODO 力扣977题 有序数组的平方: + * 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 + *@Version: 1.0 + */ +public class T05_SortedSquares { + @Test + public void test(){ + int[] nums = {-4, -1, 0, 3, 10}; + System.out.println(Arrays.toString(sortedSquares(nums))); + } + + @Test + public void test1(){ + int[] nums = {-7,-3,2,3,11}; + System.out.println(Arrays.toString(sortedSquares1(nums))); + } + + @Test + public void test2(){ + int[] nums = {-10000,-3,2,3,10000}; + System.out.println(Arrays.toString(sortedSquares(nums))); + } + + /** + * 思路:先找到最接近0的数,然后左右比较进行遍历 + * 时间复杂度应该在O(N),空间复杂度也是O(N) + * 速度击败100%,内存击败43.93% + * @param nums + * @return + */ + public int[] sortedSquares(int[] nums) { + //寻找第一个大于0的数 + int targetIndex = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] <= 0) targetIndex++; + } + //在第一个大于0的数旁边左右遍历 + int left = targetIndex - 1; + int index = 0; + int[] result = new int[nums.length]; + while (left >= 0 && targetIndex <= nums.length - 1) { + if(-nums[left]>nums[targetIndex]){ + result[index++]=nums[targetIndex]*nums[targetIndex]; + targetIndex++; + }else { + result[index++]=nums[left]*nums[left]; + left--; + } + } + //后面的用完了 + while (left>=0){ + result[index++]=nums[left]*nums[left]; + left--; + } + //前面的用完了 + while (targetIndex<= nums.length - 1){ + result[index++]=nums[targetIndex]*nums[targetIndex]; + targetIndex++; + } + + return result; + + + } + + + /** + * 代码随想录思路:虽然最小的数不知道,但最大的数一定是在数组的两边产生;避免找0的过程 + * 时间复杂度在O(N),空间复杂度也是O(N) + * 速度击败100%,内存击败63.68% + * @param nums + * @return + */ + public int[] sortedSquares1(int[] nums) { + + //从两边开始双向奔赴遍历,两边的最大值一定是最大值 + int right=nums.length-1; + int left = 0; + int index = nums.length-1; + int[] result = new int[nums.length]; + while (left <=right) { + if(-nums[left]>nums[right]){ + result[index--]=nums[left]*nums[left]; + left++; + }else { + result[index--]=nums[right]*nums[right]; + right--; + } + } + + return result; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/array/second/T01_BinarySearch.java b/Leecode/src/main/java/com/markilue/leecode/array/second/T01_BinarySearch.java new file mode 100644 index 0000000..c857c20 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/array/second/T01_BinarySearch.java @@ -0,0 +1,65 @@ +package com.markilue.leecode.array.second; + +/** + * 二刷 + * 力扣题号704:二分查找 + * 描述:给定一个n个元素有序的(升序)整型数组nums 和一个目标值target ,写一个函数搜索nums中的 target,如果目标值存在返回下标,否则返回 -1。 + * + */ +public class T01_BinarySearch { + + public static void main(String[] args) { + int[] nums = {-1,0,3,5,9,12}; + int target = 9; + + System.out.println(search(nums,target)); + } + + + /** + * 非递归法 + * @param nums + * @param target + * @return + */ + public static int search(int[] nums, int target) { + + int left=0; + int right=nums.length-1; + + while (left<=right){ + int mid=left+((right-left)>>1); + + if(nums[mid]target){ + right=mid-1; + }else { + return mid; + } + } + //出来了还没找到 + return -1; + + + + } + + + /** + * 递归法 + * @param nums + * @param target + * @return + */ + public static int search1(int[] nums, int target) { + + return 0; + } + + public static int find(int[] nums,int start,int stop, int target){ + + + return 0; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/array/second/T03_MinSubArrayLen.java b/Leecode/src/main/java/com/markilue/leecode/array/second/T03_MinSubArrayLen.java new file mode 100644 index 0000000..683d247 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/array/second/T03_MinSubArrayLen.java @@ -0,0 +1,64 @@ +package com.markilue.leecode.array.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.array.second + *@Author: dingjiawen + *@CreateTime: 2022-12-23 10:16 + *@Description: + *TODO 二次做209题 长度最小的子数组: + * 给定一个含有 n 个正整数的数组和一个正整数 target 。 + * 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 + *@Version: 1.0 + */ +public class T03_MinSubArrayLen { + + @Test + public void test(){ + int target = 7; + int[] nums = {2, 3, 1, 2, 4, 3}; + System.out.println(minSubArrayLen(target,nums)); + } + + @Test + public void test1(){ + int target = 7; + int[] nums = {1, 7, 7}; + System.out.println(minSubArrayLen(target,nums)); + } + + @Test + public void test2(){ + int target = 7; + int[] nums = {1, 1, 1}; + System.out.println(minSubArrayLen(target,nums)); + } + + + /** + * 思路:构造一个滑动窗口一直往后滑,滑动窗口刚好小于等于target + * @param target + * @param nums + * @return + */ + public int minSubArrayLen(int target, int[] nums) { + + int start=0;//窗口的开始 + int windowSum=0;//窗口数值总和 + int result=Integer.MAX_VALUE; + + for (int end = 0; end < nums.length; end++) { + windowSum+=nums[end]; + while (windowSum>=target){ + result=Math.min(result,end-start+1); + windowSum-=nums[start]; + start++; + } + } + + return result==Integer.MAX_VALUE?0:result; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/array/second/T04_generateMatrix.java b/Leecode/src/main/java/com/markilue/leecode/array/second/T04_generateMatrix.java new file mode 100644 index 0000000..c9bb030 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/array/second/T04_generateMatrix.java @@ -0,0 +1,63 @@ +package com.markilue.leecode.array.second; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.array.second + *@Author: dingjiawen + *@CreateTime: 2022-12-23 10:30 + *@Description: + * TODO 二次做59题 螺旋数组II: + * 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix + *@Version: 1.0 + */ +public class T04_generateMatrix { + + @Test + public void test(){ + int n=5; + int[][] result = generateMatrix(n); + for (int i = 0; i < n; i++) { + System.out.println(Arrays.toString(result[i])); + } + } + + + /** + * 本质上就是寻找循环不变量 + * @param n + * @return + */ + public int[][] generateMatrix(int n) { + + int[][] result = new int[n][n]; + int count = 1; + + //转几圈 + for (int i = 0; i < n / 2; i++) { + //从左往右;从上到下;从右往左;从下上到上; + int leftIndex = i; + int rightIndex = i; + for (; rightIndex < n - i-1; rightIndex++) { + result[leftIndex][rightIndex] = count++; + } + for (; leftIndex < n - i-1; leftIndex++) { + result[leftIndex][rightIndex] = count++; + } + for (; rightIndex > i; rightIndex--) { + result[leftIndex][rightIndex] = count++; + } + for (; leftIndex > i; leftIndex--) { + result[leftIndex][rightIndex] = count++; + } + } + if(n%2==1){ + result[n/2][n/2]=count; + } + + return result; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/Combine.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T01_Combine.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/Combine.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T01_Combine.java index ceb9821..058d953 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/Combine.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T01_Combine.java @@ -16,7 +16,7 @@ import java.util.List; * 你可以按 任何顺序 返回答案。 * @Version: 1.0 */ -public class Combine { +public class T01_Combine { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum3.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T02_CombinationSum3.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum3.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T02_CombinationSum3.java index 550cd34..759626d 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum3.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T02_CombinationSum3.java @@ -17,7 +17,7 @@ import java.util.List; * 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。 * @Version: 1.0 */ -public class combinationSum3 { +public class T02_CombinationSum3 { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/IetterCombinations.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T03_LetterCombinations.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/IetterCombinations.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T03_LetterCombinations.java index 47b17d2..e61fa1d 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/IetterCombinations.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T03_LetterCombinations.java @@ -1,6 +1,5 @@ package com.markilue.leecode.backtrace; -import com.markilue.leecode.stackAndDeque.EvalRPN; import org.junit.Test; import java.util.*; @@ -15,7 +14,7 @@ import java.util.*; * 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 * @Version: 1.0 */ -public class IetterCombinations { +public class T03_LetterCombinations { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T04_CombinationSum.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T04_CombinationSum.java index 2524849..4092a32 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T04_CombinationSum.java @@ -17,7 +17,7 @@ import java.util.List; * 对于给定的输入,保证和为 target 的不同组合数少于 150 个。 * @Version: 1.0 */ -public class combinationSum { +public class T04_CombinationSum { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum2.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T05_CombinationSum2.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum2.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T05_CombinationSum2.java index 4a1377e..4c878bf 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/combinationSum2.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T05_CombinationSum2.java @@ -15,7 +15,7 @@ import java.util.*; * 注意:解集不能包含重复的组合。 * @Version: 1.0 */ -public class combinationSum2 { +public class T05_CombinationSum2 { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/partition.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T06_Partition.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/partition.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T06_Partition.java index 8963810..0ca26fd 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/partition.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T06_Partition.java @@ -15,7 +15,7 @@ import java.util.List; * 回文串 是正着读和反着读都一样的字符串。 * @Version: 1.0 */ -public class partition { +public class T06_Partition { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/RestoreIpAddresses.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T07_RestoreIpAddresses.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/RestoreIpAddresses.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T07_RestoreIpAddresses.java index 65977b2..fa5ac57 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/RestoreIpAddresses.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T07_RestoreIpAddresses.java @@ -17,7 +17,7 @@ import java.util.List; * 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。 * @Version: 1.0 */ -public class RestoreIpAddresses { +public class T07_RestoreIpAddresses { @Test public void test1() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/Subsets.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T08_Subsets.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/Subsets.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T08_Subsets.java index 77367b1..c6e92ee 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/Subsets.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T08_Subsets.java @@ -16,7 +16,7 @@ import java.util.List; * 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 * @Version: 1.0 */ -public class Subsets { +public class T08_Subsets { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/SubsetsWithDup.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T09_SubsetsWithDup.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/SubsetsWithDup.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T09_SubsetsWithDup.java index c41236b..7d1768d 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/SubsetsWithDup.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T09_SubsetsWithDup.java @@ -1,9 +1,7 @@ package com.markilue.leecode.backtrace; -import com.markilue.leecode.stackAndDeque.EvalRPN; import org.junit.Test; -import javax.print.DocFlavor; import java.util.*; /** @@ -16,7 +14,7 @@ import java.util.*; * 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 * @Version: 1.0 */ -public class SubsetsWithDup { +public class T09_SubsetsWithDup { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/FindSubsequences.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T10_FindSubsequences.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/FindSubsequences.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T10_FindSubsequences.java index 8c5f89e..19c0602 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/FindSubsequences.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T10_FindSubsequences.java @@ -17,7 +17,7 @@ import java.util.Set; * 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。 * @Version: 1.0 */ -public class FindSubsequences { +public class T10_FindSubsequences { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/Permute.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T11_Permute.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/Permute.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T11_Permute.java index e8dfcca..e3a330b 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/Permute.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T11_Permute.java @@ -15,7 +15,7 @@ import java.util.List; * 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 * @Version: 1.0 */ -public class Permute { +public class T11_Permute { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/PermuteUnique.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T12_PermuteUnique.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/PermuteUnique.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T12_PermuteUnique.java index ae7369a..b4242be 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/PermuteUnique.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T12_PermuteUnique.java @@ -16,7 +16,7 @@ import java.util.List; * 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 * @Version: 1.0 */ -public class PermuteUnique { +public class T12_PermuteUnique { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/T13_0_FindItinerary.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T13_0_FindItinerary.java new file mode 100644 index 0000000..acdf94c --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T13_0_FindItinerary.java @@ -0,0 +1,207 @@ +package com.markilue.leecode.backtrace; + +import org.junit.Test; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace + *@Author: dingjiawen + *@CreateTime: 2023-02-03 10:21 + *@Description: + * TODO 力扣332 重新安排行程: + * 给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 + * 所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。 + * 例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前。 + * 假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。 + *@Version: 1.0 + */ +public class T13_0_FindItinerary { + + @Test + public void test() { + List> tickets = new ArrayList<>(); + tickets.add(Arrays.asList("MUC", "LHR")); + tickets.add(Arrays.asList("JFK", "MUC")); + tickets.add(Arrays.asList("SFO", "SJC")); + tickets.add(Arrays.asList("LHR", "SFO")); + findItinerary(tickets); + System.out.println(result); + } + + @Test + public void test1() { + List> tickets = new ArrayList<>(); + tickets.add(Arrays.asList("JFK", "SFO")); + tickets.add(Arrays.asList("JFK", "ATL")); + tickets.add(Arrays.asList("SFO", "ATL")); + tickets.add(Arrays.asList("ATL", "JFK")); + tickets.add(Arrays.asList("ATL", "SFO")); + findItinerary1(tickets); + System.out.println(result); + } + + + @Test + public void test2() { + List> tickets = new ArrayList<>(); + tickets.add(Arrays.asList("JFK", "KUL")); + tickets.add(Arrays.asList("JFK", "NRT")); + tickets.add(Arrays.asList("NRT", "JFK")); +// tickets.add(Arrays.asList("ATL", "JFK")); +// tickets.add(Arrays.asList("ATL", "SFO")); + findItinerary1(tickets); + System.out.println(result); + } + + + List cur = new ArrayList<>(); + List result = new ArrayList<>(); + List> result1 = new ArrayList<>(); + + public List findItinerary(List> tickets) { + Collections.sort(tickets, new Comparator>() { + @Override + public int compare(List o1, List o2) { + return o1.get(1).compareTo(o2.get(0)); + } + }); + backtracking(tickets, 0, "", new boolean[tickets.size()]); + return result; + } + + public void backtracking(List> tickets, int level, String last, boolean[] used) { + if (level == tickets.size()) { + //所有机票用完了 + result = new ArrayList<>(cur); + return; + } + + for (int i = 0; i < tickets.size(); i++) { + if (level == 0) { + if (!"JFK".equals(tickets.get(i).get(0))) { + //起始地一定要是JFK + continue; + } + used[i] = true; + List lines = tickets.get(i); + cur.add(lines.get(0)); + cur.add(lines.get(1)); + backtracking(tickets, level + 1, lines.get(1), used); + if (result != null) { + return; + } + used[i] = false; + cur.clear(); + } else { + if (used[i] || !last.equals(tickets.get(i).get(0))) { + continue; + } + used[i] = true; + cur.add(tickets.get(i).get(1)); + backtracking(tickets, level + 1, tickets.get(i).get(1), used); + if (result != null) { + return; + } + used[i] = false; + cur.remove(cur.size() - 1); + } + } + } + + + /** + * 代码优化 + * 速度击败26.88%,内存击败43.82% 15ms + * @param tickets + * @return + */ + public List findItinerary1(List> tickets) { + Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1))); + cur.add("JFK"); + backtracking1(tickets, 0, "JFK", new boolean[tickets.size()]); + return result; + } + + public void backtracking1(List> tickets, int level, String last, boolean[] used) { + if (level == tickets.size()) { + //所有机票用完了 + result = new ArrayList<>(cur); + return; + } + + for (int i = 0; i < tickets.size(); i++) { + if (used[i] || !last.equals(tickets.get(i).get(0))) { + continue; + } + used[i] = true; + cur.add(tickets.get(i).get(1)); + backtracking1(tickets, level + 1, tickets.get(i).get(1), used); + if (result != null && result.size() != 0) { + return; + } + used[i] = false; + cur.remove(cur.size() - 1); + } + + } + + + /** + * 官方解法:使用一个优先队列进行排序,好像默认了按字典序就一定能遍历完所有的票 + * 速度击败98.38%,内存击败5.4% 5ms + */ + Map> map = new HashMap>(); + List itinerary = new LinkedList(); + + public List findItinerary2(List> tickets) { + for (List ticket : tickets) { + String src = ticket.get(0), dst = ticket.get(1); + if (!map.containsKey(src)) { + map.put(src, new PriorityQueue()); + } + map.get(src).offer(dst); + } + dfs("JFK"); + Collections.reverse(itinerary); + return itinerary; + } + + public void dfs(String curr) { + while (map.containsKey(curr) && map.get(curr).size() > 0) { + String tmp = map.get(curr).poll(); + dfs(tmp); + } + itinerary.add(curr); + } + + + /** + * 官方最快 4ms,不用反转了,进一步优化 + */ + private List resList = new LinkedList<>(); + + public List findItinerary3(List> tickets) { + for (List ticket : tickets) { + String src = ticket.get(0); + String dst = ticket.get(1); + if (!map.containsKey(src)) { + PriorityQueue pq = new PriorityQueue<>(); + map.put(src, pq); + } + map.get(src).add(dst); + } + dfs1("JFK"); + return resList; + } + + private void dfs1(String src) { + PriorityQueue pq = map.get(src); + while (pq != null && !pq.isEmpty()){ + dfs1(pq.poll()); + } + + ((LinkedList) resList).addFirst(src); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/SolveNQueens.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T13_1_SolveNQueens.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/SolveNQueens.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T13_1_SolveNQueens.java index 1ca63be..3cdf38d 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/SolveNQueens.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T13_1_SolveNQueens.java @@ -17,7 +17,7 @@ import java.util.*; * 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 * @Version: 1.0 */ -public class SolveNQueens { +public class T13_1_SolveNQueens { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/SolveSudoku.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/T14_SolveSudoku.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/backtrace/SolveSudoku.java rename to Leecode/src/main/java/com/markilue/leecode/backtrace/T14_SolveSudoku.java index 0a81234..d63fbf1 100644 --- a/Leecode/src/main/java/com/markilue/leecode/backtrace/SolveSudoku.java +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/T14_SolveSudoku.java @@ -20,7 +20,7 @@ import java.util.List; * 数独部分空格内已填入了数字,空白格用 '.' 表示。 * @Version: 1.0 */ -public class SolveSudoku { +public class T14_SolveSudoku { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T01_Combine.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T01_Combine.java new file mode 100644 index 0000000..8409d49 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T01_Combine.java @@ -0,0 +1,58 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-01-31 10:41 + *@Description: + * TODO 二刷力扣77题 组合: + * 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 + * 你可以按 任何顺序 返回答案。 + *@Version: 1.0 + */ +public class T01_Combine { + + @Test + public void test() { + int n = 4; + int k = 2; + System.out.println(combine(n, k)); + } + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + + public List> combine(int n, int k) { + backtracking(n, k, 1); + return result; + + } + + /** + * 回溯法: + * 剪枝前速度击败35.29%,内存击败55.4% 17ms + * 剪枝后速度击败99.99%,内存击败74.46% 1ms + * @param n + * @param k + * @param now + */ + public void backtracking(int n, int k, int now) { + if (cur.size() == k) { + List list = new ArrayList<>(cur); + result.add(list); + return; + } + for (int i = now; i <= n-(k-cur.size())+1; i++) { + //剪枝,后面怎么都不够了肯定不用了 + cur.add(i); + backtracking(n, k, i + 1); + cur.remove(cur.size() - 1); + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T02_CombinationSum3.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T02_CombinationSum3.java new file mode 100644 index 0000000..9588235 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T02_CombinationSum3.java @@ -0,0 +1,61 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-01-31 11:07 + *@Description: + * TODO 二刷力扣216题 组合总和III: + * 找出所有相加之和为 n 的 k 个数的组合,且满足下列条件: + * 只使用数字1到9 + * 每个数字 最多使用一次 + * 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。 + *@Version: 1.0 + */ +public class T02_CombinationSum3 { + + @Test + public void test() { + int k = 3, n = 7; + System.out.println(combinationSum3(k, n)); + } + + @Test + public void test1() { + int k = 9, n = 45; + System.out.println(combinationSum3(k, n)); + } + + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + + public List> combinationSum3(int k, int n) { + backtracking(k, n, 0, 1); + return result; + } + + public void backtracking(int k, int n, int sum, int now) { + if (k == cur.size()) { + if (sum == n) { + result.add(new ArrayList<>(cur)); + } + return; + } + int flag = n - sum > 9 ? 9 : n - sum; + for (int i = now; i <= flag - (k - cur.size()) + 1; i++) { + cur.add(i); + sum += i; + backtracking(k, n, sum, i + 1); + sum -= i; + cur.remove(cur.size() - 1); + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T03_LetterCombinations.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T03_LetterCombinations.java new file mode 100644 index 0000000..b9b5254 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T03_LetterCombinations.java @@ -0,0 +1,70 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-01-31 11:34 + *@Description: + * TODO 二刷力扣17题 电话号码的字母组合: + * 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 + * 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 + *@Version: 1.0 + */ +public class T03_LetterCombinations { + + @Test + public void test() { +// String s = "01"; +// for (int i = 0; i < s.length(); i++) { +// System.out.println(s.charAt(i) - '0'); +// } + String digits = "23"; + System.out.println(letterCombinations(digits)); + + } + + List result = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + Map map = new HashMap() {{ + put('2', new char[]{'a', 'b', 'c'}); + put('3', new char[]{'d', 'e', 'f'}); + put('4', new char[]{'g', 'h', 'i'}); + put('5', new char[]{'j', 'k', 'l'}); + put('6', new char[]{'m', 'n', 'o'}); + put('7', new char[]{'p', 'q', 'r', 's'}); + put('8', new char[]{'t', 'u', 'v'}); + put('9', new char[]{'w', 'x', 'y', 'z'}); + }}; + + public List letterCombinations(String digits) { + if(digits.length()==0){ + return result; + } + + char[] chars = digits.toCharArray(); + backtracking(chars, chars.length, 0); + + return result; + } + + public void backtracking(char[] chars, int total, int now) { + if (builder.length() == total) { + result.add(builder.toString()); + return; + } + for (char c : map.get(chars[now])) { + builder.append(c); + backtracking(chars, total, now + 1); + builder.deleteCharAt(builder.length() - 1); + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T04_CombinationSum.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T04_CombinationSum.java new file mode 100644 index 0000000..ec61d5c --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T04_CombinationSum.java @@ -0,0 +1,86 @@ +package com.markilue.leecode.backtrace.second; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-01 09:53 + *@Description: + * TODO 二刷力扣39题 组合总和: + * 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 + * candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 + * 对于给定的输入,保证和为 target 的不同组合数少于 150 个。 + *@Version: 1.0 + */ +public class T04_CombinationSum { + + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + + public List> combinationSum(int[] candidates, int target) { + Arrays.sort(candidates); + backtracking(candidates,target,0,0); + return result; + + } + + public void backtracking(int[] candidates, int target, int sum,int now) { + if (sum == target) { + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = now; i < candidates.length; i++) { + if (sum + candidates[i] > target) {//剪枝 + return; + } + cur.add(candidates[i]); + backtracking(candidates, target, sum + candidates[i],i); + cur.remove(cur.size()-1); + } + + } + + + /** + * 官方最快,form记录一个数最多能用多少次,小于0就不能用了 + * @param con + * @param target + * @param form + * @param cur + * @param sum + * @param start + */ + private void deal(List> con,int target,int[] form,List cur,int sum,int start){ + //dfs + if(sum==target) { + con.add(new ArrayList(cur)); + return; + } + for(int i=start;i<=target-sum;++i){ + if(form[i]>0){ + cur.add(i); + --form[i]; + sum+=i; + deal(con,target,form,cur,sum,i); + sum-=i; + ++form[i]; + cur.remove(cur.size()-1); + } + } + } + public List> combinationSum2(int[] candidates, int target) { + int[] form=new int[51]; + for(int i=0;i> con=new ArrayList>(); + deal(con,target,form,new ArrayList(),0,1); + return con; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T05_CombinationSum2.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T05_CombinationSum2.java new file mode 100644 index 0000000..c6bf754 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T05_CombinationSum2.java @@ -0,0 +1,46 @@ +package com.markilue.leecode.backtrace.second; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-01 10:22 + *@Description: + * TODO 二刷力扣40题 组合总和II: + * 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 + * candidates 中的每个数字在每个组合中只能使用 一次 。 + * 注意:解集不能包含重复的组合。 + *@Version: 1.0 + */ +public class T05_CombinationSum2 { + + List cur=new ArrayList<>(); + List> result=new ArrayList<>(); + public List> combinationSum2(int[] candidates, int target) { + Arrays.sort(candidates); + backtracking(candidates,target,0,0); + return result; + } + + public void backtracking(int[] candidates, int target,int sum ,int startIndex) { + if(target==sum){ + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = startIndex; i < candidates.length; i++) { + if(sum+candidates[i]>target){ + return; + } + if(i!=startIndex&&candidates[i]==candidates[i-1])continue; + cur.add(candidates[i]); + backtracking(candidates,target,sum+candidates[i],i+1); + cur.remove(cur.size()-1); + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T06_Partition.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T06_Partition.java new file mode 100644 index 0000000..46834f2 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T06_Partition.java @@ -0,0 +1,155 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-01 11:01 + *@Description: + * TODO 二刷力扣131题 分割回文串: + * 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 + * 回文串 是正着读和反着读都一样的字符串。 + *@Version: 1.0 + */ +public class T06_Partition { + + @Test + public void test() { + String s = "abbab"; + + System.out.println(partition(s)); + } + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + boolean[][] flag; + + public List> partition(String s) { + flag = new boolean[s.length()][s.length()]; + computePalindrome1(s); + backtracking(s.toCharArray(), 0); + return result; + } + + /** + * 使用动态规划前:速度击败73.84%,内存击败55.32% 7ms + * 使用动态规划后:速度击败99.22%,内存击败47.98% 6ms + * @param chars + * @param start + */ + public void backtracking(char[] chars, int start) { + if (start == chars.length) { + //分完了,直接加入 + result.add(new ArrayList<>(cur)); + return; + } + StringBuilder builder = new StringBuilder(); + for (int i = start; i < chars.length; i++) { + builder.append(chars[i]); + if (flag[start][i]) { + cur.add(builder.toString()); + backtracking(chars, i + 1); + cur.remove(cur.size() - 1); + } + } + + } + + public boolean isPalindrome(char[] chars, int start, int end) { + if (start == end) { + return true; + } + while (start < end) { + if (chars[start++] != chars[end--]) { + return false; + } + } + return true; + + } + + /** + * 事实上判断回文不需要一个一个进行判断,可以通过动态规划递归进行判断 + * TODO 动态规划五部曲: + * 1)dp定义:dp[i][j]表示start为i,end为j的字符串是否是回文 + * 2)dp状态转移方程:dp[i][j]可以通过dp[i+1][j-1]来推断 + * dp[i][j]=char[i]==char[j]&&dp[i+1][j-1] + * 3)dp遍历顺序:由于需要知道i+1,j-1所以需要从下到上,左到右 + * 4)dp初始化:dp[i][i]=true;dp[i][i>j]=true + * 5)dp距离推导: s="aab" + * [a a b] + * i=0: t t f + * i=1: t t f + * i=2: t t t + * + * + * @param s + */ + public void computePalindrome(String s) { + char[] chars = s.toCharArray(); + //初始化,下三角全为true + for (int i = 0; i < s.length(); i++) { + for (int j = 0; j <= i; j++) { + flag[i][j] = true; + } + } + + for (int i = s.length() - 2; i >= 0; i--) { + for (int j = s.length() - 1; j > i; j--) { + flag[i][j] = chars[i] == chars[j] && flag[i + 1][j - 1]; + } + } + } + + + /** + * 根据官方的优化 + * @param s + */ + public void computePalindrome1(String s) { + char[] chars = s.toCharArray(); +// //初始化,下三角全为true +// for (int i = 0; i < s.length(); i++) { +// for (int j = 0; j <= i; j++) { +// flag[i][j] = true; +// } +// } + + for (int i = s.length() - 1; i >= 0; i--) { + for (int j = i; j = 0; i--) { + for (int j = i; j result = new ArrayList<>(); + + /** + * 本质上类似于切割回文T06,核心在于最后一层一定要切完 + * @param s + * @return + */ + public List restoreIpAddresses(String s) { + backtracking(s, 0, 0); + return result; + } + + public void backtracking(String s, int level, int start) { + if (level == 4) { + builder.deleteCharAt(builder.length() - 1); + result.add(builder.toString()); + builder.append("."); + return; + } + int flag = start + 3 > s.length() ? s.length() : start + 3; + for (int i = start; i < flag; i++) { + String now; + if (level == 3) { + now = s.substring(i); + if (!isFit(now)) return; + } else { + now = s.substring(start, i + 1); + } + if (isFit(now)) { + builder.append(now).append("."); + backtracking(s, level + 1, i + 1); + int last = builder.length(); + builder.delete(last - now.length() - 1, last); + } + if (level == 3) { + //最后一层就一次 + return; + } + } + + } + + public boolean isFit(String s) { + if (s.length() == 0) { + return false; + } + if (s.length() > 1 && s.charAt(0) == '0') { + return false; + } + long num = Long.parseLong(s); + + return num >= 0 && num <= 255; + + + } + + + /** + * 官方最快0ms + * 使用segment记录pasInt的数,最后在拼接,避免了一系列删除增加“.”的操作 + */ + List res = new ArrayList<>(); + int[] segment = new int[4]; + public List restoreIpAddresses1(String s) { + dfs(s,0,0); + return res; + } + public void dfs(String s,int segnum,int p){ + if(segnum == 4){ + if(p == s.length()){ + StringBuilder sb = new StringBuilder(); + for(int i = 0;i < 4;i++){ + sb.append(segment[i]); + if(i != 3){ + sb.append('.'); + } + } + res.add(sb.toString()); + } + return; + } + if(p == s.length()){ + return; + } + if(s.charAt(p) == '0'){ + segment[segnum] = 0; + dfs(s,segnum+1,p+1); + } + int sum = 0; + for(int i = p;i < s.length();i++){ + sum = sum * 10 + s.charAt(i) - '0'; + if(sum > 0 && sum <= 255){ + segment[segnum] = sum; + dfs(s,segnum+1,i+1); + }else{ + break; + } + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T08_Subsets.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T08_Subsets.java new file mode 100644 index 0000000..daed256 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T08_Subsets.java @@ -0,0 +1,52 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-02 10:07 + *@Description: + * TODO 二刷力扣78题 子集: + * 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 + * 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 + *@Version: 1.0 + */ +public class T08_Subsets { + + @Test + public void test() { + int[] nums={1,2,3}; + System.out.println(subsets(nums)); + } + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + + public List> subsets(int[] nums) { + backtracking(nums, 0); + return result; + + } + + public void backtracking(int[] nums, int level) { + if (level == nums.length) { + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = 0; i <= 1; i++) { + if (i == 0) { + backtracking(nums, level + 1); + } else { + cur.add(nums[level]); + backtracking(nums, level+1); + cur.remove(cur.size() - 1); + } + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T09_SubsetsWithDup.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T09_SubsetsWithDup.java new file mode 100644 index 0000000..3b93f72 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T09_SubsetsWithDup.java @@ -0,0 +1,100 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-02 10:32 + *@Description: + * TODO 力扣90题 子集II: + * 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 + * 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 + *@Version: 1.0 + */ +public class T09_SubsetsWithDup { + @Test + public void test() { + int[] nums = {1, 2, 2}; + System.out.println(subsetsWithDup1(nums)); + } + + /** + * 两种思路:1.树层去重2.map记录元素个数,横向遍历使用 + * @param nums + * @return + */ + Map map = new HashMap();// + List key = new ArrayList<>(); + List cur = new ArrayList<>(); + List> result = new ArrayList<>(); + + public List> subsetsWithDup(int[] nums) { + for (int num : nums) { + Integer count = map.getOrDefault(num, 0); + if (count == 0) { + key.add(num); + } + map.put(num, count + 1); + } + backtracking(0); + return result; + } + + /** + * map记录次数法 + * @param level + */ + public void backtracking(int level) { + if (level == key.size()) { + result.add(new ArrayList<>(cur)); + return; + } + Integer keyNow = key.get(level); + Integer count = map.get(keyNow); + for (int i = 0; i <= count; i++) { + if (i != 0) { + cur.add(keyNow); + } + backtracking(level + 1); + } + for (int i = 0; i < count; i++) { + cur.remove(cur.size() - 1); + } + } + + + public List> subsetsWithDup1(int[] nums) { + Arrays.sort(nums); + backtracking1(nums,0); + return result; + } + + /** + * 树层去重法 + * @param nums + */ + public void backtracking1(int[] nums, int startIndex) { + result.add(new ArrayList<>(cur)); + if (startIndex > nums.length) { + return; + } + + for (int i = startIndex; i < nums.length; i++) { + //树层去重 + if (i != startIndex && nums[i - 1] == nums[i]) { + continue; + } + cur.add(nums[i]); + backtracking1(nums, i + 1); + cur.remove(cur.size() - 1); + } + + + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T10_FindSubsequences.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T10_FindSubsequences.java new file mode 100644 index 0000000..546180e --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T10_FindSubsequences.java @@ -0,0 +1,101 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-02 11:28 + *@Description: + * TODO 二刷力扣491 递增子序列: + * 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 + * 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。 + *@Version: 1.0 + */ +public class T10_FindSubsequences { + + @Test + public void test() { + int[] nums = {4, 6, 7, 7}; + System.out.println(findSubsequences(nums)); + } + + + List cur = new ArrayList<>(); + List> result = new ArrayList<>(); + + /** + * 与上一题子集II类似,都是需要树层去重即可 + * @param nums + * @return + */ + public List> findSubsequences(int[] nums) { + backtracking(nums, 0); + return result; + + } + + public void backtracking(int[] nums, int startIndex) { + if (cur.size() >= 2) { + result.add(new ArrayList<>(cur)); + } + if (startIndex == nums.length) return; + HashSet set = new HashSet<>(); + for (int i = startIndex; i < nums.length; i++) { + //同一树层去除,由于相同数不一定连续,所以使用set来进行树层去重 + //这里还可以使用数组来去重,当数组的指定数位置的值为1时则证明使用过 + if (set.contains(nums[i])) { + continue; + } + set.add(nums[i]); + if (cur.isEmpty() || cur.get(cur.size() - 1) <= nums[i]) { + //是递增的 + cur.add(nums[i]); + backtracking(nums, i + 1); + cur.remove(cur.size() - 1); + } + } + + } + + + /** + * 官方最快 2ms,可能快在用lastNum来记录cur(last) + * @param nums + * @return + */ + public List> findSubsequences1(int[] nums) { + List> list = new ArrayList<>(); + if (nums == null || nums.length == 0) return list; + List result = new ArrayList<>(); + + dfs(0, nums, result, list, -101); + return list; + } + + private void dfs(int idx, int[] nums, List result, List> list, int lastNum) { + if (result.size() > 1) { + list.add(new ArrayList<>(result)); + } + + for (int i = idx; i < nums.length; i++) { + if (isRepeat(nums, idx, i)) continue; + if (nums[i] < lastNum) continue; + result.add(nums[i]); + dfs(i + 1, nums, result, list, nums[i]); + result.remove(result.size() - 1); + } + } + + private boolean isRepeat(int[] nums, int idx, int i) { + for (int j = idx; j < i; j++) { + if (nums[j] == nums[i]) return true; + } + return false; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T11_Permute.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T11_Permute.java new file mode 100644 index 0000000..689ae92 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T11_Permute.java @@ -0,0 +1,83 @@ +package com.markilue.leecode.backtrace.second; + +import com.sun.org.apache.bcel.internal.generic.LUSHR; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-02 11:55 + *@Description: + * TODO 力扣46题 全排列: + * 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 + *@Version: 1.0 + */ +public class T11_Permute { + + List cur = new ArrayList<>(); + List> result = new ArrayList<>(); + boolean[] used; + + /** + * 全排列的题,实际上就是顺序也有关系,所以直接不用传startIndex,每次都从0开始就好 + * 把used放外面速度击败82.16%,内存击败28.47% 1ms + * @param nums + * @return + */ + public List> permute(int[] nums) { + used = new boolean[nums.length]; + backtracking(nums); + return result; + + } + + public void backtracking(int[] nums) { + if(cur.size()==nums.length){ + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = 0; i < nums.length; i++) { + if(!used[i]){ + used[i]=true; + cur.add(nums[i]); + backtracking(nums); + cur.remove(cur.size()-1); + used[i]=false; + } + } + + } + + + /** + * 把used放里面传参 + * 速度击败100%,内存击败83.99% 0ms + * @param nums + * @return + */ + public List> permute1(int[] nums) { + backtracking(nums,new boolean[nums.length]); + return result; + } + public void backtracking(int[] nums, boolean[] used) { + if(cur.size()==nums.length){ + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = 0; i < nums.length; i++) { + if(!used[i]){ + used[i]=true; + cur.add(nums[i]); + backtracking(nums,used); + cur.remove(cur.size()-1); + used[i]=false; + } + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T12_PermuteUnique.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T12_PermuteUnique.java new file mode 100644 index 0000000..666261c --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T12_PermuteUnique.java @@ -0,0 +1,87 @@ +package com.markilue.leecode.backtrace.second; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-02 12:08 + *@Description: + * TODO 力扣47题 全排列II: + * 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 + *@Version: 1.0 + */ +public class T12_PermuteUnique { + + List cur = new ArrayList<>(); + List> result = new ArrayList<>(); + + /** + * 要做的事似乎还是树层去重 + * @param nums + * @return + */ + public List> permuteUnique(int[] nums) { + backtracking(nums, new boolean[nums.length]); + return result; + } + + public void backtracking(int[] nums, boolean[] used) { + if (nums.length == cur.size()) { + result.add(new ArrayList<>(cur)); + return; + } + boolean[] curUsed = new boolean[21];//本树层是否用过 + for (int i = 0; i < nums.length; i++) { + if (curUsed[nums[i] + 10]) continue; + if (!used[i]) { + curUsed[nums[i] + 10] = true; + used[i] = true; + cur.add(nums[i]); + backtracking(nums, used); + cur.remove(cur.size() - 1); + used[i] = false; + } + } + + } + + + /** + * 可以进一步优化为一个used数组 + * @param nums + * @param + */ + public List> permuteUnique1(int[] nums) { + Arrays.sort(nums); + backtracking(nums, new boolean[nums.length]); + return result; + } + public void backtracking1(int[] nums, boolean[] used) { + if (nums.length == cur.size()) { + result.add(new ArrayList<>(cur)); + return; + } + + for (int i = 0; i < nums.length; i++) { + // used[i - 1] == true,说明同一树枝nums[i - 1]使用过 + // used[i - 1] == false,说明同一树层nums[i - 1]使用过 + // 如果同一树层nums[i - 1]使用过则直接跳过 + if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { + continue; + } + if (!used[i]) { + used[i] = true; + cur.add(nums[i]); + backtracking1(nums, used); + cur.remove(cur.size() - 1); + used[i] = false; + } + } + + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T13_1_SolveNQueens.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T13_1_SolveNQueens.java new file mode 100644 index 0000000..164c42b --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T13_1_SolveNQueens.java @@ -0,0 +1,201 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-03 12:43 + *@Description: + * TODO 力扣51题 N皇后: + * 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 + * n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 + * 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + *@Version: 1.0 + */ +public class T13_1_SolveNQueens { + + @Test + public void test() { + System.out.println(solveNQueens1(4)); + } + + List> result = new ArrayList<>(); + List cur = new ArrayList<>(); + + public List> solveNQueens(int n) { + backtracking(n, 0, new boolean[n][n]); + return result; + + } + + + /** + * 速度击败41.44%,内存击败78.99% 4ms + * @param n + * @param level + * @param used + */ + public void backtracking(int n, int level, boolean[][] used) { + if (level == n) { + for (int i = 0; i < n; i++) { + StringBuilder builder = new StringBuilder(); + for (int j = 0; j < n; j++) { + if(used[i][j]){ + builder.append("Q"); + }else { + builder.append("."); + } + } + cur.add(builder.toString()); + } + result.add(new ArrayList<>(cur)); + cur.clear(); + return; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < n; i++) { + if (isFit(used, level, i)) { + used[level][i] = true; + backtracking(n, level + 1, used); + used[level][i] = false; + } + } + + } + + public boolean isFit(boolean[][] used, int row, int col) { + + //看看列 + for (int i = 0; i < row; i++) { + if (used[i][col]) { + return false; + } + } + + //看看行,行不用看,一定不会 + //看看斜边 + for (int i = row - 1, j = col - 1, k = col + 1; i >= 0; i--, j--, k++) { + if (j >= 0 && used[i][j]) { + return false; + } + if (k < used.length && used[i][k]) { + return false; + } + } + + return true; + + } + + + /** + * 代码随想录式,不用一个一个append速度提高 + * 速度击败92.55%,内存击败48.62% 2ms + */ + List> res = new ArrayList<>(); + + public List> solveNQueens1(int n) { + char[][] chessboard = new char[n][n]; + for (char[] c : chessboard) { + Arrays.fill(c, '.'); + } + backTrack(n, 0, chessboard); + return res; + } + + + public void backTrack(int n, int row, char[][] chessboard) { + if (row == n) { + res.add(Array2List(chessboard)); + return; + } + + for (int col = 0;col < n; ++col) { + if (isFitWithChar(chessboard,row, col)) { + chessboard[row][col] = 'Q'; + backTrack(n, row+1, chessboard); + chessboard[row][col] = '.'; + } + } + + } + + + public List Array2List(char[][] chessboard) { + List list = new ArrayList<>(); + + for (char[] c : chessboard) { + list.add(String.copyValueOf(c)); + } + return list; + } + + public boolean isFitWithChar(char[][] used, int row, int col) { + + //看看列 + for (int i = 0; i < row; i++) { + if (used[i][col]=='Q') { + return false; + } + } + + //看看行,行不用看,一定不会 + //看看斜边 + for (int i = row - 1, j = col - 1, k = col + 1; i >= 0; i--, j--, k++) { + if (j >= 0 && used[i][j]=='Q') { + return false; + } + if (k < used.length && used[i][k]=='Q') { + return false; + } + } + + return true; + + } + + + /** + * 官方最快 0ms + * 通过大量的位运算,和Arrays.fill加快速度 + * @param n + * @return + */ + public List> solveNQueens2(int n) { + List> ans=new ArrayList<>(); + int[] queen=new int[n]; + Arrays.fill(queen,-1); + backtrack(ans,n,queen,0,0,0,0); + return ans; + } + public void backtrack(List> ans,int n,int[] queen,int rows,int col,int diagonal1,int diagonal2){ + if(rows==n){ + List list=new ArrayList<>(); + for(int i=0;i>1); + queen[rows]=-1; + } + } + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T14_SolveSudoku.java b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T14_SolveSudoku.java new file mode 100644 index 0000000..1ccb135 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/backtrace/second/T14_SolveSudoku.java @@ -0,0 +1,104 @@ +package com.markilue.leecode.backtrace.second; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.backtrace.second + *@Author: dingjiawen + *@CreateTime: 2023-02-03 20:35 + *@Description: + * TODO 力扣37题 解数独: + * 编写一个程序,通过填充空格来解决数独问题。 + * 数独的解法需 遵循如下规则: + * 数字 1-9 在每一行只能出现一次。 + * 数字 1-9 在每一列只能出现一次。 + * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) + * 数独部分空格内已填入了数字,空白格用 '.' 表示。 + *@Version: 1.0 + */ +public class T14_SolveSudoku { + + @Test + public void test() { + char[][] board = { + {'5', '3', '.', '.', '7', '.', '.', '.', '.'}, + {'6', '.', '.', '1', '9', '5', '.', '.', '.'}, + {'.', '9', '8', '.', '.', '.', '.', '6', '.'}, + {'8', '.', '.', '.', '6', '.', '.', '.', '3'}, + {'4', '.', '.', '8', '.', '3', '.', '.', '1'}, + {'7', '.', '.', '.', '2', '.', '.', '.', '6'}, + {'.', '6', '.', '.', '.', '.', '2', '8', '.'}, + {'.', '.', '.', '4', '1', '9', '.', '.', '5'}, + {'.', '.', '.', '.', '8', '.', '.', '7', '9'}}; + + solveSudoku(board); + for (int i = 0; i < board.length; i++) { + System.out.println(Arrays.toString(board[i])); + } + } + + public void solveSudoku(char[][] board) { + backtracking(board,0,0); + + } + + public boolean backtracking(char[][] board, int row, int col) { + if (row == board.length) { + return true; + } else if (col == board.length) { + //这一行填完了,填下一行 + return backtracking(board, row + 1, 0); + } else if (board[row][col] != '.') { + //这个位置有数字了,填下一个 + return backtracking(board, row, col + 1); + } + + for (int i = 1; i <= 9; i++) { + //填入board[row][col]的数字i + if (isFit(board, row, col, i)) { + board[row][col] = (char) (i + '0'); + if (backtracking(board, row, col + 1)) { + //返回是true,不用继续遍历了,直接返回 + return true; + } + //false的话重新尝试新数字 + board[row][col] = '.'; + } + } + //全都试完了都不对 + return false; + } + + private boolean isFit(char[][] board, int row, int col, int n) { + // + char num =(char) (n + '0'); + //检查行 + for (int i = 0; i < 9; i++) { + if (board[i][col] == num) { + return false; + } + } + + //检查列 + for (int i = 0; i < 9; i++) { + if (board[row][i] == num) { + return false; + } + } + //检查九宫格 + int baseRow = row - (row % 3); + int baseCol = col - (col % 3); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (board[baseRow + i][baseCol + j] == num) { + return false; + } + } + } + + return true; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T18_Rob.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T18_Rob.java new file mode 100644 index 0000000..67185d5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T18_Rob.java @@ -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; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T19_Rob.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T19_Rob.java new file mode 100644 index 0000000..c34dd81 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T19_Rob.java @@ -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]); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T20_Rob.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T20_Rob.java new file mode 100644 index 0000000..1eb2b1f --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T20_Rob.java @@ -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 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 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 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 memo = new HashMap<>(); + return robAction(root, memo); + } + + int robAction(TreeNode root, Map 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; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java new file mode 100644 index 0000000..be80101 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java @@ -0,0 +1,209 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-12 10:02 + * @Description: + * TODO 力扣121题 买卖股票的最佳时期: + * 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 + * 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 + * 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 + * @Version: 1.0 + */ +public class T21_MaxProfit { + + @Test + public void test(){ + int[] price= {7, 1, 5, 3, 6, 4}; + System.out.println(maxProfit1(price)); + } + + @Test + public void test1(){ + int[] price= {7,6,4,3,1}; + System.out.println(maxProfit1(price)); + } + + /** + * 思路:第一反应还是一个背包问题:即在第i天所能获得的最大——但似乎是一个O(n^2)复杂度 + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][j]表示在第i天买入在第j天的最大利润 + * (2)dp状态转移方程:dp[i][j]=max(dp[i][j-1],prices[j]-prices[i],dp[i-1][j]) + * (3)dp初始化:dp[i][j<=i]=0 + * (4)dp遍历顺序: + * (5)dp举例推导: + * 到第198个案例后超出内存限制 + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + if(prices.length==1){ + return 0; + } + + int[][] dp = new int[prices.length][prices.length]; + + for (int i = 1; i < prices.length; i++) { + dp[0][i]=Math.max(dp[0][i-1],prices[i]-prices[0]); + } + + + for (int i = 1; i < prices.length; i++) { + for (int j = i+1; j < prices.length; j++) { + dp[i][j]=Math.max(Math.max(dp[i][j-1],prices[j]-prices[i]),dp[i-1][j]); + } + } + + return dp[prices.length-2][prices.length-1]; + + } + + + /** + * 思路:尝试一遍遍历,思路,遇见负数就将现在的值置为最小值,本质上是一种贪心 + * 速度击败55.26%,内存击败71.51% 2ms + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + if(prices.length==1){ + return 0; + } + + int max=0; + int min=prices[0]; + + for (int i = 1; i < prices.length; i++) { + if(prices[i] maxprofit) { + maxprofit = prices[i] - minprice; + } + } + return maxprofit; + } + + /** + * 评论区从右往左动态规划法:比较巧妙 + * 巧妙在于:当天(i)的买入最大利润=i+1天以后的最大值-当天的价格 + * 而i+1天以后的最大值=i+1天的利润+(i+1)时的股票价格 + * 所以当天利润=i+1天的利润+(i+1)时的股票价格-当天的价格 + * 速度击败30.8%,内存击败98% + * @param prices + * @return + */ + public int maxProfit3(int[] prices) { + int len=prices.length; + int[] dp=new int[len];//每天买入股票对应能获取的最大利润 + int max=0;//最大利润 + dp[len-1]=0;//最后一天买入的话利润为0 + for(int i=len-2;i>=0;i--){ + //因为利润最大的话,当然是选择价格最高的时候卖出 + //所以dp[i+1]=(第i+1天之后最高价格)- prices[i+1]; + //如果dp[i+1]=0,那么第i天之后最高价格只可能是prices[i+1] + //如果dp[i+1]>0就是dp[i+1]+prices[i+1]; + //所以第i天买入能获得的最大利润就是profit + //如果第i天价格比之后都高的话,那么profit<0 + int profit=dp[i+1]+prices[i+1]-prices[i]; + dp[i]=profit>0 ? profit : 0; + max=profit>max ? profit : max; + } + return max; + } + + /** + * 代码随想录动态规划法(版本一): + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][0]表示第i天持有股票的最多现金;dp[i][1]表示第i天不持有股票的最多现金 + * (2)dp状态转移方程: + * 1.dp[i][0] 注意:题目规定只能买入一次,所以有下面的推导 + * dp[i][0]表示当天持有,所以可以从两个情况获得:昨天就持有了;或者今天才买入 + * dp[i][0]=max(dp[i-1][0],-price[i]) + * 2.dp[i][1] + * dp[i][1]表示当天不持有,所以也可以从两种情况获得:昨天不持有;或者今天才卖出 + * dp[i][1]=max(dp[i-1][1],price[i]+dp[i-1][0]) + * (3)dp初始化:dp[0][0]=-price[0];dp[0][1]=0 + * (4)dp遍历顺序:从状态转移方程可以看出从前往后 + * (5)dp距离推导:输入:[7,1,5,3,6,4]为例,dp数组状态如下: + * [0,1] + * i=0 -7 0 + * i=1 -1 0 + * i=2 -1 4 + * i=3 -1 4 + * i=4 -1 5 + * i=5 -1 5 + * 速度击败6.7%,内存击败89.24% + * @param prices + * @return + */ + public int maxProfit4(int[] prices) { + if (prices == null || prices.length == 0) return 0; + int length = prices.length; + // dp[i][0]代表第i天持有股票的最大收益 + // dp[i][1]代表第i天不持有股票的最大收益 + int[][] dp = new int[length][2]; + int result = 0; + dp[0][0] = -prices[0]; + dp[0][1] = 0; + for (int i = 1; i < length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], -prices[i]); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]); + } + return dp[length - 1][1]; + } + + /** + * 代码随想录动态规划法(版本二):滚动数组,记录前一天的即可 + * 速度击败55.26%,内存击败16.18% + * @param prices + * @return + */ + public int maxProfit5(int[] prices) { + int[] dp = new int[2]; + // 记录一次交易,一次交易有买入卖出两种状态 + // 0代表持有,1代表卖出 + dp[0] = -prices[0]; + dp[1] = 0; + // 可以参考斐波那契问题的优化方式 + // 我们从 i=1 开始遍历数组,一共有 prices.length 天, + // 所以是 i<=prices.length + for (int i = 1; i <= prices.length; i++) { + // 前一天持有;或当天买入 + dp[0] = Math.max(dp[0], -prices[i - 1]); + // 如果 dp[0] 被更新,那么 dp[1] 肯定会被更新为正数的 dp[1] + // 而不是 dp[0]+prices[i-1]==0 的0, + // 所以这里使用会改变的dp[0]也是可以的 + // 当然 dp[1] 初始值为 0 ,被更新成 0 也没影响 + // 前一天卖出;或当天卖出, 当天要卖出,得前一天持有才行 + dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]); + } + return dp[1]; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java new file mode 100644 index 0000000..1acd6ab --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java @@ -0,0 +1,96 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-12 12:23 + * @Description: + * TODO 力扣122题 买卖股票的最佳时机 II: + * 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 + * 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 + * 返回 你能获得的 最大 利润 。 + * @Version: 1.0 + */ +public class T22_MaxProfit { + + @Test + public void test(){ + int[] prices = {7, 1, 5, 3, 6, 4}; + System.out.println(maxProfit(prices)); + } + + @Test + public void test1(){ + int[] prices = {1,2,3,4,5}; + System.out.println(maxProfit(prices)); + } + + /** + * 思路:这题实际上在贪心的时候遇到过,这里使用动态规划法: + * TODO 动态规划法: + * (1)dp定义:dp[i][0]表示当天不留股票的最大收益;dp[i][1]表示当天留股票的最大收益 + * (2)dp状态转移方程: + * 1.dp[i][0]=昨天不留+今天也不留 ;昨天留了今天卖了 + * dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i]) + * 2.dp[i][1]=昨天留了今天没买;昨天没留今天买了 + * dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i])) + * (3)dp初始化:dp[0][0]=0;dp[0][1]=-price[0] + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以prices = [7,1,5,3,6,4]为例: + * [0 1] + * i=0 0 -7 + * i=1 0 -1 + * i=2 4 -1 + * i=3 4 1 + * i=4 7 1 + * i=5 7 3 + * 速度击败24.78%,内存击败28.24% 3ms + */ + public int maxProfit(int[] prices) { + int[][] dp = new int[prices.length][2]; + dp[0][0]=0; + dp[0][1]=-prices[0]; + + for (int i = 1; i < prices.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); + dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]); + } + return dp[prices.length-1][0]; + + } + + /** + * 对上述思路进行简化:使用滑动数组记录上次 + * 速度击败82.36%,内存击败66.41% + */ + public int maxProfit1(int[] prices) { + int dp0=0; + int dp1=-prices[0]; + + for (int i = 1; i < prices.length; i++) { + dp0=Math.max(dp0,dp1+prices[i]); + dp1=Math.max(dp1,dp0-prices[i]); + } + return dp0; + + } + + + /** + * 官方贪心法:本质上就是利润累加 + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + int ans = 0; + int n = prices.length; + for (int i = 1; i < n; ++i) { + ans += Math.max(0, prices[i] - prices[i - 1]); + } + return ans; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java new file mode 100644 index 0000000..462fca3 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java @@ -0,0 +1,131 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-13 09:47 + * @Description: + * TODO 力扣123题 买卖股票的最佳时期III: + * 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * @Version: 1.0 + */ +public class T23_MaxProfit { + + @Test + public void test(){ + int[] prices={3, 3, 5, 0, 0, 3, 1, 4}; + System.out.println(maxProfit2(prices)); + } + + + + /** + * 思路:这题的核心差别是:最多可以完成两笔交易,本人没有啥思路,被卡在怎么知道卖出了第一次了,以下是代码随想录解法 + * TODO 动态规划五部曲:核心在于dp的定义和初始化 ->越来越发现,状态转移方程为什么叫状态 转移方程 + * (观察第i天的情况可以发现,第i天可能有五种状态(从来没有操作;已经买了第一次股票;已经卖出了第一次股票;已经买入了第二次股票;已经卖出了第二次股票)) + * (1)dp定义:dp[i][0]第i天从来没有操作获得的最大值;dp[i][1]第i天已经买了第一次股票获得的最大值;dp[i][2]第i天已经卖出了第一次股票获得的最大值,以此类推 + * (2)dp状态转移方程: + * 1.dp[i][0] 从来没有操作 + * dp[i][0]=0 + * 2.dp[i][1] = 昨天就买了第一次了 ;今天才买第一次 + * dp[i][1]=max(dp[i-1][1],-price[i]) + * 3.dp[i][2] = 昨天就卖出了第一次 ;昨天买了第一次的状态,今天才卖 + * dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]) + * 4.dp[i][3] = 昨天就买了第二次;昨天卖出的第一次的状态,今天才买第二次 + * dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]) + * 5.dp[i][4] = 昨天就卖出了第二次;昨天买入的第二次状态,今天卖了 + * dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]) + * (3)dp初始化:dp[0][0]=0 第一天刚买入dp[0][1]=-price[0];第一天刚买刚卖dp[0][2]=0;第一天刚买刚卖又买dp[0][3]=-price[0];第一天刚买刚卖又买又卖dp[0][4]=0 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以输入[1,2,3,4,5]为例 + * [0 1 2 3 4] + * i=0 0 -1 0 -1 0 + * i=1 0 -1 1 -1 1 + * i=2 0 -1 2 -1 2 + * i=3 0 -1 3 -1 3 + * i=4 0 -1 4 -1 4 + * 速度击败43.68%,内存击败91.87% 20ms + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + + int[][] dp = new int[prices.length][5]; + dp[0][0]=0; + dp[0][1]=-prices[0]; + dp[0][2]=0; + dp[0][3]=-prices[0]; + dp[0][4]=0; + for (int i = 1; i < prices.length; i++) { + dp[i][1]=Math.max(dp[i-1][1],-prices[i]); + dp[i][2]=Math.max(dp[i-1][2],dp[i][1]+prices[i]); + dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i]); + dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]); + } + return dp[prices.length-1][4]; + + } + + + /** + * 滚动数组优化 + * 速度击败86.14%,内存击败55.57% 2ms + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + int dp0=0; + int dp1=-prices[0]; + int dp2=0; + int dp3=-prices[0]; + int dp4=0; + for (int i = 1; i < prices.length; i++) { + dp1=Math.max(dp1,-prices[i]); + dp2=Math.max(dp2,dp1+prices[i]); + dp3=Math.max(dp3,dp2-prices[i]); + dp4=Math.max(dp4,dp3+prices[i]); + } + return dp4; + + } + + + /** + * 评论区两次遍历dp法:从高到低一次,从低到高一次。两者相加表示两次交易的最大利润总和,取最大值即可 + * 速度击败62.14%,内存击败98.29% + * 十分的精妙,本质上就是把两次买卖分成了两次T21的maxProfit + * 但无法推广到只允许k次交易 + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + int max = 0; + int minVal = prices[0], maxVal = prices[prices.length-1]; + int[] dp = new int[prices.length];//从低到高,dp[i]表示第i天以及之前的区间所获得的最大利润 + int[] dp2 = new int[prices.length];//从高到低,dp2[i]表示第i天开始直到最后一天期间所获得的最大利润 + dp[0] = -prices[0]; + //这个循环本质上是T21的maxProfit,即寻找从前往后的一次买卖获得最大值 + for(int i=1;i=0;--i){ + dp2[i] = Math.max(dp2[i+1], maxVal-prices[i]); + maxVal = Math.max(maxVal, prices[i]); + } + //把第i天之前的最大值和第i天之后的最大值相加,找到最大值 + for(int i=1;i<=prices.length-1;++i){ + // System.out.println(dp[i-1]+","+dp2[i]); + max = Math.max(dp[i-1]+dp2[i], max); + } + //只买卖一次的,和买卖两次的谁大 + return Math.max(dp[prices.length-1], max); + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java new file mode 100644 index 0000000..ffb9afd --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java @@ -0,0 +1,82 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-13 12:00 + * @Description: + * TODO 力扣188题 买卖股票的最佳时期IV: + * 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * @Version: 1.0 + */ +public class T24_MaxProfit { + + @Test + public void test() { + int k = 2; + int[] prices = {3, 2, 6, 5, 0, 3}; + System.out.println(maxProfit1(k, prices)); + } + + /** + * 思路:这题与T23十分的类似:只是将2次交易推广到k次交易,事实上T23的状态转移方程也具有推广性 + * TODO 动归五部曲:事实上k次交易可以推广到有2*k+1个状态(无操作,第一次买,第一次卖,...,第k次买,第k次买) + * (1)dp定义:dp[i][0]表示无操作;dp[i][2*k+1]表示第k次买,dp[i][2*(k+1)]表示第k次卖 + * (2)dp状态转移方程: + * 1.dp[i][2*k+1]=max(dp[i-1][2*k+1],dp[2*k]-prices[i]) + * 1.dp[i][2*(k+1)]=max(dp[i-1][2*(k+1)],dp[2*k+1]+prices[i]) + * (3)dp初始化: dp[0][i%2==0]=0;dp[0][i%2==1]=-prices[i] + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导: + * 速度击败99.9%,内部击败36.8% 1ms + * @param k + * @param prices + * @return + */ + public int maxProfit(int k, int[] prices) { + + int[][] dp = new int[prices.length][2 * k + 1]; + for (int i = 0; i < 2 * k + 1; i++) { + dp[0][i] = i % 2 == 0 ? 0 : -prices[0]; + } + + for (int i = 1; i < prices.length; i++) { + for (int j = 0; j < k; j++) { + dp[i][2 * j + 1] = Math.max(dp[i - 1][2 * j + 1], dp[i - 1][2 * j] - prices[i]); + dp[i][2 * (j + 1)] = Math.max(dp[i - 1][2 * (j + 1)], dp[i - 1][2 * j + 1] + prices[i]); + } + } + return dp[prices.length - 1][2 * k]; + + } + + + /** + * 滚动数组简化 + * 速度击败58.3%,内存击败78.2% 1ms + * @param k + * @param prices + * @return + */ + public int maxProfit1(int k, int[] prices) { + + int[] dp = new int[2 * k + 1]; + for (int i = 0; i < 2 * k + 1; i++) { + dp[i] = i % 2 == 0 ? 0 : -prices[0]; + } + + for (int i = 1; i < prices.length; i++) { + for (int j = 0; j < k; j++) { + dp[2 * j + 1] = Math.max(dp[2 * j + 1], dp[2 * j] - prices[i]); + dp[2 * (j + 1)] = Math.max(dp[2 * (j + 1)], dp[2 * j + 1] + prices[i]); + } + } + return dp[2 * k]; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T25_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T25_MaxProfit.java new file mode 100644 index 0000000..7cf334e --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T25_MaxProfit.java @@ -0,0 +1,157 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-14 09:45 + *@Description: + * TODO 力扣309题 最佳买卖股票时机含冷冻期: + * 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。 + * 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + * 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + *@Version: 1.0 + */ +public class T25_MaxProfit { + + @Test + public void test(){ + int[] prices = {1, 2, 3, 0, 2}; + System.out.println(maxProfit(prices)); + } + + /** + * 思路:正如题目所说:对应的交易状态有: [买入, 卖出, 冷冻期],由此可以列举状态转移方程 + * TODO 动归五部曲: + * (1)dp定义:dp[i][0]表示第i天处于买入了的状态最多的利润;dp[i][1]表示第i天处于卖出的状态最多的利润 + * dp[i][2]表示第i天处于冷冻期的状态最多的利润;dp[i][3]表示第i天非冷冻期的状态最多的利润 + * (2)dp状态转移方程: + * 1.dp[i][0]=昨天买入了今天没变化;昨天处于冷冻期今天买入了;除了第一次以外,下次再买一定是冷冻期之后的买 + * dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i],-prices[i]) + * 2.dp[i][1]=昨天处于冷冻期今天没变化;昨天买今天卖入了; + * dp[i][1]=max(dp[i-1][2],dp[i-1][0]+prices[i],dp[i-1][1]) + * 2.dp[i][2]=昨天卖了今天处于冷冻期;昨天处于冷冻期今天没变化 + * dp[i][2]=max(dp[i-1][1],dp[i-1][2]) + * (3)dp初始化:dp[0][0]=-prices[0];dp[0][1]=0;dp[0][2]=0 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导: 以prices = [1,2,3,0,2]为例 + * [0 1 2] + * i=0 -1 0 0 + * i=1 -1 1 0 + * i=2 -1 2 1 + * i=3 1 2 2 + * i=4 1 3 2 + * 速度击败77.78%,内存击败46.23% 1ms + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + + int[][] dp = new int[prices.length][3]; + dp[0][0]=-prices[0];dp[0][1]=0;dp[0][2]=0; + + for (int i = 1; i < prices.length; i++) { + dp[i][0]=Math.max(Math.max(dp[i-1][0],dp[i-1][2]-prices[i]),-prices[i]); + dp[i][1]=Math.max(Math.max(dp[i-1][2],dp[i-1][0]+prices[i]),dp[i-1][1]); + dp[i][2]=Math.max(dp[i-1][1],dp[i-1][2]); + } + return dp[prices.length-1][1]; + + } + + + /** + * 代码随想录动态规划法:具体状态转移推导,见笔记 + * 把交易的状态分为了四种: + * 1.状态一:买入股票状态(今天买入股票,或者是之前就买入了股票然后没有操作) + * 2.卖出股票状态,这里就有两种卖出股票状态 + * 状态二:两天前就卖出了股票,度过了冷冻期,一直没操作,今天保持卖出股票状态 + * 状态三:今天卖出了股票 + * 3.状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天 + * 速度击败77.78%,内存击败27.95% + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + int n=prices.length; + if (prices == null || n < 2) { + return 0; + } + int[][] dp = new int[n][4]; + + // bad case + dp[0][0] = -prices[0]; + dp[0][1] = 0; + dp[1][0] = 0; + dp[1][1] = 0; + + for (int i = 1; i < prices.length; i++) { + // dp公式 + dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3], dp[i - 1][1]) - prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]); + dp[i][2] = dp[i - 1][0] + prices[i]; + dp[i][3] = dp[i - 1][2]; + } + + return Math.max(dp[n - 1][3],Math.max(dp[n - 1][1], dp[n - 1][2])); + } + + + /** + * 官方三状态法: + * 速度击败77.78%,内存击败52.16% + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + if (prices.length == 0) { + return 0; + } + + int n = prices.length; + // f[i][0]: 手上持有股票的最大收益 + // f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益;这里的「处于冷冻期」指的是在第 i 天结束之后的状态 + // f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益;由于不能在冷冻期,所以今天不能卖股票 + int[][] f = new int[n][3]; + f[0][0] = -prices[0]; + for (int i = 1; i < n; ++i) { + f[i][0] = Math.max(f[i - 1][0], f[i - 1][2] - prices[i]);//昨天就持有了,或者没有没持有今天买; + f[i][1] = f[i - 1][0] + prices[i]; //必须是今天刚卖; + f[i][2] = Math.max(f[i - 1][1], f[i - 1][2]); //昨天处于冷冻期;昨天过了冷冻期但是没操作; + } + return Math.max(f[n - 1][1], f[n - 1][2]); + } + + + /** + * 官方滚动数组优化: + * 速度击败100%,内存击败79.39% + * @param prices + * @return + */ + public int maxProfit3(int[] prices) { + if (prices.length == 0) { + return 0; + } + + int n = prices.length; + int f0 = -prices[0]; + int f1 = 0; + int f2 = 0; + for (int i = 1; i < n; ++i) { + int newf0 = Math.max(f0, f2 - prices[i]); + int newf1 = f0 + prices[i]; + int newf2 = Math.max(f1, f2); + f0 = newf0; + f1 = newf1; + f2 = newf2; + } + + return Math.max(f1, f2); + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T26_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T26_MaxProfit.java new file mode 100644 index 0000000..7538939 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T26_MaxProfit.java @@ -0,0 +1,102 @@ +package com.markilue.leecode.dynamic; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-14 11:53 + *@Description: + * TODO 力扣714题 买卖股票的最佳时机含手续费: + * 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。 + * 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + * 返回获得利润的最大值。 + * 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。 + *@Version: 1.0 + */ +public class T26_MaxProfit { + + /** + * 思路:本质上只是在卖出时需要付手续费: + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][0]表示手上有股票的最大利润;dp[i][1]表示手上没有股票的最大利润 + * (2)dp状态转移方程: + * 1.dp[i][0]: + * dp[i][0]=max(dp[i-1][0],dp[i-1][1]-price[i]) + * 2.dp[i][1]: + * dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i]-fee) + * (3)dp初始化:dp[0][0]=-price[i];dp[0][1]=0; + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以prices = [1, 3, 2, 8, 4, 9], fee = 2为例 + * [0 1] + * i=0 -1 0 + * i=1 -1 0 + * i=2 -1 0 + * i=3 -1 5 + * i=4 1 5 + * i=5 1 8 + * 速度击败6.13%,内存击败80.17% 22ms + * @param prices + * @param fee + * @return + */ + public int maxProfit(int[] prices, int fee) { + + int[][] dp = new int[prices.length][2]; + dp[0][0]=-prices[0];dp[0][1]=0; + + for (int i = 1; i < prices.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]); + dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]-fee); + } + + return dp[prices.length-1][1]; + + } + + + /** + * 滚动数组优化 + * 速度击败87.26%,内存击败43.9% 3ms + * @param prices + * @param fee + * @return + */ + public int maxProfit1(int[] prices, int fee) { + + int dp0=-prices[0]; + int dp1=0; + + for (int i = 1; i < prices.length; i++) { + dp0=Math.max(dp0,dp1-prices[i]); + dp1=Math.max(dp1,dp0+prices[i]-fee); + } + + return dp1; + + } + + /** + * 官方贪心法: + * 速度击败100%,内存击败80.17% 3ms + * @param prices + * @param fee + * @return + */ + public int maxProfit2(int[] prices, int fee) { + int n = prices.length; + int buy = prices[0] + fee; + int profit = 0; + for (int i = 1; i < n; ++i) { + if (prices[i] + fee < buy) { + buy = prices[i] + fee; + } else if (prices[i] > buy) { + //因为可以买卖无数次,所以这是合理的,即可以当天买当天卖 + profit += prices[i] - buy; + //卖了之后必须重置价格,不然可以使用历史最少作为价格 + buy = prices[i]; + } + } + return profit; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T27_LengthOfLIS.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T27_LengthOfLIS.java new file mode 100644 index 0000000..7c42893 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T27_LengthOfLIS.java @@ -0,0 +1,134 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-15 09:48 + *@Description: + * TODO 力扣300题 最长递增子序列: + * 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 + * 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 + *@Version: 1.0 + */ +public class T27_LengthOfLIS { + + @Test + public void test() { + int[] nums = {0, 5,8, 4,6,7, 12, 2}; + System.out.println(lengthOfLIS1(nums)); + } + + /** + * 代码随想录思路: + * TODO 动态规划五部曲: + * (1)dp定义:dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度 + * (2)dp状态转移方程:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); + * 注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。 + * (3)dp初始化:dp[0]=1 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以[0,1,0,3,2]为例: + * [0 1 2 3 4] + * i=1 1 2 1 1 1 + * i=2 1 2 1 1 1 + * i=3 1 2 1 3 1 + * i=4 1 2 1 3 3 + * 感觉本质上还是暴力解法,与常规两次for一致 时间复杂度O(n^2) + * 速度击败72.45%,内存击败46.73% + * @param nums + * @return + */ + public int lengthOfLIS(int[] nums) { + + int[] dp = new int[nums.length]; + Arrays.fill(dp, 1); + int result = 0; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + if (dp[i] > result) result = dp[i]; // 取长的子序列 + } + return result; + + } + + /** + * 官方贪心+二分查找法:本质上是我们希望每次在上升子序列最后加上的那个数尽可能的小。 + * 时间复杂度O(nlog(n)) 遍历数组复杂度n,二分查找插入dp数组复杂度log(n) + * 速度击败99.63%,内存击败15.78% 2ms + * 以{0, 5, 8, 4, 6, 7, 12, 2}为例: + * i=1:d={0} + * i=2:d={0,5} + * i=3:d={0,5,8} + * i=4:d={0,4,8} + * i=5:d={0,4,6} + * i=6:d={0,4,6,7} + * i=7:d={0,4,6,7,12} + * i=8:d={0,2,6,7,12} + * @param nums + * @return + */ + public int lengthOfLIS1(int[] nums) { + int len = 1, n = nums.length; + if (n == 0) { + return 0; + } + int[] d = new int[n + 1]; + d[len] = nums[0]; + for (int i = 1; i < n; ++i) { + if (nums[i] > d[len]) { + d[++len] = nums[i]; + } else { + int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0 + while (l <= r) {//寻找最后一个比num[i]小的数 + int mid = (l + r) >> 1; + if (d[mid] < nums[i]) { + pos = mid; + l = mid + 1; + } else { + r = mid - 1; + } + } + d[pos + 1] = nums[i]; + } + } + return len; + } + + + /** + * 题解1ms法:本质上还是贪心,甚至没有用二分查找 + * 速度击败99.93%,内存击败5.8% 1ms + * @param nums + * @return + */ + public int lengthOfLIS2(int[] nums) { + + int N = nums.length; + //end[i]表示i+1长度的递增子序列的最小值 + int[] end = new int[N]; + end[0] = nums[0]; + int index = 0; + for(int i=1; i< N;i++){ + if(nums[i] > end[index]){ + end[++index] = nums[i]; + } else { + for (int j = 0; j <= index; j++) { + if (nums[i] <= end[j]) { + end[j] = nums[i]; + break; + } + } + } + } + return index + 1; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T28_FindLengthOfLCIS.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T28_FindLengthOfLCIS.java new file mode 100644 index 0000000..be9a212 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T28_FindLengthOfLCIS.java @@ -0,0 +1,164 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-15 11:58 + *@Description: + * TODO leetcode674题 最长连续递增序列: + * 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 + * 连续递增的子序列 可以由两个下标 l 和 r(l < r)确定 + * 如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] + * 那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。 + *@Version: 1.0 + */ +public class T28_FindLengthOfLCIS { + + @Test + public void test(){ + int[] nums = {1, 3, 5,5,6, 4, 7}; + System.out.println(findLengthOfLCIS2(nums)); + } + + /** + * 思路:核心在于连续递增,所以甚至比T27更简单,更好找规律 + * 这里先直接使用贪心,一次遍历 + * 速度击败99.96%,内存击败51.38% 1ms + * @param nums + * @return + */ + public int findLengthOfLCIS(int[] nums) { + + int min = nums[0]; + int nowLength=1; + int maxLength = 1; + + for (int i = 1; i < nums.length; i++) { + if (nums[i] <= min) { + nowLength=1; + }else { + nowLength+=1; + maxLength=Math.max(maxLength,nowLength); + } + min = nums[i]; + } + return maxLength; + + } + + /** + * 思路:核心在于连续递增,所以甚至比T27更简单,更好找规律 + * 这里尝试动归,一次遍历 + * TODO 动归五部曲: + * (1)dp定义:dp[i]表示使用num[0-i]得到的最长连续子序列 + * (2)dp状态转移方程:dp[i]=num[i]>num[i-1]?max(dp[i-1],now+1),max(dp[i-1],now) + * (3)dp初始化:dp[0]=1 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以{1, 3, 5,5,6, 4, 7}为例: + * dp=[1,2,3,3,3,3,3] + * 速度击败99.96%,内存击败32.74% 1ms + * @param nums + * @return + */ + public int findLengthOfLCIS1(int[] nums) { + + int nowLength=1; + int[] dp = new int[nums.length]; + dp[0]=1; + + for (int i = 1; i < nums.length; i++) { + if (nums[i] > nums[i-1]) { + nowLength+=1; + }else { + nowLength=1; + } + dp[i]=Math.max(dp[i-1],nowLength); + } + return dp[nums.length-1]; + + } + + + /** + * 代码随想录思路:自己的思路还是有点贪心的意思,代码随想录不会 + *速度击败99.96%,内存击败42.32% 1ms + * @param nums + * @return + */ + public int findLengthOfLCIS2(int[] nums) { + + int[] dp = new int[nums.length]; + Arrays.fill(dp,1); + int result=1; + + for (int i = 1; i < nums.length; i++) { + if (nums[i] > nums[i-1]) { + dp[i]=dp[i-1]+1; + } + if (dp[i] > result) result = dp[i]; + + } + return result; + + } + + + + /** + * 思路:动态规划滚动数组优化 + * TODO 动归五部曲: + * (1)dp定义:dp[i]表示以num[i]结尾得到的最长连续子序列 + * (2)dp状态转移方程:if nums[i + 1] > nums[i] 得到dp[i]=dp[i-1]+1 + * (3)dp初始化:dp[i]=1 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以{1, 3, 5,5,6, 4, 7}为例: + * dp=[1,2,3,1,2,1,2] + * 速度击败99.96%,内存击败32.74% 1ms + * @param nums + * @return + */ + public int findLengthOfLCIS3(int[] nums) { + + int nowLength=1; + int dp0=1; + + for (int i = 1; i < nums.length; i++) { + if (nums[i] > nums[i-1]) { + nowLength+=1; + }else { + nowLength=1; + } + dp0=Math.max(dp0,nowLength); + } + return dp0; + + } + + + /** + * 题解中0ms方法:比之前的快在count=1不需要在判断一次if(count>max) + * 速度击败100%,内存击败38.73% + * @param nums + * @return + */ + public int findLengthOfLCIS5(int[] nums) { + int left=0,right,count=1,max=1; + for (right = 1; right < nums.length; right++,left++) { + if(nums[left]max){ + max=count; + } + }else{ + count=1; + } + + } + return max; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T29_FindLength.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T29_FindLength.java new file mode 100644 index 0000000..340d861 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T29_FindLength.java @@ -0,0 +1,275 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-16 09:51 + *@Description: + * TODO 力扣718题 最长重复子数组: + * 给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度。 + * 经过测试子数组必须连续,即[3,2,1]和[3,1,2,1],其最长子数组为2而不是3 + *@Version: 1.0 + */ +public class T29_FindLength { + + @Test + public void test() { + int[] nums1 = {1, 2, 3, 2, 1}; + int[] nums2 = {3, 2, 1, 4, 7}; + System.out.println(findLength1(nums1, nums2)); + } + + @Test + public void test1() { + int[] nums1 = {1,2,3,1,2,1}; + int[] nums2 = {3,2,1,4,7}; + System.out.println(findLength2(nums1, nums2)); + } + + /** + * 思路:最长子数组既然必须要求是连续的,那么跟28其实是比较类似的,就是需要找到对应的情况 + * 比如[3,2,1]和[3,2,3,2,1],代码随想录的思路是,直接全都遍历了就知道了,所以时间复杂度O(N*M) + * TODO 代码随想录动态规划法:题目中说的子数组,其实就是连续子序列。这种问题动规最拿手 + * (1)dp定义:dp[i][j] 表示以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 + * (特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串) + * 其实dp[i][j]的定义也就决定着,我们在遍历dp[i][j]的时候i 和 j都要从1开始。 + * (2)dp状态转移方程:即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1; + * (3)dp初始化:根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的! + * 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1所以dp[i][0] 和dp[0][j]初始化为0。 + * (4)dp遍历顺序:外层for循环遍历A,内层for循环遍历B。 + * (5)dp举例推导:A: [1,2,3,2,1],B: [3,2,1,4,7]为例 + * B: 3 2 1 4 7 + * A: 0 0 0 0 0 0 + * 1 0 0 0 1 0 0 + * 2 0 0 1 0 0 0 + * 3 0 1 0 0 0 0 + * 2 0 1 2 0 0 0 + * 1 0 0 0 3 0 0 + * 速度击败71.18%,内存击败24.68% + * 时间复杂度为O(N*M) + * @param nums1 + * @param nums2 + * @return + */ + public int findLength(int[] nums1, int[] nums2) { + + int[][] dp = new int[nums1.length + 1][nums2.length + 1]; + int result = 0; + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; + if (result < dp[i][j]) result = dp[i][j]; + } + } + + return result; + } + + + /** + * 滚动数组优化思路: + *速度击败44.24%,内存击败88.2% + * @param nums1 + * @param nums2 + * @return + */ + public int findLength1(int[] nums1, int[] nums2) { + + int[] dp = new int[nums2.length + 1]; + int result = 0; + + for (int i = 1; i < nums1.length + 1; i++) { + for (int j = nums2.length + 1 - 1; j >= 1; j--) { + if (nums1[i - 1] == nums2[j - 1]) { + dp[j] = dp[j - 1] + 1; + }else { + dp[j]=0; //如果不等需要重新赋值,避免不覆盖的情况;而二维的时候不用,因为不用重复利用 + } + if (result < dp[j]) result = dp[j]; + } + } + + return result; + } + + + /** + * 官方 滑动窗口法:从A来一遍又从B来一遍,目的是防止[3,2,1]和[3,2,3,2,1],出现两次3,2的情况 + * 时间复杂度O((N+M)*min(N,M)),空间复杂度O(1) + * 速度击败38.34%,内存击败84.78% + * @param nums1 + * @param nums2 + * @return + */ + public int findLength2(int[] nums1, int[] nums2) { + int n = nums1.length, m = nums2.length; + int ret = 0; + for (int i = 0; i < n; i++) { + int len = Math.min(m, n - i); + int maxlen = maxLength(nums1, nums2, i, 0, len); + ret = Math.max(ret, maxlen); + } + for (int i = 0; i < m; i++) { + int len = Math.min(n, m - i); + int maxlen = maxLength(nums1, nums2, 0, i, len); + ret = Math.max(ret, maxlen); + } + return ret; + } + + //寻找A从addA开始和B从addB开始,连续相同的最大值 + public int maxLength(int[] A, int[] B, int addA, int addB, int len) { + int ret = 0, k = 0; + for (int i = 0; i < len; i++) { + if (A[addA + i] == B[addB + i]) { + k++; + } else { + k = 0; + } + ret = Math.max(ret, k); + } + return ret; + } + + + /** + * 官方二分查找+哈希表法,不细研究: + * 核心思想是如果数组 A 和 B 有一个长度为 k 的公共子数组,那么它们一定有长度为 j <= k 的公共子数组。 + * 这样我们可以通过二分查找的方法找到最大的 k。 + */ + int mod = 1000000009; + int base = 113; + + public int findLength3(int[] A, int[] B) { + int left = 1, right = Math.min(A.length, B.length) + 1; + while (left < right) { + int mid = (left + right) >> 1; + if (check(A, B, mid)) { + left = mid + 1; + } else { + right = mid; + } + } + return left - 1; + } + + public boolean check(int[] A, int[] B, int len) { + long hashA = 0; + for (int i = 0; i < len; i++) { + hashA = (hashA * base + A[i]) % mod; + } + Set bucketA = new HashSet(); + bucketA.add(hashA); + long mult = qPow(base, len - 1); + for (int i = len; i < A.length; i++) { + hashA = ((hashA - A[i - len] * mult % mod + mod) % mod * base + A[i]) % mod; + bucketA.add(hashA); + } + long hashB = 0; + for (int i = 0; i < len; i++) { + hashB = (hashB * base + B[i]) % mod; + } + if (bucketA.contains(hashB)) { + return true; + } + for (int i = len; i < B.length; i++) { + hashB = ((hashB - B[i - len] * mult % mod + mod) % mod * base + B[i]) % mod; + if (bucketA.contains(hashB)) { + return true; + } + } + return false; + } + + // 使用快速幂计算 x^n % mod 的值 + public long qPow(long x, long n) { + long ret = 1; + while (n != 0) { + if ((n & 1) != 0) { + ret = ret * x % mod; + } + x = x * x % mod; + n >>= 1; + } + return ret; + } + + + /** + * 官方题解中,最快的方法: + * 似乎是二分查找+哈希表法 + * 5ms + * 速度击败100%,内存击败82.24% + */ + long a = 13131;//形象地说,就是把 S 看成一个类似 base 进制的数(左侧为高位,右侧为低位),它的十进制值就是这个它的哈希值。 + + int N; + int M; + + public int findLength4(int[] nums1, int[] nums2) { + N = nums1.length; + M = nums2.length; + + int l = 0; + int r = Math.min(N, M); + + while(l < r){ + int mid = l + r + 1 >> 1; + //寻找有没有长度为k的公共子数组,找到了就说明可能有更大的 + if(findDup(nums1, nums2, mid)) l = mid; + else r = mid - 1; + } + + return l; + } + + public boolean findDup(int[] nums1, int[] nums2, int len){ + //为了便于理解进行举例:若nums1={1,2,3,1,2,1};nums2={3,2,1,4,7} + long h1 = 0; + long h2 = 0; + long al = 1; + + Set set = new HashSet<>(); + + //计算[0-len]的nums1子数组哈希值 + for(int i = 0; i < len; i++){ + //a进制下的123 + h1 = h1 * a + nums1[i]; + //a进制下的1000 + al = al * a; + } + set.add(h1); + + //计算所有长度为len的子数组哈希值 + for(int i = 1; i <= N - len; i++){ + //以i=1为例 + h1 = h1 * a;//1230 + h1 = h1 - nums1[i - 1] * al;//1230-1000 + h1 = h1 + nums1[i + len - 1];//231 + set.add(h1); + } + //计算[0-len]的nums1子数组哈希值 + for(int i = 0; i < len; i++) h2 = h2 * a + nums2[i]; + + if(set.contains(h2)) return true; + //计算所有len长的的nums2子数组哈希值 + for(int i = 1; i <= M - len; i++){ + h2 = h2 * a; + h2 = h2 - nums2[i - 1] * al; + h2 = h2 + nums2[i + len - 1]; + + if(set.contains(h2)) return true;//只要找到了就返回找到 + } + + return false; + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T30_LongestCommonSubsequence.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T30_LongestCommonSubsequence.java new file mode 100644 index 0000000..6ab0841 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T30_LongestCommonSubsequence.java @@ -0,0 +1,230 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-16 12:51 + *@Description: + * TODO 力扣1143题 最长公共子序列: + * 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 + * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 + * 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 + * 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。 + *@Version: 1.0 + */ +public class T30_LongestCommonSubsequence { + + @Test + public void test(){ + String text2 = "abcacde"; + String text1 = "acfe"; + System.out.println(longestCommonSubsequence1(text1,text2)); + } + + @Test + public void test1(){ + String text2 = "abcba"; + String text1 = "abcbcba"; + System.out.println(longestCommonSubsequence1(text1,text2)); + } + + /** + * 思路:与T29类似,只是将连续条件去掉了,变得可以不连续: + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][j]表示以i-1结尾的text1和以j-1结尾的text2的最长公共子序列 + * (2)dp状态转移方程: for j in (0,len-(i-1) ) if(num[i-1]==num[i-1+j:]) dp[i][j]=max(dp[i-1])+1 + * (3)dp初始化:dp[0]=0 + * (4)dp遍历顺序:两个for可以随意交换顺序 + * (5)dp举例推导:以 text2 = "abcacde", text1 = "acfe" 为例 + * text2: a b c a c d e + * text1: 0 0 0 0 0 0 0 0 + * a: 0 1 1 1 1 1 1 1 + * c: 0 1 1 2 2 2 2 2 + * f: 0 1 1 2 2 2 2 2 + * e: 0 1 1 2 2 2 2 3 + * 速度击败39.62%,内存击败47.86% 11ms + * @param text1 + * @param text2 + * @return + */ + public int longestCommonSubsequence(String text1, String text2) { + + int[][] dp = new int[text1.length() + 1][text2.length() + 1]; + + for (int i = 1; i < text1.length()+1; i++) { + char char1 = text1.charAt(i-1); + for (int j = 1; j < text2.length() + 1; j++) { + if(char1==text2.charAt(j-1)){ + //不要text1[i-1]大,还是不要text2[j-1]大,还是都要大 + dp[i][j]=Math.max(dp[i][j-1],Math.max(dp[i-1][j],dp[i-1][j-1]+1)); + }else { + //不要text1[i-1]大,还是不要text2[j-1]大 + dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]); + } + } + } + return dp[text1.length()][text2.length()]; + + + } + + /** + * 对于自己的浅浅优化一下:不需要判断不要text2[j-1]大, + * 因为他事实上取决于它的[j-2]和[i-1][j-2]谁大,所以他一定比[i-1]+1小 + * 速度击败65.48%,内存击败34.92% 10ms + * @param text1 + * @param text2 + * @return + */ + public int longestCommonSubsequence1(String text1, String text2) { + + int[][] dp = new int[text1.length() + 1][text2.length() + 1]; + + for (int i = 1; i < text1.length()+1; i++) { + char char1 = text1.charAt(i-1); + for (int j = 1; j < text2.length() + 1; j++) { + if(char1==text2.charAt(j-1)){ + //不要text1[i-1]大,还是都要大 + dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1); + }else { + //不要text1[i-1]大,还是不要text2[j-1]大 + dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]); + } + } + } + return dp[text1.length()][text2.length()]; + + + } + + /** + * 对于代码随想录进一步优化一下:不需要判断dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1); + * 因为他事实上dp[i-1][j-1]+1一定比dp[i][j-1]大 + * 速度击败77.38%,内存击败34.92% 9ms + * @param text1 + * @param text2 + * @return + */ + public int longestCommonSubsequence2(String text1, String text2) { + + int[][] dp = new int[text1.length() + 1][text2.length() + 1]; + + for (int i = 1; i < text1.length()+1; i++) { + char char1 = text1.charAt(i-1); + for (int j = 1; j < text2.length() + 1; j++) { + if(char1==text2.charAt(j-1)){ + //不要text1[i-1]大,还是都要大 + dp[i][j]=dp[i-1][j-1]+1; + }else { + //不要text1[i-1]大,还是不要text2[j-1]大 + dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]); + } + } + } + return dp[text1.length()][text2.length()]; + + + } + + /** + * 一维dp数组优化 + * 速度击败77.38%,内存击败97.17% + * @param text1 + * @param text2 + * @return + */ + public int longestCommonSubsequence3(String text1, String text2) { + int n1 = text1.length(); + int n2 = text2.length(); + + // 多从二维dp数组过程分析 + // 关键在于 如果记录 dp[i - 1][j - 1] + // 因为 dp[i - 1][j - 1] dp[j - 1] <=> dp[i][j - 1] + int [] dp = new int[n2 + 1]; + + for(int i = 1; i <= n1; i++){ + + // 这里pre相当于 dp[i - 1][j - 1] + int pre = dp[0]; + for(int j = 1; j <= n2; j++){ + + //用于给pre赋值 + int cur = dp[j]; + if(text1.charAt(i - 1) == text2.charAt(j - 1)){ + //这里pre相当于dp[i - 1][j - 1] 千万不能用dp[j - 1] !! + //或者倒序遍历也行 + dp[j] = pre + 1; + } else{ + // dp[j] 相当于 dp[i - 1][j] + // dp[j - 1] 相当于 dp[i][j - 1] + dp[j] = Math.max(dp[j], dp[j - 1]); + } + + //更新dp[i - 1][j - 1], 为下次使用做准备 + pre = cur; + } + } + + return dp[n2]; + } + + /** + * 官方题解中合理且最快的方法:本质上还是动态规划, + * 但是优化在选取text长度最小的作为内层for,并将string变成数组后进行判断 + * 速度击败99.96%,内存击败98.16% 3ms + * @param text1 + * @param text2 + * @return + */ + public int longestCommonSubsequence4(String text1, String text2) { + //动态规划 + //f(n) f(n-1) + //f(1) 递推 + //确保text1最长 + if(text1.length() tmpMax) tmpMax = tmp;//tmp实际上记录的是dp[i-1][j];tmpMax实际上记录的是dp[i][j-1] + if (dp[j] > rs) { + rs = dp[j]; + } + } + } + return rs; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T32_MaxSubArray.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T32_MaxSubArray.java new file mode 100644 index 0000000..6b25b75 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T32_MaxSubArray.java @@ -0,0 +1,115 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-18 11:15 + *@Description: + * TODO 力扣53题 最大子数组和: + * 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + * 子数组 是数组中的一个连续部分。 + *@Version: 1.0 + */ +public class T32_MaxSubArray { + + @Test + public void test(){ + int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; + System.out.println(maxSubArray(nums)); + } + + @Test + public void test1(){ + int[] nums = {-2}; + System.out.println(maxSubArray(nums)); + } + + @Test + public void test2(){ + int[] nums = {-2, -1}; + int[] nums1 = {-1, -2}; + System.out.println(maxSubArray1(nums1)); + } + + /** + * 本人基础思路:当前的状态可以分为:要当前这个数;或者是不要当前这个数 + * TODO 动态规划法:子数组要求一定连续 + * (1)dp定义:dp[i][0]表示不要当前这个数的最大值;dp[i][1]表示要当前这个数的最大值 + * (2)dp状态转移方程: + * 1.dp[i][0]=上一个数也不要(包含全都不要了);要上一个数 + * dp[i][0]=max(dp[i-1][0],dp[i-1][1]) + * 2.dp[i][1]=上一个数不要(一定是num[i],因为子数组要求连续);上一个数要 + * dp[i][1]=max(num[i],dp[i-1][1]+num[i]) + * (3)dp初始化:dp[0][0]=0;dp[0][1]=nums[0] + * (4)dp遍历顺序:从前往后 + * (5)dp距离推导:以nums = [-2,1,-3,4,-1,2,1,-5,4]为例 + * [0 1] + * i=0: 0 -2 + * i=1: 0 1 + * i=2: 1 -2 + * i=3: 1 4 + * i=4: 4 3 + * i=5: 4 5 + * i=6: 5 6 + * i=7: 6 1 + * i=8: 6 5 + * 速度击败5.19%,内存击败5.11% 16ms + * @param nums + * @return + */ + public int maxSubArray(int[] nums) { + int[][] dp = new int[nums.length][2]; + dp[0][0]=Integer.MIN_VALUE;dp[0][1]=nums[0]; + for (int i = 1; i < nums.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]); + dp[i][1]=Math.max(nums[i],dp[i-1][1]+nums[i]); + } + //要求子数组要求必须至少一个数,所以dp[0][0]=Integer.MIN_VALUE + return Math.max(dp[nums.length-1][0],dp[nums.length-1][1]); + + } + + /** + * 代码随想录法:仅需要算要当前这个数即可 + * 以nums = [-2,1,-3,4,-1,2,1,-5,4]为例 + * dp:-2,1,-2,4,3,5,6,1,5 + * 速度击败43.54%,内存击败7.22% 2ms + * @param nums + * @return + */ + public int maxSubArray1(int[] nums) { + int[] dp = new int[nums.length]; + dp[0]=nums[0]; + int result=dp[0]; + for (int i = 1; i < nums.length; i++) { + dp[i]=Math.max(nums[i],dp[i-1]+nums[i]); + if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值 + } + //要求子数组要求必须至少一个数,所以dp[0][0]=Integer.MIN_VALUE + return result; + + } + + /** + * 一维dp优化 + * 以nums = [-2,1,-3,4,-1,2,1,-5,4]为例 + * dp:-2,1,-2,4,3,5,6,1,5 + * 速度击败100%,内存击败39.12% 1ms + * @param nums + * @return + */ + public int maxSubArray2(int[] nums) { + int dp0 =nums[0]; + int result=dp0; + for (int i = 1; i < nums.length; i++) { + dp0=Math.max(nums[i],dp0+nums[i]); + if (dp0 > result) result = dp0; // result 保存dp[i]的最大值 + } + //要求子数组要求必须至少一个数,所以dp[0][0]=Integer.MIN_VALUE + return result; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java new file mode 100644 index 0000000..43e01cd --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java @@ -0,0 +1,186 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-19 10:05 + *@Description: + * TODO 力扣392题 判断子序列: + * 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 + * 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。 + * (例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 + * 进阶: + * 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码? + *@Version: 1.0 + */ +public class T33_IsSubsequence { + + @Test + public void test() { + String s = "abc"; + String t = "ahbgdc"; + System.out.println(isSubsequence1(s, t)); + } + + @Test + public void test1() { + String s = "axc"; + String t = "ahbgdc"; + System.out.println(isSubsequence1(s, t)); + } + + @Test + public void test2() { + String s = ""; + String t = "ahbgdc"; + System.out.println(isSubsequence1(s, t)); + } + + /** + * 本质上还是一个子序列的判断问题,与之前的子序列类似: + * TODO 动态规划五部曲:本质上还是一个暴力解法 时间复杂度O(MN) + * (1)dp定义:dp[i][j]表示使用s[0-i]是否是t[0-j]的子序列 + * (2)dp状态转移方程: + * 1.以前就是||现在才是 注意以前就是必须是t多,s多不是;最后一个是现在才是 + * dp[i][j]=dp[i-1][j]||(t[i]==s[j] and dp[i-1][j-1]) + * 为了初始化方便,这里改为使用s[0-(i-1)]是否是t[0-(j-1)] + * (3)dp初始化:dp[i][0]=true 没有的时候一定是子序列 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以s = "abc", t = "ahbgdc"为例 + * [0 a b c] + * i=0: t f f f + * i=1: t t f f + * i=2: t t f f + * i=3: t t t f + * i=4: t t t f + * i=5: t t t f + * i=6: t t t t + * 速度击败29.74%,内存击败8.12% 3ms + * @param s + * @param t + * @return + */ + public boolean isSubsequence(String s, String t) { + + char[] sArray = s.toCharArray(); + char[] tArray = t.toCharArray(); + + boolean[][] dp = new boolean[tArray.length + 1][sArray.length + 1]; + for (int i = 0; i < dp.length; i++) { + dp[i][0] = true; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + dp[i][j] = dp[i - 1][j] || tArray[i - 1] == sArray[j - 1] && dp[i - 1][j - 1]; + } + } + return dp[tArray.length][sArray.length]; + + } + + + /** + * 一维dp优化 + * 速度击败27.55%,内存击败88.7% 4ms + * @param s + * @param t + * @return + */ + public boolean isSubsequence1(String s, String t) { + + boolean[] dp = new boolean[s.length() + 1]; + dp[0] = true; + + for (int i = 1; i < t.length()+1; i++) { + for (int j = dp.length-1; j >=1 ; j--) { + //倒序,因为dp[j]依赖于dp[j-1],因此在dp[j]改变之前不能先改变dp[j-1] + dp[j] = dp[j] || (t.charAt(i-1) == s.charAt(j-1) && dp[j - 1]);//dp[j - 1]相当于上面的dp[i-1][j-1] + if(dp[s.length()])return true;//如果最后是true了那么s不用遍历完 + } + } + return dp[s.length()];//这里不能直接返回false因为可能没进for + + } + + /** + * 代码随想录动态规划:这道题目算是编辑距离的入门题目(毕竟这里只是涉及到减法),也是动态规划解决的经典题型。 + * 速度击败27.55%,内存击败17.96% 4ms + * @param s + * @param t + * @return + */ + public boolean isSubsequence2(String s, String t) { + + int length1 = s.length(); int length2 = t.length(); + int[][] dp = new int[length1+1][length2+1]; + for(int i = 1; i <= length1; i++){ + for(int j = 1; j <= length2; j++){ + if(s.charAt(i-1) == t.charAt(j-1)){ + dp[i][j] = dp[i-1][j-1] + 1; + }else{ + dp[i][j] = dp[i][j-1]; + } + } + } + if(dp[length1][length2] == length1){ + return true; + }else{ + return false; + } + + } + + + /** + * 官方题解: + * 双指针法:由于是子序列即可,所以匹配不上的时候就移动t,匹配上了就都移动 + * 时间复杂度O(M+N) + * 速度击败87.41%,内存击败46.86% 1ms + * @param s + * @param t + * @return + */ + public boolean isSubsequence3(String s, String t) { + int n = s.length(), m = t.length(); + int i = 0, j = 0; + while (i < n && j < m) { + if (s.charAt(i) == t.charAt(j)) { + i++; + } + j++; + } + return i == n; + } + + + /** + * 题解中最快的: + * 时间复杂度O(N) + * 速度击败100% 内存击败57.34% 0ms + * @param s + * @param t + * @return + */ + public boolean isSubsequence4(String s, String t) { + int flag = 0; + for(int i = 0; i < s.length(); i++){ + int temp = t.indexOf(s.charAt(i)); + //没找到直接返回false + if(-1 == temp){ + return false; + } + flag = temp; + //继续寻找他的子数组 + t = t.substring(temp + 1); + + } + return true; + } + + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java new file mode 100644 index 0000000..75d6425 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java @@ -0,0 +1,179 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-19 11:11 + *@Description: + * TODO 力扣115题 不同的子序列: + * 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 + * 字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。 + * (例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是) + * 题目数据保证答案符合 32 位带符号整数范围。 + *@Version: 1.0 + */ +public class T34_NumDistinct { + + @Test + public void test() { + String s = "rabbbit"; + String t = "rabbit"; + System.out.println(numDistinct3(s, t)); + } + + @Test + public void test1() { + String s = "babgbag"; + String t = "bag"; + System.out.println(numDistinct2(s, t)); + } + + /** + * 思路:本质上就是要求s的子序列能凑出t的个数 + * TODO 动态规划法: + * (1)dp定义:dp[i][j]表示是s[0-i]中能抽出t[0-j]的子序列个数 + * (2)dp状态转移方程: + * 1.dp[i][j] 有两种情况:s[i]!=t[j];s[i]==t[j] + * if s[i]!=t[j]: + * dp[i][j]=dp[i-1][j] + * else + * dp[i-1][j]==0: //证明之前没匹配上过,这是第一次匹配,那么与之前刚好匹配上的保持一致 + * dp[i][j]=dp[i-1][j-1] + * else: //证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数 + * dp[i][j]=dp[i-1][j-1]+dp[i-1][j] + * (3)dp初始化:为了初始化方便,改为s[i-1]!=t[j-1],,则dp[0][j]=0;dp[i][0]=1; + * (4)dp遍历顺序:s的for在外边 + * (5)dp举例推导: 以s = "rabbbit", t = "rabbit"为例: + * [0 r a b b i t] + * s=0 1 0 0 0 0 0 0 + * s=r 1 1 0 0 0 0 0 + * s=a 1 1 1 0 0 0 0 + * s=b 1 1 1 1 0 0 0 + * s=b 1 1 1 2 1 0 0 c21 c22 + * s=b 1 1 1 3 3 0 0 c31 c32 + * s=i 1 1 1 3 3 3 0 + * s=t 1 1 1 3 3 3 3 + * 速度击败40.32%,内存击败32.75% 15ms + * @param s + * @param t + * @return + */ + public int numDistinct(String s, String t) { + + int[][] dp = new int[s.length() + 1][t.length() + 1]; + for (int i = 0; i < dp.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if (s.charAt(i - 1) != t.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j]; + } else { + if (dp[i - 1][j] == 0) { + //证明之前没匹配上过,这是第一次匹配,那么与之前刚好匹配上的保持一致 + dp[i][j] = dp[i - 1][j - 1]; + } else { + //证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数 + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; + } + } + + } + } + return dp[s.length()][t.length()]; + + + } + + + /** + * 浅优化,本质上是说 当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。 + * 1.一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。 + * 2.一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。 + * 即如果s为bagg且t为bag,那么遍历到bagg时, + * 1.可以用ba加上最后的g来凑bag + * 2.可以就只用bag不用最后一个g来凑bag + * @param s + * @param t + * @return + */ + public int numDistinct1(String s, String t) { + + int[][] dp = new int[s.length() + 1][t.length() + 1]; + for (int i = 0; i < dp.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if (s.charAt(i - 1) != t.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j]; + } else { + //证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数 + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; + } + + } + } + return dp[s.length()][t.length()]; + + + } + + /** + * 一维dp优化 + * 速度击败73.2%,内存击败93.11% 11ms + * @param s + * @param t + * @return + */ + public int numDistinct2(String s, String t) { + + int[] dp = new int[t.length() + 1]; + dp[0] = 1; + for (int i = 1; i < s.length()+1; i++) { + for (int j = dp.length-1; j >=1; j--) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + //倒序遍历,因为需要用到之前的dp[j-1] + dp[j] = dp[j - 1] + dp[j]; + } + + } + } + return dp[t.length()]; + + + } + + + /** + * 官方题解中合理且最快的方法:本质上还是动态规划法 + * 速度击败99.84%,内存击败23.18% + */ + Integer[][] memo; + public int numDistinct3(String s, String t) { + int sLen = s.length(), tLen = t.length(); + memo = new Integer[tLen][sLen]; + + return dfs(s, t, s.length() - 1, t.length() - 1); + } + + private int dfs(String s, String t, int sIndex, int tIndex) {//递归作用,缩小规划,好分析边界,解决问题 + if (tIndex < 0) return 1; //t的索引小于零了,那么就找到了 + if (sIndex < 0) return 0; + + if (sIndex < tIndex) return 0; // <3> 优化3, 12ms --> 2ms 这里表示s的长度都小于t了,那么就是没找到 + + if (memo[tIndex][sIndex] != null) return memo[tIndex][sIndex];//以前算过了,直接返回 + //没算过就去算 + int ans = 0; + //这个递推公式可以参照那个二维数组法;本质上也是反向遍历 + if (s.charAt(sIndex) == t.charAt(tIndex)) ans += dfs(s, t, sIndex - 1, tIndex - 1); + ans += dfs(s, t, sIndex - 1, tIndex);//匹配时,可选,可不选。, //不匹配时,只能不选, + + memo[tIndex][sIndex] = ans; //memo记录动态规划的dp数组 + return ans; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T35_MinDistance.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T35_MinDistance.java new file mode 100644 index 0000000..e53c75c --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T35_MinDistance.java @@ -0,0 +1,142 @@ +package com.markilue.leecode.dynamic; + + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-20 10:17 + *@Description: + * TODO 力扣583题 两个字符串的删除操作: + * 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。 + * 每步 可以删除任意一个字符串中的一个字符。 + *@Version: 1.0 + */ +public class T35_MinDistance { + + @Test + public void test(){ + String word1 = "sea"; + String word2 = "eat"; + System.out.println(minDistance1(word1,word2)); + } + + @Test + public void test1(){ + String word1 = "leetcode"; + String word2 = "etco"; + System.out.println(minDistance1(word1,word2)); + } + + /** + * 思路:本质上还是求最少删除多少个字符会相等 + * TODO 动态规划法: + * (1)dp定义:dp[i][j]表示使用word1[0-i]和word2[0-j]需要删除多少个变成一样 + * (2)dp状态转移方程: + * 1.如果两个数相等:和以前没加这两个数时一样 + * if word1[i]==word2[j] dp[i][j]=dp[i-1][j-1] + * 2.如果两个数不相等:可以去掉当前这个word1[i]或者去掉word1[j]看谁减去的字符多 + * else dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1) + * (3)dp初始化:dp[i][0]=i;dp[0][i]=i + * (4)dp遍历顺序: + * (5)dp举例推导:以word1 = "sea", word2 = "eat"为例 + * [0 e a t] + * i=0: 0 1 2 3 + * i=s: 1 2 3 4 + * i=e: 2 1 2 3 + * i=a: 3 2 1 2 + * 速度击败62.85%,内存击败66.73% + * @param word1 + * @param word2 + * @return + */ + public int minDistance(String word1, String word2) { + + int[][] dp = new int[word1.length() + 1][word2.length() + 1]; + //初始化 + for (int i = 0; i < dp.length; i++) { + dp[i][0]=i; + } + for (int i = 1; i < dp[0].length; i++) { + dp[0][i]=i; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; 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,dp[i][j-1]+1); + } + } + + return dp[word1.length()][word2.length()]; + + } + + + /** + * 代码随想录另一种思路:这题和求最长公共子序列类似: + * 只要求出两个字符串的最长公共子序列长度即可,那么除了最长公共子序列之外的字符都是必须删除的, + * 最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。 + * 速度击败62.85%,内存击败14.5% + * @param word1 + * @param word2 + * @return + */ + public int minDistance1(String word1, String word2) { + + int[][] dp = new int[word1.length() + 1][word2.length() + 1]; + //初始化 + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; 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 word1.length()+word2.length()-dp[word1.length()][word2.length()]*2; + + } + + /** + * 官方题解中最快:本质上是最长子序列的解法 + * 速度击败100%,内存击败91.88% 3ms + * @param word1 + * @param word2 + * @return + */ + public int minDistance2(String word1, String word2) { + int m = word1.length(); + int n = word2.length(); + if (m < n) { + String s = word1; + word1 = word2; + word2 = s; + + int t = m; + m = n; + n = t; + } + char[] w1 = word1.toCharArray(); + char[] w2 = word2.toCharArray(); + int[] prev = new int[n + 1]; + int[] curr = new int[n + 1]; + for (char c : w1) { + for (int j = 0; j < n; j++) { + if (c == w2[j]) { + curr[j + 1] = prev[j] + 1; + } else { + curr[j + 1] = Math.max(prev[j + 1], curr[j]); + } + } + int[] t = prev; + prev = curr; + curr = t; + } + return m + n - 2 * prev[n]; + } + + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T36_MinDistance.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T36_MinDistance.java new file mode 100644 index 0000000..f635db2 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T36_MinDistance.java @@ -0,0 +1,132 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-20 11:36 + *@Description: + * TODO 力扣72题 编辑距离: + * 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。 + * 你可以对一个单词进行如下三种操作: + * 插入一个字符 + * 删除一个字符 + * 替换一个字符 + *@Version: 1.0 + */ +public class T36_MinDistance { + + @Test + public void test(){ + String word1 = "horse"; + String word2 = "ros"; + System.out.println(minDistance(word1,word2)); + } + + @Test + public void test1(){ + String word1 = "intention"; + String word2 = "execution"; + System.out.println(minDistance(word1,word2)); + } + + /** + * 由于可以插入,可以替换,可以删除,本人没有明确的想法,这里先用距离推导状态转移方程 + * TODO 动态规划法: + * 1.dp定义:dp[i][j]表示word1[0-i]变成word2[0-j]所需最少步骤 + * 2.dp状态转移方程: + * 1.当word1[i]=word2[j]时,那么仅需要处理i,j之前的数 + * dp[i][j]=dp[i-1][j-1] + * 2.当word1[i]!=word2[j]时 + * 1)如果word1长度不够,他可能需要从word[j]添加一个数 dp[i][j-1]+1 + * 2)如果word1长度刚好,他可能需要在word[i-1][j-1]的基础上替换当前这个数 dp[i-1][j-1]+1 + * 3)如果word1长度超过,他直接把他删了,在dp[i-1][j]的基础上删除 dp[i-1][j]+1 + * dp[i][j]=min(dp[i][j-1]+1,dp[i-1][j-1]+1,dp[i-1][j]+1) + * 3.dp初始化:dp[0][i]=i;dp[i][0]=i + * 4.dp遍历顺序: + * 5.dp举例推导:以word1 = "horse", word2 = "ros"为例 + * [0 r o s] + * i=0: 0 1 2 3 + * i=h: 1 1 2 3 + * i=o: 2 2 1 2 + * i=r: 3 2 2 2 + * i=s: 4 3 3 2 + * i=e: 5 4 4 3 + * 速度击败95.89%,内存击败5.17% + * @param word1 + * @param word2 + * @return + */ + public int minDistance(String word1, String word2) { + char[] char1 = word1.toCharArray(); + char[] char2 = word2.toCharArray(); + int length1 = char1.length; + int length2 = char2.length; + + int[][] dp = new int[length1 + 1][length2 + 1]; + //初始化 + for (int i = 0; i < dp.length; i++) { + dp[i][0]=i; + } + for (int i = 0; i < dp[0].length; i++) { + dp[0][i]=i; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if(char1[i-1]==char2[j-1]) dp[i][j]=dp[i-1][j-1]; + else dp[i][j]=Math.min(Math.min(dp[i][j-1]+1,dp[i-1][j-1]+1),dp[i-1][j]+1); + } + } + + return dp[length1][length2]; + + } + + + /** + * 官方题解中最快的方法,本质上还是dp,使用递归实现 + * 速度击败100%,内存击败5.17% 2ms + */ + int[][] meno ; + public int minDistance1(String word1, String word2){ + + meno = new int[word1.length()][word2.length()]; + + for (int[] ints : meno) { + Arrays.fill(ints,-1); + } + + return dp(word1,word1.length()-1,word2,word2.length()-1); + } + + private int dp(String word1, int i, String word2, int j) { + + if(i==-1){ + //i不够了,全添加到j + return j+1; + } + if(j==-1){ + //j不够了,把i全删了 + return i+1; + } + + if(meno[i][j]!=-1){ + return meno[i][j];//以前算过了,直接返回;有点像记忆化搜索 + } + + //状态转移方程 + if(word1.charAt(i)==word2.charAt(j)){ + meno[i][j] = dp(word1,i-1,word2,j-1); + + }else{ + meno[i][j] = Math.min(Math.min(dp(word1,i,word2,j-1),dp(word1,i-1,word2,j)),dp(word1,i-1,word2,j-1))+1; + } + + return meno[i][j]; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java new file mode 100644 index 0000000..ea57737 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java @@ -0,0 +1,240 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-21 09:33 + *@Description: + * TODO 力扣647题 回文子串: + * 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 + * 回文字符串 是正着读和倒过来读一样的字符串。 + * 子字符串 是字符串中的由连续字符组成的一个序列。 + * 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 + *@Version: 1.0 + */ +public class T37_CountSubstrings { + + @Test + public void test() { + String s = "abc"; + System.out.println(countSubstrings(s)); + } + + @Test + public void test1() { + String s = "aaa"; + System.out.println(countSubstrings1(s)); + } + + + @Test + public void test2() { + String s = "fdsklf"; + System.out.println(countSubstrings2(s)); + } + + + /** + * 思路:求回文子字符串个数,可以通过逐个增加的方式求得 + * TODO 动态规划法: + * 1.dp定义:dp[i][j]表示s[i]结尾,s[j]开头的字符串有几个回文子串;很明显,j<=i + * 2.dp状态转移方程: + * dp[i][j]有两种情况,需要新加了s[i],和不需要新加的s[i];以aa加上最后一个a为例 + * 1.不需要新加的s[i] 如aa + * dp[i][j]=dp[i-1][j] + * 2.需要新加的s[j] + * if s[i-1]=s[j-1], 如aaa和后两个aa + * dp[i][j]=dp[i][j+1]+1 + * else, 如只有后两个aa + * dp[i][j]=dp[i][j+1] + * dp[i][j]=1.+2.-dp[i-1][j+1] //1和2两者有一个重复的地方 + * 3.dp初始化:dp[0][0]=1;dp[i][i]=1 + * 4.dp遍历顺序:第一层for正序,第二层for倒序 + * 5.dp距离推导:以aaa为例 + * [0 a a a] + * i=0: 1 0 0 0 + * i=a: 1 1 0 0 + * i=a: 1 3 1 0 + * i=a: 1 6 3 1 + * 暂时存在错误,无法知道当前子字符串是不是回文 + * @param s + * @return + */ + public int countSubstrings(String s) { + + int[][] dp = new int[s.length() + 1][s.length() + 1]; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 1; j--) { + if (s.charAt(i - 1) == s.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j] + dp[i][j + 1] + 1 - dp[i - 1][j + 1]; + } else { + dp[i][j] = dp[i - 1][j] + dp[i][j + 1] - dp[i - 1][j + 1]; + } + } + } + + return dp[s.length()][1]; + + } + + + /** + * 另一种思路:dp定义为当前子字符串是不是回文 + * dp[i][j]=s[i]==s[j]&&dp[i-1][j+1] + * 速度击败19.97%,内存击败42.14% 12ms + * @param s + * @return + */ + public int countSubstrings1(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + + int result = 0; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = true; + result += 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 0; j--) { + if (i - 1 > j + 1) { + dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i - 1][j + 1]; + } else { + dp[i][j] = s.charAt(i) == s.charAt(j); + } + + if (dp[i][j]) result += 1; + } + } + + return result; + + } + + + /** + * 另一种思路:dp定义为当前子字符串是不是回文 + * dp[i][j]=s[i]==s[j]&&dp[i-1][j+1] + * 速度击败12.56%,内存击败23.72% + * @param s + * @return + */ + public int countSubstrings2(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + + int result = 0; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = true; + if (i + 1 < dp.length) { + dp[i][i + 1] = true; + } + result += 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 0; j--) { + dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i - 1][j + 1]; + if (dp[i][j]) result += 1; + } + } + + return result; + + } + + + + /** + * 代码随想录思路:与本人方法类似,只不过将相等的情况分为了三种 + * TODO + * 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串 + * 情况二:下标i 与 j相差为1,例如aa,也是回文子串 + * 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了, + * 我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间 + * ,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。 + * 速度击败62.5%,内存击败22.99% 4ms + * @param s + * @return + */ + public int countSubstrings3(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + int result = 0; + char[] chars = s.toCharArray(); + +// for (int i = 1; i < dp.length; i++) { +// for (int j = i ; j >= 0; j--) { +// if(chars[i]==chars[j]){ +// if(i-j<=1){//情况一 情况二 +// result++; +// dp[i][j]=true; +// }else if(dp[i-1][j+1]){//情况三 +// result++; +// dp[i][j]=true; +// } +// } +// +// } +// } + + + for (int i = 0; i < dp.length; i++) { + for (int j = i ; j >= 0; j--) { + if(chars[i]==chars[j]&&((i-j<=1)||dp[i-1][j+1])){ + //三种情况简化后的版本 + result++; + dp[i][j]=true; + } + + } + } + return result; + + } + + + /** + * 双指针法:判断一个字符串是不是回文,可以从中心向两边扩散: + * 中心的情况可以分为两种:一个中心,或者两个中心 + * 如aaa,开始遍历 + * i=0:判断第一个a是不是+第一个aa是不是 + * i=1:判断第二个a是不是,判断扩散后aaa是不是+判断第二个aa是不是 + * i=2:判断第三个a是不是 + * 时间复杂度O(N^2),但空间复杂度降至O(1) + * 速度击败99.97%,内存击败58.49% 1ms + * @param s + * @return + */ + public int countSubstrings4(String s) { + + int result = 0; + char[] chars = s.toCharArray(); + for (int i = 0; i < s.length(); i++) { + result += extend(chars, i, i, s.length()); // 以i为中心 + result += extend(chars, i, i + 1, s.length()); // 以i和i+1为中心 + } + return result; + + } + + public int extend(char[] s, int i, int j, int n) { + int res = 0; + while (i >= 0 && j < n && s[i] == s[j]) { + i--; + j++; + res++; + } + return res; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java new file mode 100644 index 0000000..f55f9e5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java @@ -0,0 +1,135 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; +import org.omg.CORBA.PUBLIC_MEMBER; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-21 11:57 + *@Description: + * TODO 力扣516题 最长回文子序列: + * 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 + * 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 + *@Version: 1.0 + */ +public class T38_LongestPalindromeSubseq { + + @Test + public void test(){ + String s ="bbbab"; + System.out.println(longestPalindromeSubseq1(s)); + } + + @Test + public void test1(){ + String s ="cbbd"; + System.out.println(longestPalindromeSubseq(s)); + } + + /** + * 思路:最长回文子序列,则可以删除 + * TODO 动态规划法: + * (1)dp定义:dp[i][j]表示以s[j]开始,以s[i]结束的最长回文子序列长度 + * (2)dp状态转移方程:可以根据当前s[i]等不等于s[j]来划分状态 + * 1.若s[i]!=s[j] 最长是和把他去掉一样 + * dp[i][j]=Math.max(dp[i-1][j],dp[i][j+1]);//掐头大还是去尾大 + * 2.若s[i]==s[j] 掐头去尾后+这两个字符 + * dp[i][j]=dp[i-1][j+1]+2 + * (3)dp初始化: + * (4)dp遍历顺序: + * (5)dp举例推导:以bbbab举例: + * [b b b a b] + * i=b: 1 0 0 0 0 + * i=b: 2 1 0 0 0 + * i=b: 3 2 1 0 0 + * i=a: 3 2 1 1 0 + * i=b: 4 3 3 1 1 + * 速度击败99.61%,内存击败15.44% + * @param s + * @return + */ + public int longestPalindromeSubseq(String s) { + + char[] chars = s.toCharArray(); + int[][] dp = new int[s.length()][s.length()]; + for (int i = 0; i < dp.length; i++) { + dp[i][i]=1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i-1; j >=0 ; j--) { + if(chars[i]==chars[j]){ + dp[i][j]=dp[i-1][j+1]+2; + }else { + dp[i][j]=Math.max(dp[i-1][j],dp[i][j+1]);//掐头大还是去尾大 + } + } + } + + return dp[s.length()-1][0]; + + } + + + /** + * 题解中最快的方法:本质上是一维dp优化 + * 速度击败100%,内存击败98.53% + * @param s + * @return + */ + public int longestPalindromeSubseq1(String s) { + char[] c = s.toCharArray(); + int[] dp = new int[c.length]; + int max = 0; + for (int i = 0; i < dp.length; i++ ) { + dp[i] = 1; + int curMax = 0; + for (int j = i - 1; j >= 0; j--) { + int prev = dp[j];//prev=dp[i-1][j];curmax理论上记录的是dp[i-1][j+1] + if (c[i] == c[j]) + dp[j] = curMax + 2;//本质上说的是如果不等则dp[i][j]=dp[i][j-1] + curMax = Math.max(prev, curMax);//之所以可以这样是因为dp[i-1][j]一定>=dp[i-1][j+1],但不能改成下式 +// curMax = prev;//之所以可以这样是因为dp[i-1][j]一定>=dp[i-1][j+1] + } + } + for (int n : dp) { + max = Math.max(max, n); + } + return max; + } + + + /** + * 正常的一维dp优化: + * 10ms + * @param s + * @return + */ + public int longestPalindromeSubseq2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] chars = s.toCharArray(); + int[] dp = new int[chars.length]; + int old; + int temp; + + for (int start = chars.length-1; start >= 0; start--) { + dp[start] = 1; + old = 0; + for (int end = start+1; end < chars.length; end++) { + temp = dp[end]; + if (chars[start] == chars[end]){ + dp[end] = 2 + old; + }else { + dp[end] = Math.max(dp[end], dp[end-1]); + } + old = temp; + } + } + + return dp[chars.length-1]; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/FindContentChildren.java b/Leecode/src/main/java/com/markilue/leecode/greedy/T01_FindContentChildren.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/greedy/FindContentChildren.java rename to Leecode/src/main/java/com/markilue/leecode/greedy/T01_FindContentChildren.java index 7ce8c92..59ae4d6 100644 --- a/Leecode/src/main/java/com/markilue/leecode/greedy/FindContentChildren.java +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/T01_FindContentChildren.java @@ -19,7 +19,7 @@ import java.util.Comparator; * * @Version: 1.0 */ -public class FindContentChildren { +public class T01_FindContentChildren { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/WiggleMaxLength.java b/Leecode/src/main/java/com/markilue/leecode/greedy/T02_WiggleMaxLength.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/greedy/WiggleMaxLength.java rename to Leecode/src/main/java/com/markilue/leecode/greedy/T02_WiggleMaxLength.java index ea143cf..61c7191 100644 --- a/Leecode/src/main/java/com/markilue/leecode/greedy/WiggleMaxLength.java +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/T02_WiggleMaxLength.java @@ -15,7 +15,7 @@ import org.junit.Test; * 给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 * @Version: 1.0 */ -public class WiggleMaxLength { +public class T02_WiggleMaxLength { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/MaxSubArray.java b/Leecode/src/main/java/com/markilue/leecode/greedy/T03_MaxSubArray.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/greedy/MaxSubArray.java rename to Leecode/src/main/java/com/markilue/leecode/greedy/T03_MaxSubArray.java index 09c94f4..aa856ee 100644 --- a/Leecode/src/main/java/com/markilue/leecode/greedy/MaxSubArray.java +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/T03_MaxSubArray.java @@ -12,7 +12,7 @@ import org.junit.Test; * 子数组 是数组中的一个连续部分。 * @Version: 1.0 */ -public class MaxSubArray { +public class T03_MaxSubArray { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/second/T01_FindContentChildren.java b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T01_FindContentChildren.java new file mode 100644 index 0000000..392b887 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T01_FindContentChildren.java @@ -0,0 +1,80 @@ +package com.markilue.leecode.greedy.second; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.greedy.second + *@Author: dingjiawen + *@CreateTime: 2023-02-06 10:21 + *@Description: + * TODO 力扣455 分发饼干: + * 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 + * 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。 + * 如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。 + * 你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 + *@Version: 1.0 + */ +public class T01_FindContentChildren { + + @Test + public void test(){ + int[] g = {1, 2, 3}, s = {1, 1}; + System.out.println(findContentChildren(g,s)); + } + + /** + * 让s中最大的依次满足g中最大的,满足不了就满足第二大的 + * 由于需要排序,所以时间复杂度O(nlogn) + * @param g + * @param s + * @return + */ + public int findContentChildren(int[] g, int[] s) { + if (g == null || g.length == 0 || s == null || s.length == 0) { + return 0; + } + + //排序选择最大的 + Arrays.sort(g); + Arrays.sort(s); + int result=0; + int sIndex=s.length-1; + for (int i = g.length-1; i >= 0; i--) { + if(sIndex<0){ + break; + } + if(s[sIndex]>=g[i]){ + //满足了再偏移s + result++; + sIndex--; + } + } + return result; + } + + + /** + * 官方最快 7ms + * 速度击败100%,内存击败47.38% + * @param g + * @param s + * @return + */ + public int findContentChildren1(int[] g, int[] s) { + if(s.length == 0 || g.length == 0) return 0; + Arrays.sort(g); + Arrays.sort(s); + int startIndex = s.length; + int childrenNum = 0; + for(int i = g.length; i > 0 && startIndex > 0; i--) { + if(g[i - 1] <= s[startIndex - 1]) { + childrenNum++; + startIndex--; + } + } + return childrenNum; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/second/T02_WiggleMaxLength.java b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T02_WiggleMaxLength.java new file mode 100644 index 0000000..ee3b0a3 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T02_WiggleMaxLength.java @@ -0,0 +1,144 @@ +package com.markilue.leecode.greedy.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.greedy.second + *@Author: dingjiawen + *@CreateTime: 2023-02-06 11:01 + *@Description: + * TODO 力扣376 摆动序列: + * 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 + * 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。 + * 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 + * 子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。 + * 给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。 + *@Version: 1.0 + */ +public class T02_WiggleMaxLength { + + @Test + public void test() { + int[] nums = {1, 7, 4, 9, 2, 5}; + System.out.println(wiggleMaxLength(nums)); + } + + @Test + public void test1() { + int[] nums = {1, 17, 5, 10, 13, 15, 10, 5, 16, 8}; + System.out.println(wiggleMaxLength(nums)); + } + + @Test + public void test2() { + int[] nums = {3,3,3,2,5}; + System.out.println(wiggleMaxLength(nums)); + } + + + /** + * 思路:记录遍历到当前位置,如果是单调的就直接继续找下一个,只用记录极值 + * @param nums + * @return + */ + public int wiggleMaxLength(int[] nums) { + + int result = 1; + int lastNum = nums[0]; + Boolean flag=null; + + for (int i = 1; i < nums.length; i++) { + if(flag==null){ + if(nums[i]!=lastNum){ + result++; + flag = nums[i] > lastNum ? true : false; + } + } + + if (flag!=null&&((flag && nums[i] < lastNum) || (!flag && nums[i] > lastNum))) { + result++; + flag = !flag; + } + lastNum = nums[i]; + } + + return result; + + + } + + /** + * 优秀写法 + * @param nums + * @return + */ + public int wiggleMaxLength1(int[] nums) { + int result = 1; + + int curdiff = 0; //记录现在的差值 + int prediff = 0; //记录上次的差值 + for (int i = 0; i < nums.length-1; i++) { + curdiff = nums[i + 1] - nums[i]; + if ((prediff >= 0 && curdiff < 0) || (prediff <= 0 && curdiff > 0)) { + result++; + prediff = curdiff; + } + + } + + return result; + } + + + /** + * 尝试动态规划法 + * TODO 动态规划五部曲: + * 1)dp定义:dp[i][0]表示第i个数作为山峰的情况;dp[i][1]表示第i个数作为山谷的情况 + * 2)dp状态转移方程: + * 1)dp[i][0]: + * max(dp[i-1][0],dp[i-1][1]&&num[i]>num[i-1]+1) + * 2)dp[i][1]: + * * max(dp[i-1][1],dp[i-1][0]&&num[i] nums[i - 1]) { + up[i] = Math.max(up[i - 1], down[i - 1] + 1); + down[i] = down[i - 1]; + } else if (nums[i] < nums[i - 1]) { + up[i] = up[i - 1]; + down[i] = Math.max(up[i - 1] + 1, down[i - 1]); + } else { + up[i] = up[i - 1]; + down[i] = down[i - 1]; + } + } + return Math.max(up[n - 1], down[n - 1]); + + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/second/T03_MaxSubArray.java b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T03_MaxSubArray.java new file mode 100644 index 0000000..b17dcf6 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/second/T03_MaxSubArray.java @@ -0,0 +1,105 @@ +package com.markilue.leecode.greedy.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.greedy.second + *@Author: dingjiawen + *@CreateTime: 2023-02-06 12:52 + *@Description: + * TODO 力扣53题 最大子数组和: + * 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + * 子数组 是数组中的一个连续部分。 + *@Version: 1.0 + */ +public class T03_MaxSubArray { + + @Test + public void test(){ + int[] nums= {5, 4, -1, 7, 8}; + System.out.println(maxSubArray1(nums)); + } + + + /** + * 贪心 + * @param nums + * @return + */ + public int maxSubArray(int[] nums) { + + int maxSum = Integer.MIN_VALUE; + int curSum = 0; + for (int i = 0; i < nums.length; i++) { + curSum += nums[i]; + if (curSum > maxSum) { + maxSum = curSum; + } + + if (curSum < 0) { + curSum = 0; + continue; + } + } + return maxSum; + + } + + + /** + * 动态规划法: + * TODO DP五部曲: + * 1)dp定义:dp[i][0]表示使用num[0-i]时不要当前值的最大值;dp[i][1]表示使用num[0-i]时要之前的最大值 + * 2)dp状态转移方程: + * 1.dp[i][0]不要现在的:要之前不要现在 和 不要之前不要现在 + * dp[i][0]=Max(dp[i-1][0],dp[i-1][1]) + * 2.dp[i][1]要之前的: 要之前要现在 和 不要之前要现在 + * dp[i][1]=Max(dp[i-1][1]+num[i],num[i]) + * 3)dp初始化:dp[i][0]=num[0] + * 4)dp遍历顺序:从前往后 + * 5)dp举例推导:nums = [-2,1,-3,4,-1,2,1,-5,4] + * [0 1] + * i=-2: -2 -2 + * i=1: -2 1 + * i=-3: 1 -2 + * i=4: 1 4 + * i=-1: 4 3 + * i=2: 4 5 + * i=1: 5 6 + * i=-5: 6 1 + * i=4: 6 5 + * @param nums + * @return + */ + public int maxSubArray1(int[] nums) { + + int[][] dp=new int[nums.length][2]; + dp[0][0]=dp[0][1]=nums[0]; + for (int i = 1; i < nums.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]); + dp[i][1]=Math.max(dp[i-1][1]+nums[i],nums[i]); + } + + return Math.max(dp[dp.length-1][0],dp[dp.length-1][1]); + + } + + /** + * dp优化 + * @param nums + * @return + */ + public int maxSubArray2(int[] nums) { + + + int last0=nums[0],last1=nums[0]; + for (int i = 1; i < nums.length; i++) { + last0=Math.max(last0,last1); + last1=Math.max(last1+nums[i],nums[i]); + } + + return Math.max(last0,last1); + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/IsAnagram.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T01_IsAnagram.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/IsAnagram.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T01_IsAnagram.java index 78aaff6..784fcd5 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/IsAnagram.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T01_IsAnagram.java @@ -14,7 +14,7 @@ import java.util.HashMap; * 注意:若s 和 t中每个字符出现的次数都相同,则称s 和 t互为字母异位词。 * @Version: 1.0 */ -public class IsAnagram { +public class T01_IsAnagram { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/Intersection.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T02_Intersection.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/Intersection.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T02_Intersection.java index f924cd6..5c3265b 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/Intersection.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T02_Intersection.java @@ -17,7 +17,7 @@ import java.util.Set; * 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 * @Version: 1.0 */ -public class Intersection { +public class T02_Intersection { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/T03_IsHappy.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T03_IsHappy.java new file mode 100644 index 0000000..9e7d60f --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T03_IsHappy.java @@ -0,0 +1,113 @@ +package com.markilue.leecode.hashtable; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable + *@Author: dingjiawen + *@CreateTime: 2022-12-27 11:06 + *@Description: + * TODO 力扣202题 快乐数: + * 编写一个算法来判断一个数 n 是不是快乐数。 + * 「快乐数」 定义为: + * 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 + * 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 + * 如果这个过程 结果为 1,那么这个数就是快乐数。 + * 如果 n 是 快乐数 就返回 true ;不是,则返回 false 。 + *@Version: 1.0 + */ +public class T03_IsHappy { + + @Test + public void test(){ + int n=19; + System.out.println(isHappy(n)); + } + + @Test + public void test1(){ + int n=2; + System.out.println(isHappy(n)); + } + + /** + * 思路:利用hashset记录重复的,一旦重复了就进入了循环,那么直接返回false + * 速度击败81.35%,内存击败42.29% 1ms + * @param n + * @return + */ + public boolean isHappy(int n) { + + HashSet set = new HashSet<>(); + set.add(n); + while (n!=1){ + int next=0; + //计算各个位置之和 + while (n!=0){ + int num = n % 10; + next+=num*num; + n/=10; + } + if(set.contains(next)){ + //已经开始循环,直接开始返回 + return false; + }else { + set.add(next); + n=next; + } + } + return true; + + } + + /** + * 官方hash思路:与本人hash + * 利用hashset记录重复的,一旦重复了就进入了循环或者等于1,那么就可以返回了 + * 速度击败81.35%,内存击败67.9% 1ms + * 时间复杂度O(logn) + * @param n + * @return + */ + public boolean isHappy1(int n) { + + Set seen = new HashSet<>(); + while (n != 1 && !seen.contains(n)) { + seen.add(n); + n = getNext(n); + } + return n == 1; + } + + private int getNext(int n) { + int totalSum = 0; + while (n > 0) { + int d = n % 10; + n = n / 10; + totalSum += d * d; + } + return totalSum; + } + + + /** + * 官方快慢指针法:核心还是监测是否有环;如果有环,那么快的一定可以和慢的相遇 + * 速度击败100% + * @param n + * @return + */ + public boolean isHappy2(int n) { + int slowRunner = n; + int fastRunner = getNext(n); + while (fastRunner != 1 && slowRunner != fastRunner) { + slowRunner = getNext(slowRunner); + fastRunner = getNext(getNext(fastRunner)); + } + return fastRunner == 1; + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/TwoSum.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T04_TwoSum.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/TwoSum.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T04_TwoSum.java index b547e31..10b5fcc 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/TwoSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T04_TwoSum.java @@ -19,7 +19,7 @@ import java.util.HashMap; * @Version: 1.0 */ -public class TwoSum { +public class T04_TwoSum { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/FourSumCount.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T05_FourSumCount.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/FourSumCount.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T05_FourSumCount.java index 398c834..4c0d2fc 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/FourSumCount.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T05_FourSumCount.java @@ -19,7 +19,7 @@ import java.util.HashSet; * 2) nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0 * @Version: 1.0 */ -public class FourSumCount { +public class T05_FourSumCount { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/T06_CanConstruct.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T06_CanConstruct.java new file mode 100644 index 0000000..4a2702c --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T06_CanConstruct.java @@ -0,0 +1,88 @@ +package com.markilue.leecode.hashtable; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable + *@Author: dingjiawen + *@CreateTime: 2022-12-28 10:12 + *@Description: + * TODO 力扣383题 赎金信: + * 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 + * 如果可以,返回 true ;否则返回 false 。 + * magazine 中的每个字符只能在 ransomNote 中使用一次。 + *@Version: 1.0 + */ +public class T06_CanConstruct { + + /** + * 思路本质上还是和第一题同素异形类似,记录字母出现的次数,只不过允许多不允许少 + * 速度击败99.88%,内存击败76.12% + * @param ransomNote + * @param magazine + * @return + */ + public boolean canConstruct(String ransomNote, String magazine) { + char[] mChars = magazine.toCharArray(); + char[] rChars = ransomNote.toCharArray(); + + int[] map = new int[26];//数组当map用 + for (char mChar : mChars) { + map[mChar-'a']++; + } + + for (char rChar : rChars) { + map[rChar-'a']--; + } + + for (int i : map) { + if(i<0){ + return false; + } + } + return true; + } + + /** + * 思路:浅优化,判断可以在第二个for就开始 + * 速度击败99.88%,内存击败71.7% + * @param ransomNote + * @param magazine + * @return + */ + public boolean canConstruct2(String ransomNote, String magazine) { + char[] mChars = magazine.toCharArray(); + char[] rChars = ransomNote.toCharArray(); + + int[] map = new int[26];//数组当map用 + for (char mChar : mChars) { + map[mChar-'a']++; + } + + for (char rChar : rChars) { + map[rChar-'a']--; + if(map[rChar-'a']<0){ + return false; + } + } + return true; + } + + + /** + * 官方最快:记录这次找到的位置,如果下次要找相同的字符只能从这次的后面开始找 + * 时间复杂度是不是要高一些,本质上取决于indexOf的时间复杂度 + * 速度击败100%,内存击败96.44% + * @param ransomNote + * @param magazine + * @return + */ + public boolean canConstruct1(String ransomNote, String magazine) { + int[] charIndex = new int[26]; + for (char c : ransomNote.toCharArray()) { + int p = magazine.indexOf(c, charIndex[c - 'a']);//indexOf(char,fromIndex) + if(p == -1) return false; + charIndex[c - 'a'] = p + 1;//如果下次要找相同的字符只能从这次的后面开始找 + } + return true; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/ThreeSum.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T07_ThreeSum.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/ThreeSum.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T07_ThreeSum.java index 5dcb089..996c821 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/ThreeSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T07_ThreeSum.java @@ -17,7 +17,7 @@ import java.util.List; * 注意:答案中不可以包含重复的三元组。 * @Version: 1.0 */ -public class ThreeSum { +public class T07_ThreeSum { /** * 这道题使用哈希法的思路: diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/FourSum.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/T08_FourSum.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/hashtable/FourSum.java rename to Leecode/src/main/java/com/markilue/leecode/hashtable/T08_FourSum.java index 8682409..bb3c34c 100644 --- a/Leecode/src/main/java/com/markilue/leecode/hashtable/FourSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/T08_FourSum.java @@ -17,7 +17,7 @@ import java.util.*; * 你可以按 任意顺序 返回答案 。 * @Version: 1.0 */ -public class FourSum { +public class T08_FourSum { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T01_IsAnagram.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T01_IsAnagram.java new file mode 100644 index 0000000..a62c93d --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T01_IsAnagram.java @@ -0,0 +1,66 @@ +package com.markilue.leecode.hashtable.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable.second + *@Author: dingjiawen + *@CreateTime: 2022-12-27 10:01 + *@Description: + * TODO 二刷242题 有效的字母异位词: + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 + *@Version: 1.0 + */ +public class T01_IsAnagram { + + @Test + public void test(){ + String s = "anagram"; + String t = "nagaram"; + + System.out.println(isAnagram(s,t)); + } + + @Test + public void test1(){ + String s = "rat"; + String t = "car"; + + System.out.println(isAnagram(s,t)); + } + + /** + * 思路:统计个字符出现的次数,再作比较即可 + * 由于字符都是小写字母,所以可以用数组存 + * @param s + * @param t + * @return + */ + public boolean isAnagram(String s, String t) { + int[] sArray = new int[26]; +// int[] tArray = new int[26]; + + char[] chars1 = s.toCharArray(); + char[] chars2 = t.toCharArray(); + + for (int i = 0; i < chars1.length; i++) { + sArray[chars1[i]-'a']++; + } + + for (int i = 0; i < chars2.length; i++) { + sArray[chars2[i]-'a']--; + } + + for (int i = 0; i < sArray.length; i++) { + if(sArray[i]!=0){ + return false; + } + } + + return true; + + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T02_Intersection.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T02_Intersection.java new file mode 100644 index 0000000..4a0e106 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T02_Intersection.java @@ -0,0 +1,90 @@ +package com.markilue.leecode.hashtable.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable.second + *@Author: dingjiawen + *@CreateTime: 2022-12-27 10:45 + *@Description: + * TODO 二刷349题 两个数组的交集: + * 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 + *@Version: 1.0 + */ +public class T02_Intersection { + + @Test + public void test(){ + int[] nums1 = {1, 2, 2, 1}; + int[] nums2 = {2, 2}; + System.out.println(Arrays.toString(intersection(nums1,nums2))); + } + + @Test + public void test1(){ + int[] nums1 = {4,9,5}; + int[] nums2 = {9,4,9,8,4}; + System.out.println(Arrays.toString(intersection(nums1,nums2))); + } + + /** + * 思路:唯一且不要求顺序:hashset + * 速度击败98.76%,内存击败13.48% 1ms + * @param nums1 + * @param nums2 + * @return + */ + public int[] intersection(int[] nums1, int[] nums2) { + HashSet set = new HashSet<>(); + for (int i : nums1) { + set.add(i); + } + int size=0; +// int length=Math.max(nums1.length,nums2.length); + int[] result = new int[set.size()]; + + for (int i : nums2) { + if(set.contains(i)){ + result[size++]=i; + set.remove(i); + } + } + return Arrays.copyOf(result,size); + + + } + + /** + * 官方最快:使用一个boolean[]来代替hashSet + * 速度击败100%,内存击败52.55% 0ms + * @param nums1 + * @param nums2 + * @return + */ + public int[] intersection1(int[] nums1, int[] nums2) { + boolean[] flag = new boolean[1001]; + ArrayList integers = new ArrayList<>(); + for(int i :nums1){ + flag[i] = true; + } + for(int j :nums2){ + if(flag[j]){ + integers.add(j); + flag[j] = false; + } + } + int[] ints = new int[integers.size()]; + for(int i = 0;i + * 速度击败78.74%,内存击败14.5% + * @param nums + * @param target + * @return + */ + public int[] twoSum(int[] nums, int target) { + + HashMap map = new HashMap<>(); + + for (int i = 0; i < nums.length; i++) { + Integer find = map.getOrDefault( nums[i], -1); + if(find!=-1){ + //找到了 + return new int[]{find,i}; + }else { + map.put(target-nums[i],i); + } + } + return new int[2]; + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T05_FourSumCount.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T05_FourSumCount.java new file mode 100644 index 0000000..4c92b80 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T05_FourSumCount.java @@ -0,0 +1,142 @@ +package com.markilue.leecode.hashtable.second; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable.second + *@Author: dingjiawen + *@CreateTime: 2022-12-27 12:10 + *@Description: + * TODO 二刷454题 四数相加 II: + * 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: + * 0 <= i, j, k, l < n + * nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0 + *@Version: 1.0 + */ +public class T05_FourSumCount { + + @Test + public void test(){ + int[] nums1 = {1, 2}, nums2 = {-2, -1}, nums3 = {-1, 2}, nums4 = {0, 2}; + System.out.println(fourSumCount(nums1,nums2,nums3,nums4)); + } + + @Test + public void test1(){ + int[] nums1 = {0}, nums2 = {0}, nums3 = {0}, nums4 = {0}; + System.out.println(fourSumCount(nums1,nums2,nums3,nums4)); + } + + /** + * 思路:这题求得是组合数,而不要求里面到底是那四个index, + * 考虑两个nums两两组成map + * 速度击败62.9%,内存击败24.56% 143% + * @param nums1 + * @param nums2 + * @param nums3 + * @param nums4 + * @return + */ + public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { + + HashMap map = new HashMap<>(); + for (int i : nums1) { + for (int i1 : nums2) { + map.put(i+i1,map.getOrDefault(i+i1,0)+1); + } + } + HashMap map1 = new HashMap<>(); + for (int i : nums3) { + for (int i1 : nums4) { + map1.put(i+i1,map1.getOrDefault(i+i1,0)+1); + } + } + int result=0; + for (Integer integer : map.keySet()) { + result+=map1.getOrDefault(-integer,0)*map.get(integer); + } + return result; + + } + + /** + * 官方hash解法:比本人少用一次hashmap,直接在for循环里面加即可 + * 速度击败82.48%,内存击败5.1% + * @param A + * @param B + * @param C + * @param D + * @return + */ + public int fourSumCount1(int[] A, int[] B, int[] C, int[] D) { + Map countAB = new HashMap(); + for (int u : A) { + for (int v : B) { + countAB.put(u + v, countAB.getOrDefault(u + v, 0) + 1); + } + } + int ans = 0; + for (int u : C) { + for (int v : D) { + if (countAB.containsKey(-u - v)) { + ans += countAB.get(-u - v); + } + } + } + return ans; + } + + /** + * 官方题解中最快的解法: + * 速度击败99.95%,内存击败10.31% 47ms + * @param nums1 + * @param nums2 + * @param nums3 + * @param nums4 + * @return + */ + public int fourSumCount2(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { + Arrays.sort(nums1); + Arrays.sort(nums2); + Arrays.sort(nums3); + Arrays.sort(nums4); + + int resultCounter = 0; + Map numMap = new HashMap<>(); + + for (int i = 0; i < nums1.length; i++) { + int iStart = i; + //重复就加快循环 + while (i + 1 < nums1.length && nums1[i] == nums1[i + 1]) { + i++; + } + for (int j = 0; j < nums2.length; j++) { + int jStart = j; + while (j + 1 < nums2.length && nums2[j] == nums2[j + 1]) { + j++; + } + numMap.put(nums1[i] + nums2[j], numMap.getOrDefault(nums1[i] + nums2[j], 0) + (i-iStart+1) * (j-jStart+1)); + } + } + for (int i = 0; i < nums3.length; i++) { + int iStart = i; + while (i + 1 < nums3.length && nums3[i] == nums3[i + 1]) { + i++; + } + for (int j = 0; j < nums4.length; j++) { + int jStart = j; + while (j + 1 < nums4.length && nums4[j] == nums4[j + 1]) { + j++; + } + resultCounter += numMap.getOrDefault(-nums3[i]-nums4[j], 0) * (i - iStart + 1) * (j - jStart + 1); + } + } + return resultCounter; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T07_ThreeSum.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T07_ThreeSum.java new file mode 100644 index 0000000..e9cb7b1 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T07_ThreeSum.java @@ -0,0 +1,116 @@ +package com.markilue.leecode.hashtable.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable.second + *@Author: dingjiawen + *@CreateTime: 2022-12-28 10:30 + *@Description: + * TODO 二刷15题 三数之和: + * 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k , + * 同时还满足 nums[i] + nums[j] + nums[k] == 0 。请 + * 你返回所有和为 0 且不重复的三元组。 + * 注意:答案中不可以包含重复的三元组。 + *@Version: 1.0 + */ +public class T07_ThreeSum { + + @Test + public void test(){ + int[] nums = {-1, 0, 1, 2, -1, -4}; + System.out.println(threeSum(nums)); + } + + /** + * 思路:这题本质上是一个O(N^3)的一个暴力解法,考虑一层for放外面,里面用hashMap记录将复杂度变为O(N^2) + * 由于对于结果不考虑index只考虑具体的数,因此考虑先对数组进行排序 + * 速度击败47.14%,内存击败84.92% 22ms + * 增加第一个数大于0,则一定不可能了的判断以后 速度击败75.68%,内存击败80.11% 20ms + * @param nums + * @return + */ + public List> threeSum(int[] nums) { + List> result = new ArrayList<>(); + if (nums.length < 3) { + return result; + } + Arrays.sort(nums);//排序 + + for (int i = 0; i < nums.length - 2; i++) { + //第一个数大于0,则一定不可能了 + if(nums[i]>0){ + break; + } + //避免重复 + if(i>0&&nums[i]==nums[i-1]){ + continue; + } + int left = i + 1; + int right = nums.length - 1; + while (left < right) { + int sum = nums[left] + nums[right]; + if (sum > -nums[i]){ + //整体大于0 + right--; + }else if(sum<-nums[i]){ + //整体小于0 + left++; + }else { + //刚刚好 + result.add(new ArrayList<>(Arrays.asList(nums[i],nums[left],nums[right]))); + left++; + right--; + //由于不能重复三元组,所以需要多偏移 + while (left> threeSum1(int[] nums) { + List> res = new ArrayList>(); + if(nums.length < 3) return res; + Arrays.sort(nums); + for(int i=0;i 0 && nums[i] != nums[i-1])) { + int j = i+1; + int k = nums.length-1; + while(j cur = new ArrayList(); + cur.add(nums[i]); + cur.add(nums[j]); + cur.add(nums[k]); + res.add(cur); + while(j 0) k--; + else j++; + } + } + } + return res; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T08_FourSum.java b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T08_FourSum.java new file mode 100644 index 0000000..d02d3e3 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hashtable/second/T08_FourSum.java @@ -0,0 +1,163 @@ +package com.markilue.leecode.hashtable.second; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hashtable.second + *@Author: dingjiawen + *@CreateTime: 2022-12-28 11:16 + *@Description: + * TODO 二刷力扣18题 四数之和: + * 给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。 + * 请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] + * (若两个四元组元素一一对应,则认为两个四元组重复): + * 0 <= a, b, c, d < n + * a、b、c 和 d 互不相同 + * nums[a] + nums[b] + nums[c] + nums[d] == target + * 你可以按 任意顺序 返回答案 。 + *@Version: 1.0 + */ +public class T08_FourSum { + + @Test + public void test() { + int[] nums = {1, 0, -1, 0, -2, 2}; + int target = 0; + System.out.println(fourSum(nums, target)); + } + + @Test + public void test1() { + int[] nums = {2, 2, 2, 2, 2}; + int target = 8; + System.out.println(fourSum(nums, target)); + } + + @Test + public void test2() { + int[] nums = {1, -2, -5, -4, -3, 3, 3, 5}; + int target = -11; + System.out.println(fourSum(nums, target)); + } + + /** + * 思路:在三数之和外面再加上一层for,时间复杂度O(N^3) + * 熟读击败49.87%,内存击败71.72% + * @param nums + * @param target + * @return + */ + public List> fourSum(int[] nums, int target) { + List> result = new ArrayList<>(); + if (nums.length < 4) { + return result; + } + Arrays.sort(nums); + + for (int i = 0; i < nums.length - 3; i++) { + if (nums[i] > 0 && nums[i] > target) break; + if (i > 0 && nums[i] == nums[i - 1]) continue; + for (int j = i + 1; j < nums.length - 2; j++) { + if (j > i + 1 && nums[j] == nums[j - 1]) continue; + + int left = j + 1; + int right = nums.length - 1; + + while (left < right) { + long sum = (long) target - nums[left] - nums[right]; + if (sum < nums[i] + nums[j]) { + //整体大于target + right--; + } else if (sum > nums[i] + nums[j]) { + //整体小于target + left++; + } else { + //刚刚好 + result.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[left], nums[right]))); + left++; + right--; + while (left < right && nums[left - 1] == nums[left]) left++; + while (left < right && nums[right + 1] == nums[right]) right--; + } + } + } + } + + return result; + + } + + /** + * 官方最快:2ms + * 与本人一致,多了几个考验功力的减枝操作 + * @param nums + * @param target + * @return + */ + public List> fourSum1(int[] nums, int target) { + Arrays.sort(nums); + List> res = new ArrayList<>(); + int length = nums.length; + for (int i = 0; i < length - 3; i++) { + // 跳过重复值 + if (i > 0 && nums[i] == nums[i - 1]) { + continue; + } + // 最小合都大于目标值,说明无解 + if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) { + //太大了 + break; + } + // 最大合小于目标值,说明i太小了,继续后移 + if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) { + //太小了 + continue; + } + for (int j = i + 1; j < length - 2; j++) { + // 跳过重复的数字 + if (j > i + 1 && nums[j] == nums[j - 1]) { + continue; + } + // 最小合都大于目标值,说明无解 + if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) { + break; + } + // 最大合小于目标值,说明i太小了,继续后移 + if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) { + continue; + } + int left = j + 1, right = length - 1; + while (left < right) { + int x = nums[left], y = nums[right]; + int sum = nums[i] + nums[j] + x + y; + if (sum == target) { + res.add(Arrays.asList(nums[i], nums[j], x, y)); + // 将两个指针都往中间推移,遇到重复的数字就跳过 + left++; + while (left < right && nums[left] == nums[left - 1]) { + left++; + } + right--; + while (left < right && nums[right] == nums[right + 1]) { + right--; + } + } else if (sum < target) { + // 和小于目标值,因为是排序过的,要想结果变大,左标就要往右移动,数字更大 + left++; + } else { + // 同理,和大于目标值,右标就要往左移动,数字更小 + right--; + } + } + } + } + return res; + + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/ListNode.java b/Leecode/src/main/java/com/markilue/leecode/listnode/ListNode.java new file mode 100644 index 0000000..30d79c7 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/ListNode.java @@ -0,0 +1,24 @@ +package com.markilue.leecode.listnode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode + *@Author: dingjiawen + *@CreateTime: 2022-12-23 10:52 + *@Description: TODO 链表基础接口 + *@Version: 1.0 + */ +public class ListNode { + + public int val; + public ListNode next; + + public ListNode(){} + public ListNode(int val){ + this.val=val; + } + public ListNode(int val,ListNode next){ + this.val=val; + this.next=next; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/ListNodeUtils.java b/Leecode/src/main/java/com/markilue/leecode/listnode/ListNodeUtils.java new file mode 100644 index 0000000..792292a --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/ListNodeUtils.java @@ -0,0 +1,57 @@ +package com.markilue.leecode.listnode; + +import org.junit.Test; + +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode + *@Author: dingjiawen + *@CreateTime: 2022-12-22 11:55 + *@Description: TODO 链表的使用工具 + *@Version: 1.0 + */ +public class ListNodeUtils { + + @Test + public void testPrint(){ + int[] nodeList= {2, 4, 3}; + ListNode root = build(nodeList); + print(root); + } + + /** + * 通过一个val数组建立链表 + * @param nodeList + * @return + */ + public static ListNode build(int[] nodeList){ + if(nodeList.length==0||nodeList==null)return null; + ListNode root =new ListNode(nodeList[0]); + ListNode temp=root; + for (int i = 1; i < nodeList.length; i++) { + temp.next= new ListNode(nodeList[i]); + temp=temp.next; + } + return root; + } + + /** + * 打印链表 + * @param root + */ + public static void print(ListNode root){ + StringBuilder builder=new StringBuilder("["); + ListNode temp=root; + while (temp!=null){ + builder.append(temp.val); + builder.append(","); + temp=temp.next; + } + builder.append("]"); + + System.out.println(builder.toString()); + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/RemoveElement.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T01_RemoveElement.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/listnode/RemoveElement.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T01_RemoveElement.java index 0f95961..6dd6024 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/RemoveElement.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T01_RemoveElement.java @@ -6,7 +6,7 @@ import org.junit.Test; * 移除链表元素: * 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 */ -public class RemoveElement { +public class T01_RemoveElement { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList.java similarity index 96% rename from Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList.java index 257ae3c..d901c35 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList.java @@ -22,11 +22,11 @@ import org.junit.Test; * obj.addAtIndex(index,val); * obj.deleteAtIndex(index); */ -public class MyLinkedList { +public class T02_MyLinkedList { public ListNode head; - public MyLinkedList() { + public T02_MyLinkedList() { } @@ -163,7 +163,7 @@ public class MyLinkedList { @Test public void test(){ - MyLinkedList linkedList = new MyLinkedList(); + T02_MyLinkedList linkedList = new T02_MyLinkedList(); linkedList.addAtHead(1); printList(linkedList.head); System.out.println(); @@ -185,7 +185,7 @@ public class MyLinkedList { } @Test public void test1(){ - MyLinkedList linkedList = new MyLinkedList(); + T02_MyLinkedList linkedList = new T02_MyLinkedList(); linkedList.addAtHead(7); printList(linkedList.head); @@ -231,7 +231,7 @@ public class MyLinkedList { } @Test public void test2(){ - MyLinkedList linkedList = new MyLinkedList(); + T02_MyLinkedList linkedList = new T02_MyLinkedList(); linkedList.addAtHead(2); printList(linkedList.head); @@ -269,7 +269,7 @@ public class MyLinkedList { @Test public void test3(){ - MyLinkedList linkedList = new MyLinkedList(); + T02_MyLinkedList linkedList = new T02_MyLinkedList(); linkedList.addAtHead(1); printList(linkedList.head); diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList1.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList1.java similarity index 92% rename from Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList1.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList1.java index e022842..5810681 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/MyLinkedList1.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T02_MyLinkedList1.java @@ -22,13 +22,13 @@ import org.junit.Test; * obj.addAtIndex(index,val); * obj.deleteAtIndex(index); */ -public class MyLinkedList1 { +public class T02_MyLinkedList1 { //该head为虚拟节点,不存放任何数据 public ListNode head; public int size; - public MyLinkedList1() { + public T02_MyLinkedList1() { head = new ListNode(0); size = 0; } @@ -122,7 +122,7 @@ public class MyLinkedList1 { @Test public void test() { - MyLinkedList1 linkedList = new MyLinkedList1(); + T02_MyLinkedList1 linkedList = new T02_MyLinkedList1(); linkedList.addAtHead(1); printList(linkedList.head); System.out.println(); @@ -145,7 +145,7 @@ public class MyLinkedList1 { @Test public void test1() { - MyLinkedList1 linkedList = new MyLinkedList1(); + T02_MyLinkedList1 linkedList = new T02_MyLinkedList1(); linkedList.addAtHead(7); printList(linkedList.head); @@ -192,7 +192,7 @@ public class MyLinkedList1 { @Test public void test2() { - MyLinkedList1 linkedList = new MyLinkedList1(); + T02_MyLinkedList1 linkedList = new T02_MyLinkedList1(); linkedList.addAtHead(2); printList(linkedList.head); @@ -230,7 +230,7 @@ public class MyLinkedList1 { @Test public void test3() { - MyLinkedList1 linkedList = new MyLinkedList1(); + T02_MyLinkedList1 linkedList = new T02_MyLinkedList1(); linkedList.addAtHead(1); printList(linkedList.head); @@ -254,7 +254,7 @@ public class MyLinkedList1 { @Test public void test4() { - MyLinkedList1 linkedList = new MyLinkedList1(); + T02_MyLinkedList1 linkedList = new T02_MyLinkedList1(); linkedList.addAtHead(4); printList(linkedList.head); @@ -295,19 +295,3 @@ public class MyLinkedList1 { */ } -class ListNode { - public int val; - public ListNode next; - - public ListNode() { - } - - public ListNode(int val) { - this.val = val; - } - - public ListNode(int val, ListNode next) { - this.val = val; - this.next = next; - } -} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/ReverseList.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T03_ReverseList.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/listnode/ReverseList.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T03_ReverseList.java index bb5ace0..7a58aef 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/ReverseList.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T03_ReverseList.java @@ -10,7 +10,7 @@ import org.junit.Test; * @Description: TODO leecode第206题,翻转链表:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 * @Version: 1.0 */ -public class ReverseList { +public class T03_ReverseList { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/swapPairs.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T04_swapPairs.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/listnode/swapPairs.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T04_swapPairs.java index 8515530..9af3e90 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/swapPairs.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T04_swapPairs.java @@ -2,7 +2,7 @@ package com.markilue.leecode.listnode; -public class swapPairs { +public class T04_swapPairs { public static void main(String[] args) { diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/removeNthFromEnd.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T05_removeNthFromEnd.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/listnode/removeNthFromEnd.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T05_removeNthFromEnd.java index 72f4376..f00e456 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/removeNthFromEnd.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T05_removeNthFromEnd.java @@ -3,7 +3,7 @@ package com.markilue.leecode.listnode; /** * 删除链表的倒数第N个节点 */ -public class removeNthFromEnd { +public class T05_removeNthFromEnd { public static void main(String[] args) { diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/T06_GetIntersectionNode.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T06_GetIntersectionNode.java new file mode 100644 index 0000000..74cbb85 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T06_GetIntersectionNode.java @@ -0,0 +1,217 @@ +package com.markilue.leecode.listnode; + +import java.util.HashSet; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode + *@Author: dingjiawen + *@CreateTime: 2022-12-26 11:09 + *@Description: + * TODO 力扣160题 相交链表: + * 给你两个单链表的头节点 headA 和 headB , + * 请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 + *@Version: 1.0 + */ +public class T06_GetIntersectionNode { + + /** + * 思路:核心在于如何判断两个链表相交了,显然不能通过两个数的值是否相等 + * 核心是内存,所以用== ? + * 使用一个hashset装headA,遍历headB? 时间复杂度O(n) + * 速度击败6.13%,内存击败96.59% 8ms + * @param headA + * @param headB + * @return + */ + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + ListNode tempA = headA; + ListNode tempB = headB; + HashSet set = new HashSet<>(); + while (tempA != null) { + set.add(tempA); + tempA = tempA.next; + } + while (tempB != null) { + boolean flag = set.contains(tempB); + if (flag) { + return tempB; + } + tempB = tempB.next; + } + return null; + } + + + /** + * 代码随想录思路:核心在于如何判断两个链表相交了,显然不能通过两个数的值是否相等 + * 分别计算出两个链表的长度,如果两个链表相同那么他们的长度应该相同 + * 因此把两个链表的尾端对齐,从短链表头开始遍历 + * 速度击败97.96%,内存击败22.84% 1ms + * @param headA + * @param headB + * @return + */ + public ListNode getIntersectionNode1(ListNode headA, ListNode headB) { + ListNode tempA = headA; + ListNode tempB = headB; + int lengthA = 0; + int lengthB = 0; + + //计算A长度 + while (tempA != null) { + lengthA++; + tempA = tempA.next; + } + //计算B长度 + while (tempB != null) { + lengthB++; + tempB = tempB.next; + } + + //让A成为最长的 + ListNode curA = headA; + ListNode curB = headB; + if (lengthA < lengthB) { + ListNode temp = curA; + curA = curB; + curB = temp; + int len = lengthA; + lengthA = lengthB; + lengthB = len; + } + + //遍历到两个头 + int len = lengthA - lengthB; + while (len-- > 0) { + curA = curA.next; + } + + //开始查找 + while (curA != null && curA != curB) { + curA = curA.next; + curB = curB.next; + } + return curA; + } + + + /** + * 官方题解中最快:快在不用交换 + * 先求俩链表的长度,然后将指针pA和pB移动到相同长度的位置 + * 判断两个指针的节点是否相同,如果相同则返回; + * 否则从此相同起点往后移动并判断俩几点是否相同 + * 速度击败100% 0ms + */ + public ListNode getIntersectionNode2(ListNode headA, ListNode headB) { + int m = 0, n = 0; + ListNode pA = headA, pB = headB; + for(; pA != null; m++){ + pA = pA.next; + } + for(; pB != null; n++){ + pB = pB.next; + } + + pA = headA; pB = headB; + // 如果是链表A长了就往后移动m-n个位置 + if(m > n){ + for(int i = 0; i < m - n; i++){ + pA = pA.next; + } + // 如果链表B长了就往后移动n-m个位置 + }else if(m < n){ + for(int i = 0; i < n - m; i++){ + pB = pB.next; + } + } + + // 判断相同起点的俩节点是否相同 + if(pA == pB){ + return pA; + } + + // 否则同时向后移动并判断 + ListNode ret = null; + while(pA != null && pB != null){ + pA = pA.next; + pB = pB.next; + if(pA == pB){ + ret = pA; + break; + } + + } + + return ret; + } + + + public ListNode getIntersectionNode3(ListNode headA, ListNode headB) { + ListNode tempA = headA; + ListNode tempB = headB; + int lengthA = 0; + int lengthB = 0; + + //计算A长度 + while (tempA != null) { + lengthA++; + tempA = tempA.next; + } + //计算B长度 + while (tempB != null) { + lengthB++; + tempB = tempB.next; + } + + //让A成为最长的 + ListNode curA = headA; + ListNode curB = headB; + if (lengthA < lengthB) { + int len=lengthB-lengthA; + for (int i = 0; i < len; i++) { + curB=curB.next; + } + }else { + int len=lengthA-lengthB; + for (int i = 0; i < len; i++) { + curA=curA.next; + } + } + //开始查找 + while (curA != null && curA != curB) { + curA = curA.next; + curB = curB.next; + } + return curA; + } + + + /** + * 官方题解法:双指针法,本质上和代码随想录方法一致 + * 只有当链表 headA和 headB都不为空时,两个链表才可能相交。 + * 因此首先判断链表 headA和 headB是否为空,如果其中至少有一个链表为空,则两个链表一定不相交,返回 null + * 当链表 headA和 headB都不为空时,创建两个指针 pA和 pB,初始时分别指向两个链表的头节点 headA和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下: + * 每步操作需要同时更新指针 pA{pA}pA 和 pB{pB}pB。 + * 如果指针 pA 不为空,则将指针 pA移到下一个节点;如果指针 pB不为空,则将指针 pB移到下一个节点。 + * 如果指针 pA为空,则将指针 pA移到链表 headB的头节点;如果指针 pB为空,则将指针 pB移到链表 headA的头节点。 + * 当指针 pA和 pB指向同一个节点或者都为空时,返回它们指向的节点或者 null。 + * 速度击败97.96% 内存击败9.31% 1ms + * @param headA + * @param headB + * @return + */ + public ListNode getIntersectionNode4(ListNode headA, ListNode headB) { + if (headA == null || headB == null) { + return null; + } + ListNode pA = headA, pB = headB; + //本质上和代码随想录是一样的,先求长度再遍历到差值,然后从短链表头开始遍历 + while (pA != pB) { + pA = pA == null ? headB : pA.next; + pB = pB == null ? headA : pB.next; + } + return pA; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/DetectCycle.java b/Leecode/src/main/java/com/markilue/leecode/listnode/T07_DetectCycle.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/listnode/DetectCycle.java rename to Leecode/src/main/java/com/markilue/leecode/listnode/T07_DetectCycle.java index 640cba7..f107d07 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/DetectCycle.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/T07_DetectCycle.java @@ -19,7 +19,7 @@ import java.util.HashSet; * @Version: 1.0 */ -public class DetectCycle { +public class T07_DetectCycle { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T01_RemoveElement.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T01_RemoveElement.java new file mode 100644 index 0000000..bbbaeb7 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T01_RemoveElement.java @@ -0,0 +1,128 @@ +package com.markilue.leecode.listnode.second; + + +import com.markilue.leecode.listnode.ListNode; +import com.markilue.leecode.listnode.ListNodeUtils; +import org.junit.Test; + +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-23 10:50 + *@Description: + * TODO 二刷 力扣203题 移除链表元素: + * 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 + *@Version: 1.0 + */ +public class T01_RemoveElement { + + @Test + public void test(){ + int[] head={1,2,6,3,4,5,6}; + int val=6; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(removeElements(root, val)); + } + + @Test + public void test1(){ + int[] head={}; + int val=6; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(removeElements(root, val)); + } + + @Test + public void test2(){ + int[] head={6,6,6,6,6}; + int val=6; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(removeElements(root, val)); + } + + + /** + * 速度击败51.77%,内存击败20.51% 1ms + * @param head + * @param val + * @return + */ + public ListNode removeElements(ListNode head, int val) { + ListNode root =new ListNode(); + root.next=head; + + ListNode temp=root; + + while (temp.next!=null){ + while (temp.next!=null&&temp.next.val==val){ + temp.next=temp.next.next; + } + if(temp.next!=null){ + temp=temp.next; + } + + } + + return root.next; + + } + + + /** + * 官方题解中最快:看起来比本人快在少了一个判断,但是对于头结点的处理不如本人 + * 0ms + * @param head + * @param val + * @return + */ + public ListNode removeElements1(ListNode head, int val) { + //删除值为val的头结点 + while(head!=null && head.val == val){ + head = head.next; + } + //传入空节点或所有结点都被删除,返回null + if(head == null)return null; + //用cur表示可能要删除的节点的前序节点 + ListNode cur = head; + + while(cur.next != null){ + //删除元素后,未遍历的新节点cur.next的前序节点还是cur + if(cur.next.val == val){ + cur.next = cur.next.next; + //仅当cur.next.val != val时,才能遍历下1个节点 + }else{ + cur = cur.next; + } + } + return head; + } + + /** + * 根据官方题解的修改: + * 0ms + * @param head + * @param val + * @return + */ + public ListNode removeElements2(ListNode head, int val) { + ListNode root =new ListNode(); + root.next=head; + + ListNode temp=root; + + while (temp.next!=null){ + //改为if以后少判断一次,因为近来就会判断一次 + if (temp.next.val==val){ + temp.next=temp.next.next; + }else { + temp=temp.next; + } + } + + return root.next; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T02_MyLinkedList.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T02_MyLinkedList.java new file mode 100644 index 0000000..c050467 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T02_MyLinkedList.java @@ -0,0 +1,259 @@ +package com.markilue.leecode.listnode.second; + +import com.markilue.leecode.listnode.ListNode; +import org.junit.Test; + +import javax.print.DocFlavor; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-23 11:20 + *@Description: + * TODO 二刷707题 设计链表: + * 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。 + * val 是当前节点的值,next 是指向下一个节点的指针/引用。 + * 如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。 + * 假设链表中的所有节点都是 0-index 的。 + * 在链表类中实现这些功能: + * get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 + * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 + * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。 + * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。 + * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。 + *@Version: 1.0 + */ +public class T02_MyLinkedList { + + //创建的虚拟头结点 + ListNode root; + int size; + + public T02_MyLinkedList() { + this.root = new ListNode(); + this.size=0; + } + + public int get(int index) { + if(index<0||index>size-1){ + return -1; + } + int find = 0; + ListNode temp = root.next; + while (find != index && temp != null) { + temp=temp.next; + find++; + } + + if(find==index){ + //找到了 + return temp.val; + }else { + //找不到 + return -1; + } + + } + + public void addAtHead(int val) { + ListNode listNode = new ListNode(val); + listNode.next = root.next; + root.next = listNode; + size++; + } + + public void addAtTail(int val) { + ListNode temp = root; + while (temp.next != null) { + temp = temp.next; + } + temp.next = new ListNode(val); + size++; + } + + public void addAtIndex(int index, int val) { + if(index<0){ + addAtHead(val); + return; + }else if(index==size){ + addAtTail(val); + return; + } + int find=0; + ListNode temp=root; + + while (find!=index&&temp.next!=null){ + temp=temp.next; + find++; + } + //找到就插了 + if(find==index){ + ListNode listNode = new ListNode(val); + listNode.next=temp.next; + temp.next=listNode; + size++; + } + + } + + public void deleteAtIndex(int index) { + if(index<0||index>size-1){ + return; + } + int find=0; + ListNode temp=root; + + while (find!=index&&temp.next!=null){ + temp=temp.next; + find++; + } + //找没找到都要插了 + if(find==index){ + temp.next=temp.next.next; + size--; + } + + } + + @Test + public void test(){ + T02_MyLinkedList linkedList = new T02_MyLinkedList(); + linkedList.addAtHead(1); + linkedList.addAtTail(3); + linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3 + System.out.println(linkedList.get(1)); + linkedList.deleteAtIndex(1); //现在链表是1-> 3 + System.out.println(linkedList.get(1)); + } + + @Test + public void test1(){ + T02_MyLinkedList linkedList = new T02_MyLinkedList(); + linkedList.addAtHead(1); + linkedList.addAtTail(3); + linkedList.addAtIndex(3,2); //链表变为1-> 2-> 3 + System.out.println(linkedList.get(1)); + linkedList.deleteAtIndex(-1); //现在链表是1-> 3 + System.out.println(linkedList.get(1)); + } + +} + + +/** + * 双向队列 + */ +class MyLinkedList { + int size; + BiListNode head; + BiListNode tail; + + public MyLinkedList() { + size = 0; + head = new BiListNode(0); + tail = new BiListNode(0); + head.next = tail; + tail.prev = head; + } + + public int get(int index) { + if (index < 0 || index >= size) { + return -1; + } + BiListNode curr; + //看看用头开始快还是用尾开始快 + if (index + 1 < size - index) { + curr = head; + for (int i = 0; i <= index; i++) { + curr = curr.next; + } + } else { + curr = tail; + for (int i = 0; i < size - index; i++) { + curr = curr.prev; + } + } + return curr.val; + } + + public void addAtHead(int val) { + addAtIndex(0, val); + } + + public void addAtTail(int val) { + addAtIndex(size, val); + } + + public void addAtIndex(int index, int val) { + if (index > size) { + return; + } + index = Math.max(0, index); + BiListNode pred, succ; + if (index < size - index) { + pred = head; + for (int i = 0; i < index; i++) { + pred = pred.next; + } + succ = pred.next; + } else { + succ = tail; + for (int i = 0; i < size - index; i++) { + succ = succ.prev; + } + pred = succ.prev; + } + size++; + BiListNode toAdd = new BiListNode(val); + toAdd.prev = pred; + toAdd.next = succ; + pred.next = toAdd; + succ.prev = toAdd; + } + + public void deleteAtIndex(int index) { + if (index < 0 || index >= size) { + return; + } + BiListNode pred, succ; + if (index < size - index) { + pred = head; + for (int i = 0; i < index; i++) { + pred = pred.next; + } + succ = pred.next.next; + } else { + succ = tail; + for (int i = 0; i < size - index - 1; i++) { + succ = succ.prev; + } + pred = succ.prev.prev; + } + size--; + pred.next = succ; + succ.prev = pred; + } +} + +class BiListNode { + int val; + BiListNode next; + BiListNode prev; + + public BiListNode(int val) { + this.val = val; + } +} + + + +/** + * Your MyLinkedList object will be instantiated and called as such: + * MyLinkedList obj = new MyLinkedList(); + * int param_1 = obj.get(index); + * obj.addAtHead(val); + * obj.addAtTail(val); + * obj.addAtIndex(index,val); + * obj.deleteAtIndex(index); + */ diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T03_ReverseList.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T03_ReverseList.java new file mode 100644 index 0000000..93edf12 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T03_ReverseList.java @@ -0,0 +1,46 @@ +package com.markilue.leecode.listnode.second; + +import com.markilue.leecode.listnode.ListNode; +import com.markilue.leecode.listnode.ListNodeUtils; +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-23 12:20 + *@Description: + * TODO 二刷206题 反转链表: + * 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 + *@Version: 1.0 + */ +public class T03_ReverseList { + + @Test + public void test(){ + int[] head = {1, 2, 3, 4, 5 }; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(reverseList(root)); + } + + @Test + public void test1(){ + int[] head = {}; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(reverseList(root)); + } + + public ListNode reverseList(ListNode head) { + ListNode root = new ListNode(); + ListNode temp=head; + ListNode tt; + while (temp!=null){ + tt=temp.next; + temp.next=root.next; + root.next=temp; + temp=tt; + } + return root.next; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T04_SwapPairs.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T04_SwapPairs.java new file mode 100644 index 0000000..a26fb75 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T04_SwapPairs.java @@ -0,0 +1,112 @@ +package com.markilue.leecode.listnode.second; + +import com.markilue.leecode.listnode.ListNode; +import com.markilue.leecode.listnode.ListNodeUtils; +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-26 09:50 + *@Description: + * TODO 二刷力扣24题 两两交换链表中的节点: + * 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。 + * 你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 + *@Version: 1.0 + */ +public class T04_SwapPairs { + + @Test + public void test(){ + int[] head={1,2,3,4}; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(swapPairs(root)); + } + + @Test + public void tes1t(){ + int[] head={1}; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(swapPairs(root)); + } + + /** + * 自己迭代法思路:速度击败100%,内存击败53.44% + * @param head + * @return + */ + public ListNode swapPairs(ListNode head) { + + if (head == null || head.next == null) { + return head; + } + + ListNode root = new ListNode(); + root.next = head; + ListNode temp = root; + while (temp.next != null) { + //以1,2,3,4为例 + if(temp.next.next!=null){ + //记录2,3,4 + ListNode temp1=temp.next.next; + //1,3,4 + temp.next.next=temp.next.next.next; + //2,1,3,4 + temp1.next=temp.next; + //root,2,1,3,4 + temp.next=temp1; + //后移继续遍历 + temp=temp.next.next; + }else { + temp=temp.next; + } + } + return root.next; + } + + + /** + * 官方迭代法 + * @param head + * @return + */ + public ListNode swapPairs1(ListNode head) { + + ListNode dummyNode = new ListNode(0); + dummyNode.next = head; + ListNode prev = dummyNode; + + while (prev.next != null && prev.next.next != null) { + ListNode temp = head.next.next; // 缓存 next + prev.next = head.next; // 将 prev 的 next 改为 head 的 next + head.next.next = head; // 将 head.next(prev.next) 的next,指向 head + head.next = temp; // 将head 的 next 接上缓存的temp + prev = head; // 步进1位 + head = head.next; // 步进1位 + } + return dummyNode.next; + } + + + /** + * 官方递归法 + * @param head + * @return + */ + public ListNode swapPairs2(ListNode head) { + // base case 退出提交 + if(head == null || head.next == null) return head; + // 获取当前节点的下一个节点 + ListNode next = head.next; + // 进行递归,先解决后面的 + ListNode newNode = swapPairs(next.next); + // 这里进行交换 + next.next = head; + head.next = newNode; + + return next; + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T05_RemoveNthFromEnd.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T05_RemoveNthFromEnd.java new file mode 100644 index 0000000..68ad784 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T05_RemoveNthFromEnd.java @@ -0,0 +1,66 @@ +package com.markilue.leecode.listnode.second; + +import com.markilue.leecode.listnode.ListNode; +import com.markilue.leecode.listnode.ListNodeUtils; +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-26 10:33 + *@Description: + * TODO 二刷19题 删除链表的倒数第 N 个结点: + * 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点 + *@Version: 1.0 + */ +public class T05_RemoveNthFromEnd { + + @Test + public void test(){ + int[] head={1,2,3,4,5}; + int n = 5; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(removeNthFromEnd(root,n)); + } + + @Test + public void test1(){ + int[] head={1}; + int n = 1; + ListNode root = ListNodeUtils.build(head); + ListNodeUtils.print(removeNthFromEnd(root,n)); + } + + /** + * 自己思路迭代法: + * 双指针:一个比另一个多走n,然后另一个到最末尾的时候,这个刚好到要删的地方 + * 速度击败100%,内存击败95.42% + * @param head + * @param n + * @return + */ + public ListNode removeNthFromEnd(ListNode head, int n) { + //弄个虚拟头结点,方便删除头结点 + ListNode root = new ListNode(); + root.next=head; + ListNode fast=root; + ListNode slow=root; + //往前置一位,找到要删除的前一个 + for (int i = 0; i < n; i++) { + fast=fast.next; + } + + //让快指针走到头 + while (fast.next!=null){ + fast=fast.next; + slow=slow.next; + } + + //慢指针进行删除 + if(slow.next!=null){ + slow.next=slow.next.next; + } + return root.next; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/second/T07_DetectCycle.java b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T07_DetectCycle.java new file mode 100644 index 0000000..964f7e0 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/second/T07_DetectCycle.java @@ -0,0 +1,54 @@ +package com.markilue.leecode.listnode.second; + +import com.markilue.leecode.listnode.ListNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.listnode.second + *@Author: dingjiawen + *@CreateTime: 2022-12-26 12:36 + *@Description: + * TODO 二刷 力扣142题 环形链表 II: + * 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 + * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 + * 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 + * 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 + * 不允许修改 链表。 + *@Version: 1.0 + */ +public class T07_DetectCycle { + + /** + * 思路:快慢指针:一个一次走一步,一个一次走两步,如果有环那么他们一定能相遇: + * 通过推导可知:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点 + * @param head + * @return + */ + public ListNode detectCycle(ListNode head) { + if(head==null||head.next==null){ + return null; + } + ListNode root = new ListNode(); + root.next=head; + ListNode fast=root; + ListNode slow=root; + + while(fast!=null&&fast.next!=null){ + fast=fast.next.next; + slow=slow.next; + //让两者相遇 + if(fast==slow){ + //从头结点出发一个,再从相遇节点出发一个 + ListNode temp=root; + while (temp!=slow){ + temp=temp.next; + slow=slow.next; + } + return slow; + } + } + //找不到相遇的 + return null; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/listnode/selftry/reverseKGroup.java b/Leecode/src/main/java/com/markilue/leecode/listnode/selftry/reverseKGroup.java index c05f7d3..d0a52df 100644 --- a/Leecode/src/main/java/com/markilue/leecode/listnode/selftry/reverseKGroup.java +++ b/Leecode/src/main/java/com/markilue/leecode/listnode/selftry/reverseKGroup.java @@ -1,6 +1,6 @@ package com.markilue.leecode.listnode.selftry; -import com.markilue.leecode.listnode.swapPairs; +import com.markilue.leecode.listnode.T04_swapPairs; public class reverseKGroup { @@ -16,7 +16,7 @@ public class reverseKGroup { } public static class ListNode { int val; - swapPairs.ListNode next; + T04_swapPairs.ListNode next; ListNode() { } @@ -25,7 +25,7 @@ public class reverseKGroup { this.val = val; } - ListNode(int val, swapPairs.ListNode next) { + ListNode(int val, T04_swapPairs.ListNode next) { this.val = val; this.next = next; } diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue.java similarity index 95% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue.java index 8a50a73..bbb2e5b 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue.java @@ -1,7 +1,6 @@ package com.markilue.leecode.stackAndDeque; import org.junit.Test; -import org.omg.CORBA.PUBLIC_MEMBER; import java.util.Stack; @@ -27,12 +26,12 @@ import java.util.Stack; * @Version: 1.0 */ -public class MyQueue { +public class T01_MyQueue { public Stack stack1; public Stack stack2; - public MyQueue() { + public T01_MyQueue() { stack1=new Stack<>(); stack2=new Stack<>(); @@ -88,7 +87,7 @@ public class MyQueue { @Test public void test(){ - MyQueue myQueue = new MyQueue(); + T01_MyQueue myQueue = new T01_MyQueue(); myQueue.push(1); // queue is: [1] myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) System.out.println(myQueue.peek());// return 1 diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue1.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue1.java similarity index 95% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue1.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue1.java index 4240e37..32b9523 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyQueue1.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T01_MyQueue1.java @@ -26,12 +26,12 @@ import java.util.Stack; * @Version: 1.0 */ -public class MyQueue1 { +public class T01_MyQueue1 { public Stack stack1; public Stack stack2; - public MyQueue1() { + public T01_MyQueue1() { stack1=new Stack<>(); stack2=new Stack<>(); @@ -76,7 +76,7 @@ public class MyQueue1 { @Test public void test(){ - MyQueue1 myQueue = new MyQueue1(); + T01_MyQueue1 myQueue = new T01_MyQueue1(); myQueue.push(1); // queue is: [1] myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) System.out.println(myQueue.peek());// return 1 diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack.java similarity index 72% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack.java index e1b86ae..36d8dd8 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack.java @@ -4,34 +4,33 @@ import org.junit.Test; import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.PriorityBlockingQueue; /** * @BelongsProject: Leecode * @BelongsPackage: com.markilue.leecode.stackAndDeque * @Author: dingjiawen * @CreateTime: 2022-09-13 09:59 - * @Description: TODO 力扣225题 用队列实现栈: - * 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 - * 实现 MyStack 类: - * void push(int x) 将元素 x 压入栈顶。 - * int pop() 移除并返回栈顶元素。 - * int top() 返回栈顶元素。 - * boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。 - *

+ * @Description: + * TODO 力扣225题 用队列实现栈: + * 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 + * 实现 MyStack 类: + * void push(int x) 将元素 x 压入栈顶。 + * int pop() 移除并返回栈顶元素。 + * int top() 返回栈顶元素。 + * boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。 *

* 注意: - * 你只能使用队列的基本操作 —— 也就是push to back、peek/pop from front、size 和is empty这些操作。 - * 你所使用的语言也许不支持队列。你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。 + * 你只能使用队列的基本操作 —— 也就是push to back、peek/pop from front、size 和is empty这些操作。 + * 你所使用的语言也许不支持队列。你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。 * @Version: 1.0 */ -public class MyStack { +public class T02_MyStack { public Queue queue1; public Queue queue2; - public MyStack() { + public T02_MyStack() { queue1 = new ArrayDeque<>(); queue2 = new ArrayDeque<>(); } @@ -96,7 +95,7 @@ public class MyStack { @Test public void test() { - MyStack obj = new MyStack(); + T02_MyStack obj = new T02_MyStack(); obj.push(1); obj.push(2); obj.push(3); diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack1.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack1.java similarity index 95% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack1.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack1.java index 6d93e2e..85fef83 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MyStack1.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T02_MyStack1.java @@ -24,12 +24,12 @@ import java.util.Queue; * 你所使用的语言也许不支持队列。你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。 * @Version: 1.0 */ -public class MyStack1 { +public class T02_MyStack1 { public Queue queue1; - public MyStack1() { + public T02_MyStack1() { queue1 = new ArrayDeque<>(); } @@ -71,7 +71,7 @@ public class MyStack1 { @Test public void test() { - MyStack1 obj = new MyStack1(); + T02_MyStack1 obj = new T02_MyStack1(); obj.push(1); obj.push(2); obj.push(3); diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/IsValid.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T03_IsValid.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/IsValid.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T03_IsValid.java index bce2c3d..0e686de 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/IsValid.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T03_IsValid.java @@ -18,7 +18,7 @@ import java.util.Stack; * 每个右括号都有一个对应的相同类型的左括号。 * @Version: 1.0 */ -public class IsValid { +public class T03_IsValid { @Test public void test() { String s = "{([([])])}"; diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T04_RemoveDuplicates.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T04_RemoveDuplicates.java new file mode 100644 index 0000000..4089d71 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T04_RemoveDuplicates.java @@ -0,0 +1,105 @@ +package com.markilue.leecode.stackAndDeque; + +import org.junit.Test; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque + *@Author: dingjiawen + *@CreateTime: 2023-01-03 10:24 + *@Description: + * TODO 力扣104题 删除组字符串中的所有相邻重复项 + * 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 + * 在 S 上反复执行重复项删除操作,直到无法继续删除。 + * 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 + *@Version: 1.0 + */ +public class T04_RemoveDuplicates { + + @Test + public void test(){ + String s= "abbaca"; + System.out.println(removeDuplicates(s)); + } + + + /** + * 思路:类似消消乐,可以使用一个栈装,如果和栈顶一样就消栈,否则入栈 + * 速度击败56.41%,内存击败62.57% 44ms + * @param s + * @return + */ + public String removeDuplicates(String s) { + + Deque stack = new LinkedList(); + char[] chars = s.toCharArray(); + + for (char aChar : chars) { + if (!stack.isEmpty() && stack.peek() == aChar) { + stack.pop(); + } else { + stack.push(aChar); + } + } + StringBuilder builder =new StringBuilder(); + //stack全pop出,组成字符串 + while (!stack.isEmpty()){ + builder.insert(0,stack.pop()); + } + + return builder.toString(); + } + + + /** + * 代码随想录拿字符串做栈 + * 速度击败71.16%,内存击败96.29% 22ms + * @param s + * @return + */ + public String removeDuplicates1(String s) { + // 将 res 当做栈 + // 也可以用 StringBuilder 来修改字符串,速度更快 + // StringBuilder res = new StringBuilder(); + StringBuffer res = new StringBuffer(); + // top为 res 的长度 + int top = -1; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top-- + if (top >= 0 && res.charAt(top) == c) { + res.deleteCharAt(top); + top--; + // 否则,将该字符 入栈,同时top++ + } else { + res.append(c); + top++; + } + } + return res.toString(); + } + + + /** + * 评论区方法:使用相同了就覆盖,类似于快慢指针 + * 速度击败96.3%,内存击败74.66% 4ms + * @param S + * @return + */ + public String removeDuplicates2(String S) { + char[] s = S.toCharArray(); + int top = -1; + for (int i = 0; i < S.length(); i++) { + if (top == -1 || s[top] != s[i]) { + s[++top] = s[i]; + } else { + top--; + } + } + return String.valueOf(s, 0, top + 1); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/EvalRPN.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T05_EvalRPN.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/EvalRPN.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T05_EvalRPN.java index 3a176b3..9a766b8 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/EvalRPN.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T05_EvalRPN.java @@ -17,7 +17,7 @@ import java.util.*; * 可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 * @Version: 1.0 */ -public class EvalRPN { +public class T05_EvalRPN { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MaxSlidingWindow.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T06_MaxSlidingWindow.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MaxSlidingWindow.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T06_MaxSlidingWindow.java index 0573eae..6fd7fcf 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/MaxSlidingWindow.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T06_MaxSlidingWindow.java @@ -9,12 +9,14 @@ import java.util.*; * @BelongsPackage: com.markilue.leecode.stackAndDeque * @Author: dingjiawen * @CreateTime: 2022-09-14 09:31 - * @Description: TODO 力扣239题 滑动窗口最大值: - * 给你一个整数数组 nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k个数字。滑动窗口每次只向右移动一位。 - * 返回 滑动窗口中的最大值 。 + * @Description: + * TODO 力扣239题 滑动窗口最大值: + * 给你一个整数数组 nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。 + * 你只可以看到在滑动窗口内的 k个数字。滑动窗口每次只向右移动一位。 + * 返回 滑动窗口中的最大值 。 * @Version: 1.0 */ -public class MaxSlidingWindow { +public class T06_MaxSlidingWindow { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/TopKFrequent.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T07_TopKFrequent.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/stackAndDeque/TopKFrequent.java rename to Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T07_TopKFrequent.java index 3eae9a6..5ca1e50 100644 --- a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/TopKFrequent.java +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/T07_TopKFrequent.java @@ -13,7 +13,7 @@ import java.util.*; * 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 * @Version: 1.0 */ -public class TopKFrequent { +public class T07_TopKFrequent { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T01_MyQueue.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T01_MyQueue.java new file mode 100644 index 0000000..904be45 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T01_MyQueue.java @@ -0,0 +1,68 @@ +package com.markilue.leecode.stackAndDeque.second; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque.second + *@Author: dingjiawen + *@CreateTime: 2023-01-02 11:46 + *@Description: + * TODO 二刷leecode232题 用栈实现队列 + * 实现 MyQueue 类: + * void push(int x) 将元素 x 推到队列的末尾 + * int pop() 从队列的开头移除并返回元素 + * int peek() 返回队列开头的元素 + * boolean empty() 如果队列为空,返回 true ;否则,返回 false + * 说明: + * 你只能使用标准的栈操作 —— 也就是只有push to top,peek/pop from top,size, 和is empty操作是合法的。 + * 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 + *@Version: 1.0 + */ +public class T01_MyQueue { + + //cur维持的是一个队列;temp维持的是一个栈 + //入栈时走temp,出栈的时候走cur + //当cur没了之后,将temp中的值灌入cur + Stack cur; + Stack temp; + + public T01_MyQueue() { + cur=new Stack<>(); + temp=new Stack<>(); + } + + public void push(int x) { + temp.push(x); + } + + public int pop() { + if(!cur.empty()){ + //cur有就出cur + return cur.pop(); + }else { + //cur没有,就将temp灌入 + while (!temp.empty()){ + cur.push(temp.pop()); + } + return cur.pop(); + } + } + + public int peek() { + if(!cur.empty()){ + return cur.peek(); + }else { + //cur没有,就将temp灌入 + while (!temp.empty()){ + cur.push(temp.pop()); + } + return cur.peek(); + } + + } + + public boolean empty() { + return temp.empty()&& cur.empty(); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T02_MyStack.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T02_MyStack.java new file mode 100644 index 0000000..4eab1d0 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T02_MyStack.java @@ -0,0 +1,70 @@ +package com.markilue.leecode.stackAndDeque.second; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque.second + *@Author: dingjiawen + *@CreateTime: 2023-01-02 12:23 + *@Description: + * TODO 二刷力扣225题 用队列实现栈: + * 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 + * 实现 MyStack 类: + * void push(int x) 将元素 x 压入栈顶。 + * int pop() 移除并返回栈顶元素。 + * int top() 返回栈顶元素。 + * boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。 + * 注意: + * 你只能使用队列的基本操作 —— 也就是push to back、peek/pop from front、size 和is empty这些操作。 + * 你所使用的语言也许不支持队列。你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。 + *@Version: 1.0 + */ +public class T02_MyStack { + + //temp当队列,cur当栈 + Queue temp; + Queue cur; + + public T02_MyStack() { + temp=new LinkedList<>(); + cur=new LinkedList<>(); + } + + public void push(int x) { + if(temp.isEmpty()){ + temp.add(x); + while (!cur.isEmpty()){ + temp.add(cur.poll()); + } + }else { + cur.add(x); + while (!temp.isEmpty()){ + cur.add(temp.poll()); + } + } + } + + public int pop() { + if(temp.isEmpty()){ + return cur.poll(); + }else { + return temp.poll(); + } + } + + public int top() { + if(temp.isEmpty()){ + return cur.peek(); + }else { + return temp.peek(); + } + } + + public boolean empty() { + return temp.isEmpty()&&cur.isEmpty(); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T03_IsValid.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T03_IsValid.java new file mode 100644 index 0000000..5cec615 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T03_IsValid.java @@ -0,0 +1,120 @@ +package com.markilue.leecode.stackAndDeque.second; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque.second + *@Author: dingjiawen + *@CreateTime: 2023-01-02 13:07 + *@Description: + * TODO 二刷力扣20题 有效的括号: + * 给定一个只包括 '(',')','{','}','[',']'的字符串 s ,判断字符串是否有效。 + *

+ * 有效字符串需满足: + * 左括号必须用相同类型的右括号闭合。 + * 左括号必须以正确的顺序闭合。 + * 每个右括号都有一个对应的相同类型的左括号。 + *@Version: 1.0 + */ +public class T03_IsValid { + + /** + * 思路:本质上是一个左右匹配,见词就消的思路,使用栈实现 + * 速度击败98.79%,内存击败58.57% + * @param s + * @return + */ + public boolean isValid(String s) { + char[] chars = s.toCharArray(); + + Stack stack = new Stack<>(); + + for (char aChar : chars) { + if(aChar=='}'){ + if(stack.isEmpty()||stack.pop()!='{'){ + return false; + } + }else if(aChar==')'){ + if(stack.isEmpty()||stack.pop()!='('){ + return false; + } + }else if(aChar==']'){ + if(stack.isEmpty()||stack.pop()!='['){ + return false; + } + }else { + stack.push(aChar); + } + } + if(stack.isEmpty()){ + return true; + }else { + return false; + } + + + } + + + /** + * 代码随想方法,直接见左加右,相对较为简洁 + * @param s + * @return + */ + public boolean isValid1(String s) { + Deque deque = new LinkedList<>(); + char ch; + for (int i = 0; i < s.length(); i++) { + ch = s.charAt(i); + //碰到左括号,就把相应的右括号入栈 + if (ch == '(') { + deque.push(')'); + }else if (ch == '{') { + deque.push('}'); + }else if (ch == '[') { + deque.push(']'); + } else if (deque.isEmpty() || deque.peek() != ch) { + return false; + }else {//如果是右括号判断是否和栈顶元素匹配 + deque.pop(); + } + } + //最后判断栈中元素是否匹配 + return deque.isEmpty(); + } + + + /** + * 官方最快:较为巧妙,使用数组模拟栈 + * 速度击败100%,内存击败45.42% + * @param s + * @return + */ + public boolean isValid2(String s){ + if(s == null || s.length()==0){ + return true; + } + char[] str = s.toCharArray(); + int N = str.length; + int size = 0; + char[] stack=new char[N]; + for (int i=0;i stack = new Stack<>();//存数 + + for (String token : tokens) { + + if(token.equals("+")){ + Integer num2 = stack.pop(); + Integer num1 = stack.pop(); + int value=num1+num2; + stack.push(value); + }else if(token.equals("-")){ + Integer num2 = stack.pop(); + Integer num1 = stack.pop(); + int value=num1-num2; + stack.push(value); + }else if(token.equals("*")){ + Integer num2 = stack.pop(); + Integer num1 = stack.pop(); + int value=num1*num2; + stack.push(value); + }else if(token.equals("/")){ + Integer num2 = stack.pop(); + Integer num1 = stack.pop(); + int value=num1/num2; + stack.push(value); + }else { + stack.push(Integer.valueOf(token)); + } + } + + return stack.peek(); + + } + + + /** + * 官方的规范写法 + * @param tokens + * @return + */ + public int evalRPN1(String[] tokens) { + + Stack stack = new Stack<>(); + + ArrayList list = new ArrayList<>(Arrays.asList("+", "-", "*", "/")); + + for (String token : tokens) { + if(!list.contains(token)){ + stack.push(Integer.valueOf(token)); + }else { + int num2=stack.pop(); + int num1=stack.pop(); + if(token.equals("+")){ + stack.push(num1+num2); + }else if(token.equals("-")){ + stack.push(num1-num2); + }else if(token.equals("*")){ + stack.push(num1*num2); + }else if(token.equals("/")){ + stack.push(num1/num2); + } + } + } + return stack.pop(); + + } + + + /** + * 官方最快: + * 速度击败100%,内存击败31.2% 1ms + */ + int index; + String[] tokens; + + public int evalRPN2(String[] tokens) { + this.tokens = tokens; + index = tokens.length - 1; + return recursion(); + } + //递归模拟栈 + public int recursion() { + String sub = tokens[index]; + switch (sub) { + case "+" : { + index--; + int right = recursion(); + index--; + int left = recursion(); + return left + right; + } + case "-" : { + index--; + int right = recursion(); + index--; + int left = recursion(); + return left - right; + } + case "*" : { + index--; + int right = recursion(); + index--; + int left = recursion(); + return left * right; + } + case "/" : { + index--; + int right = recursion(); + index--; + int left = recursion(); + return left / right; + } + default : { + return Integer.parseInt(sub); + } + } + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T06_MaxSlidingWindow.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T06_MaxSlidingWindow.java new file mode 100644 index 0000000..0bf5923 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T06_MaxSlidingWindow.java @@ -0,0 +1,156 @@ +package com.markilue.leecode.stackAndDeque.second; + +import org.junit.Test; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque.second + *@Author: dingjiawen + *@CreateTime: 2023-01-03 12:00 + *@Description: + * TODO 二刷力扣239题 滑动窗口最大值: + * 给你一个整数数组 nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。 + * 你只可以看到在滑动窗口内的 k个数字。滑动窗口每次只向右移动一位。 + * 返回 滑动窗口中的最大值 。 + *@Version: 1.0 + */ +public class T06_MaxSlidingWindow { + + @Test + public void test() { + int[] nums = {1, 3, -1, -3, 5, 3, 6, 7}; + int k = 3; + System.out.println(Arrays.toString(maxSlidingWindow(nums, k))); + } + + @Test + public void test1() { + int[] nums = {1, -1}; + int k = 1; + System.out.println(Arrays.toString(maxSlidingWindow(nums, k))); + } + + /** + * 思路:维护一个优先队列(单调的),每次淘汰过期的元素即可 + * 速度18.32%,内存击败33.44% + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow(int[] nums, int k) { + + int[] result = new int[nums.length - k + 1]; + PriorityQueue deque = new PriorityQueue<>(new Comparator() { + @Override + public int compare(int[] o1, int[] o2) { + return o1[0] != o2[0] ? o2[0] - o1[0] : o2[1] - o1[1]; + } + });// + + + //构造第一个窗口 + int max = nums[0]; //维护窗口最大值 + int index = -1; + for (int i = 0; i < k; i++) { + deque.offer(new int[]{nums[i], i}); + if (max < nums[i]) max = nums[i]; + } + result[++index] = max; + + for (int i = k; i < nums.length; i++) { + deque.offer(new int[]{nums[i], i}); + while (i - k >= deque.peek()[1]) { + deque.poll(); + } + result[++index] = deque.peek()[0]; + } + return result; + + } + + /** + * 代码随想录思路:维护一个双端队列,然后使其单调,每次加入时淘汰过窗口的和非单调的 + * 速度击败83.77%,内存击败48.77% + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow1(int[] nums, int k) { + ArrayDeque deque = new ArrayDeque<>(); + int n = nums.length; + int[] res = new int[n - k + 1]; + int idx = 0; + for(int i = 0; i < n; i++) { + // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点 + // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出 + while(!deque.isEmpty() && deque.peek() < i - k + 1){ + deque.poll(); + } + // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出 + while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { + deque.pollLast(); + } + + deque.offer(i); + + // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了 + if(i >= k - 1){ + res[idx++] = nums[deque.peek()]; + } + } + return res; + } + + /** + * 官方最快:理论上来说是最基础的方法,只不过可能剪枝效果太好,看起来速度很多 + * 极端情况下需要一直在窗口中for来寻找最大值,时间复杂度直接O(k*n) + * 速度击败100%,内存击败99.59% + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow2(int[] nums, int k) { + int n = nums.length; + int left = 0, right = k - 1; + // 当前的最大值的下标 + int pre = -1; + int max = Integer.MIN_VALUE; // min - 1 = max + int[] ans = new int[n - k + 1]; + + while (right < n) { + // 滑动窗口left-right + if (left <= pre) { + // 窗口滑动前的最大值,还在窗口里面 + // 比较上一轮最大值和窗口新增的值 + if (nums[right] >= nums[pre]) { + pre = right; + max = nums[right]; + } + } else if (nums[right] >= max - 1 ) { + //max-1是因为max是right-left中最大的,如果有相同的他肯定是最后一个 + //所以这个数后面的数如果不是最大值,那么肯定<=max-1 + pre = right; + max = nums[right]; + } else if (nums[left] >= max - 1) { + pre = left; + max = nums[left]; + } else { + //既不是最左边又不是最右边,就需要手动求最大值 + max = Integer.MIN_VALUE; + // 求当前窗口的最大值 + for (int i = left; i <= right; i++) { + if (nums[i] >= max) { + max = nums[i]; + pre = i; + } + } + } + ans[left] = max; + left++; + right++; + } + return ans; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T07_TopKFrequent.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T07_TopKFrequent.java new file mode 100644 index 0000000..0aa3fe2 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/second/T07_TopKFrequent.java @@ -0,0 +1,164 @@ +package com.markilue.leecode.stackAndDeque.second; + +import org.junit.Test; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.stackAndDeque.second + *@Author: dingjiawen + *@CreateTime: 2023-01-04 10:21 + *@Description: + * TODO 二刷leecode347 前k个高频元素: + * 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案 + *@Version: 1.0 + */ +public class T07_TopKFrequent { + + + @Test + public void test(){ + int[] nums = {1, 1, 1, 2, 2, 3}; + int k = 2; + System.out.println(Arrays.toString(topKFrequent(nums,k))); + } + + @Test + public void test2(){ + int[] nums = {3,2,3,1,2,4,5,5,6,7,7,8,2,3,1,1,1,10,11,5,6,2,4,7,8,5,6}; + int k = 10; + System.out.println(Arrays.toString(topKFrequent(nums,k))); + } + + @Test + public void test1(){ + int[] nums = {6,0,1,4,9,7,-3,1,-4,-8,4,-7,-3,3,2,-3,9,5,-4,0}; + int k = 6; + System.out.println(Arrays.toString(topKFrequent1(nums,k))); + } + + + /** + * 思路:TopK的本质还是排序后留下前K个,需要记录 + * 速度击败5.31%,内存击败5.6% 23ms + * @param nums + * @param k + * @return + */ + public int[] topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for (int num : nums) { + map.put(num, map.getOrDefault(num, 0) + 1); + } + + int[] result = new int[k];//直接使用数组的问题就是,每插一次就要把他后面的往后挪 + Arrays.fill(result,Integer.MIN_VALUE); + if(map.size()==k){ + int i=0; + for (Integer integer : map.keySet()) { + result[i++]=integer; + } + }else { + for (Map.Entry entry : map.entrySet()) { + Integer count = entry.getValue(); + //找到第一个小于count的数 + int flag = 0; + while (flag < k && map.getOrDefault(result[flag],0) >= count) { + flag++; + } + int temp=flag; + //插入 + for (int i = 1; i < k-flag; i++) { + result[k-i] = result[k-i-1]; + } + + if(temp,使用能排序的优先队列实现 + * 时间复杂度O(nlogk) 堆化操作复杂度logk + * 速度击败90.55%,内存击败48.40% 12ms + * @param nums + * @param k + * @return + */ + public int[] topKFrequent1(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for (int num : nums) { + map.put(num, map.getOrDefault(num, 0) + 1); + } + +// int[] result = new int[k];//直接使用数组的问题就是,每插一次就要把他后面的往后挪 + PriorityQueue deque = new PriorityQueue<>(new Comparator() { + @Override + public int compare(int[] o1, int[] o2) { + return o1[1]==o2[1]?o2[0]-o1[0]:o2[1]-o1[1]; + } + }); + + for (Map.Entry entry : map.entrySet()) { + + deque.offer(new int[]{entry.getKey(),entry.getValue()}); + + } + int[] result = new int[k]; + for (int i = 0; i < result.length; i++) { + result[i]=deque.poll()[0]; + } + return result; + + + } + + + /** + * 官方最快:时间复杂度降到O(N) + * 速度击败100%,内存击败24.45% 1ms + * @param nums + * @param k + * @return + */ + public int[] topKFrequent2(int[] nums, int k) { + int max = Integer.MIN_VALUE,min = Integer.MAX_VALUE; + for(int i : nums){//找出最大最小值 + max = (max < i)?i:max; + min = (min > i)?i:min; + } + if(max==min)return new int[]{nums[0]}; + int times[] = new int[max-min+1];//定义桶大小 + for(int i : nums){ + times[i-min]++;//记录各个数出现的次数 + } + ArrayList count[] = new ArrayList[nums.length];//不同数量对应的数字集合 + for(int i = 0; i < times.length; i++){ + if(times[i] > 0){ + if(count[times[i]] == null){ + count[times[i]] = new ArrayList(); + } + count[times[i]].add(i+min);//相同次数的加在同一个count[i]这种 + } + } + int res[] = new int[k];//答案 + for(int i = count.length-1,j = 0; i >=0 && j < k ; i--){ + if(count[i] != null){ + while(!count[i].isEmpty()){ + res[j++] = count[i].remove(count[i].size()-1); + } + } + } + return res; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/string/ReverseString.java b/Leecode/src/main/java/com/markilue/leecode/string/T01_ReverseString.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/string/ReverseString.java rename to Leecode/src/main/java/com/markilue/leecode/string/T01_ReverseString.java index f284b79..3e4daf1 100644 --- a/Leecode/src/main/java/com/markilue/leecode/string/ReverseString.java +++ b/Leecode/src/main/java/com/markilue/leecode/string/T01_ReverseString.java @@ -14,7 +14,7 @@ import org.junit.Test; * * @Version: 1.0 */ -public class ReverseString { +public class T01_ReverseString { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/string/ReverseStr.java b/Leecode/src/main/java/com/markilue/leecode/string/T02_ReverseStr.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/string/ReverseStr.java rename to Leecode/src/main/java/com/markilue/leecode/string/T02_ReverseStr.java index 50387ac..39dc0d1 100644 --- a/Leecode/src/main/java/com/markilue/leecode/string/ReverseStr.java +++ b/Leecode/src/main/java/com/markilue/leecode/string/T02_ReverseStr.java @@ -13,7 +13,7 @@ import org.junit.Test; * 2)如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 * @Version: 1.0 */ -public class ReverseStr { +public class T02_ReverseStr { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/string/T03_ReplaceSpace.java b/Leecode/src/main/java/com/markilue/leecode/string/T03_ReplaceSpace.java new file mode 100644 index 0000000..7e0dcc4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/string/T03_ReplaceSpace.java @@ -0,0 +1,77 @@ +package com.markilue.leecode.string; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.string + *@Author: dingjiawen + *@CreateTime: 2022-12-28 12:41 + *@Description: + * TODO 剑指offer05 替换空格: + * + *@Version: 1.0 + */ +public class T03_ReplaceSpace { + + @Test + public void test(){ + String s = "We are happy."; + System.out.println(replaceSpace(s)); + } + + /** + * 思路:使用额外的辅助空间build进行构造 + * 速度击败100%,内存击败36.8% + * @param s + * @return + */ + public String replaceSpace(String s) { + StringBuilder builder = new StringBuilder(); + char[] chars = s.toCharArray(); + for (char aChar : chars) { + if(' '==aChar){ + builder.append("%20"); + }else { + builder.append(aChar); + } + } + + return builder.toString(); + + } + + + /** + * 双指针法:不使用额外的空间,首先将原来的进行扩容 + * 速度击败100%,内存击败20.93% + * @param s + * @return + */ + public String replaceSpace1(String s) { + int count = 0; // 统计空格的个数 + int sOldSize = s.length(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == ' ') { + count++; + } + } + // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小 + char[] chars = Arrays.copyOf(s.toCharArray(), s.length() + count * 2); + int sNewSize = chars.length; + // 从后先前将空格替换为"%20" + for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) { + if (chars[j] != ' ') { + chars[i] = chars[j]; + } else { + chars[i] = '0'; + chars[i - 1] = '2'; + chars[i - 2] = '%'; + i -= 2; + } + } + return new String(chars); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/string/ReverseWords.java b/Leecode/src/main/java/com/markilue/leecode/string/T04_ReverseWords.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/string/ReverseWords.java rename to Leecode/src/main/java/com/markilue/leecode/string/T04_ReverseWords.java index 9205569..eeb0bb7 100644 --- a/Leecode/src/main/java/com/markilue/leecode/string/ReverseWords.java +++ b/Leecode/src/main/java/com/markilue/leecode/string/T04_ReverseWords.java @@ -19,7 +19,7 @@ import java.util.stream.Stream; * 注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 * @Version: 1.0 */ -public class ReverseWords { +public class T04_ReverseWords { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/string/T05_ReverseLeftWords.java b/Leecode/src/main/java/com/markilue/leecode/string/T05_ReverseLeftWords.java new file mode 100644 index 0000000..27f7f2b --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/string/T05_ReverseLeftWords.java @@ -0,0 +1,92 @@ +package com.markilue.leecode.string; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.string + *@Author: dingjiawen + *@CreateTime: 2022-12-30 11:48 + *@Description: + * TODO 力扣 剑指offer58题 左旋转字符串: + * 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。 + * 比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 + *@Version: 1.0 + */ +public class T05_ReverseLeftWords { + + @Test + public void test(){ + String s = "abcdefg"; + int k = 2; + System.out.println(reverseLeftWords(s,k)); + } + + @Test + public void test1(){ + String s = "lrloseumgh"; + int k = 6; + System.out.println(reverseLeftWords(s,k)); + } + + /** + * 思路:和T04很像,比T04简单 + * 速度击败52.3%,内存击败28.97% + * @param s + * @param n + * @return + */ + public String reverseLeftWords(String s, int n) { + StringBuilder builder = new StringBuilder(); + char[] chars = s.toCharArray(); + int right=n; + while (n chars.length-1){ + reverse(chars,i, chars.length-1); + }else { + reverse(chars,i,i+k-1); + } + } + return new String(chars); + + } + public void reverse(char[] s, int startIndex,int endIndex) { + char temp; + while (startIndex= start) { + if (chars[end] != ' ' || chars[end + 1] != ' ') { + newChar[k++] = chars[end--]; + } else { + end--; + } + } + char[] result = Arrays.copyOf(newChar, k); + //反转内部单词 + int left = 0; + int right = 0; + while (right < result.length) { + if (right == result.length - 1) { + reverse(result, left, right); + } + if (result[right] == ' ') { + reverse(result, left, right - 1); + left = right + 1; + } + right++; + } + return new String(result); + } + + + public void reverse(char[] chars, int start, int end) { + char temp; + while (start < end) { + temp = chars[start]; + chars[start] = chars[end]; + chars[end] = temp; + start++; + end--; + } + } + + + /** + * 官方最快:省去了反转时间 + * 速度击败100%,内存击败74.78% 1ms + * @param s + * @return + */ + public String reverseWords1(String s) { + char[] c = s.toCharArray(); + char[] newC = new char[c.length + 1]; + int i = c.length - 1; + int index = 0; + while(i >= 0){ + while(i >= 0 && c[i] == ' ') --i;//删除前空格 + int right = i; + while(i >= 0 && c[i] != ' ') --i;//找到第一个空格 + //找到左右入口单词直接填入即可,剩下的单词类似于递归,继续重新开始上面的步骤 + for(int left = i + 1; left <= right; ++left){ + newC[index++] = c[left]; + if(left == right){ + newC[index++] = ' '; + } + } + } + if(index == 0){ + return ""; + } + else{ + return new String(newC, 0, index - 1);//左闭右开的 + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/string/second/T06_StrStr.java b/Leecode/src/main/java/com/markilue/leecode/string/second/T06_StrStr.java new file mode 100644 index 0000000..2a8fd25 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/string/second/T06_StrStr.java @@ -0,0 +1,247 @@ +package com.markilue.leecode.string.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.string.second + *@Author: dingjiawen + *@CreateTime: 2022-12-30 12:05 + *@Description: + * TODO 二刷力扣28题 找出字符串中第一个匹配项的下标: + * 给你两个字符串 haystack 和 needle + * 请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。 + * 如果 needle 不是 haystack 的一部分,则返回 -1 。 + *@Version: 1.0 + */ +public class T06_StrStr { + + @Test + public void test() { + String haystack = "sadbutsad", needle = "sad"; + System.out.println(strStr1(haystack, needle)); + } + + @Test + public void test1() { + String haystack = "leetcode", needle = "leeto"; + System.out.println(strStr(haystack, needle)); + } + + @Test + public void test2() { + String haystack = "hello", needle = "ll"; + System.out.println(strStr(haystack, needle)); + } + + @Test + public void test3() { + String haystack = "mississippi", needle = "issip"; + System.out.println(strStr(haystack, needle)); + } + + @Test + public void test4() { + String haystack = "ababaabbbbababbaabaaabaabbaaaabbabaabbbbbbabbaabbabbbabbbbbaaabaababbbaabbbabbbaabbbbaaabbababbabbbabaaabbaabbabababbbaaaaaaababbabaababaabbbbaaabbbabb", needle = "abbabbbabaa"; + System.out.println(strStr3(haystack, needle)); + } + + /** + * 思路:试试动态规划法:有问题,一旦匹配不上,后面就一直匹配不上了,虽然好像可以改状态转移方程,但是改完之后定义就变了 + * TODO 动态规划五部曲:暂时有问题 + * 1.dp定义: dp[i][j]表示使用haystack[0-j]能否匹配上needle[0-i] + * 2.dp状态转移方程:dp[i][j]有两种方式得到:使用haystack[j]和不使用haystack[j] + * 1.使用haystack[j]:现在刚匹配上 + * dp[i][j]=!dp[i-1][j-2]&&dp[i-1][j-1]&&(haystack[j]==needle[i]) + * 2.不使用haystack[j]:以前就匹配上了 + * dp[i][j]=dp[i][j-1] + * 所以dp[i][j]=(!dp[i-1][j-2]&&dp[i-1][j-1]&&(haystack[j]==needle[i]))|| (dp[i][j-1]) + * 3.dp初始化:为了方便初始化,将上述定义变为haystack[0-j-1]能否匹配上needle[0-i-1],那么dp[0][i]=true;dp[i][0]=false + * 4.dp遍历顺序: + * 5.dp举例推导:以haystack = "sadbutsad", needle = "sad"为例: + * [0 s a d b t s a d] + * i=0 t t t t t t t t t + * i=s f t t t t t t t t + * i=a f f t t t t t t t + * i=d f f f t t t t t t + * @param haystack + * @param needle + * @return + */ + public int strStr(String haystack, String needle) { + + boolean[][] dp = new boolean[needle.length() + 1][haystack.length() + 1]; + //初始化 + for (int i = 0; i < dp[0].length; i++) { + dp[0][i] = true; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + boolean flag = j - 2 >= 0 && i - 2 > 0 ? !dp[i - 1][j - 2] : true; + dp[i][j] = (flag && dp[i - 1][j - 1] && (haystack.charAt(j - 1) == needle.charAt(i - 1))) || (dp[i][j - 1]); + } + } + + for (int i = 1; i < dp[0].length; i++) { + if (dp[needle.length()][i]) { + return i - needle.length(); //找到了全匹配到的位置 + } + } + + return -1; + + } + + + /** + * 思路:尝试KMP算法。当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。 + * 速度击败45.39%,内存击败5.21% + * @param haystack + * @param needle + * @return + */ + public int strStr1(String haystack, String needle) { + if (needle == null || needle.length() == 0) { + return 0; + } + int[] next = getNext1(needle); + int index = -1;//开始位置 + + for (int i = 0; i < haystack.length(); i++) { + while (index >= 0 && needle.charAt(index + 1) != haystack.charAt(i)) { + index = next[index]; + } + if (needle.charAt(index + 1) == haystack.charAt(i)) { + index++; + } + if (index == needle.length() - 1) { + return (i - needle.length() + 1); + } + } + + return -1;//没找到 + + } + + + /** + * 官方获取next数组的方法:精髓在于这次的next[i]可以通过上一次的next[i-1]来获取,分为两种情况: + * 1)如果next[i-1]的下一个数和next[i]的下一个数相等,那么最大子串一定是在上一个的基础上+1,对应下面的if的内容 + * 2)如果next[i-1]的下一个数和next[i]的下一个数不相等,那么寻找上一个最大子串,看看上一次最大子串的下一个数和next[i]的下一个数相等,如果不相等就寻找在上一次的,对应于下面的while内容 + * 下所述的看就是用来记录上一次最大子串的索引 + * @param needle + * @return + */ + public int[] getNext1(String needle) { + int[] next = new int[needle.length()]; + next[0] = -1; + int k = -1; + for (int i = 1; i < needle.length(); ++i) { + //神奇的是,k既是长度又是上一次不相等的索引 + while (k != -1 && needle.charAt(k + 1) != needle.charAt(i)) { + k = next[k]; + } + if (needle.charAt(k + 1) == needle.charAt(i)) { + ++k; + } + next[i] = k; + } + + return next; + + } + + + /** + * 官方最快 + * 速度击败100%,内存击败37.1% 0ms + * @param haystack + * @param needle + * @return + */ + public int strStr2(String haystack, String needle) { + int n = haystack.length(), m = needle.length(); + if (m == 0) { + return 0; + } + int[] pi = new int[m];//next数组 + for (int i = 1, j = 0; i < m; i++) { + while (j > 0 && needle.charAt(i) != needle.charAt(j)) { + j = pi[j - 1]; + } + if (needle.charAt(i) == needle.charAt(j)) { + j++; + } + pi[i] = j; + } + for (int i = 0, j = 0; i < n; i++) { + while (j > 0 && haystack.charAt(i) != needle.charAt(j)) { + j = pi[j - 1]; + } + if (haystack.charAt(i) == needle.charAt(j)) { + j++; + } + if (j == m) { + return i - m + 1; + } + } + return -1; + } + + + /** + * 自己尝试next不减1的做法 + * 速度击败100%,内存击败70.85% + * @param haystack + * @param needle + * @return + */ + public int strStr3(String haystack, String needle) { + int m = haystack.length(); + int n = needle.length(); + if (n == 0) { + return 0; + } + //根据needle构造一个next数组,next[i]表示needle[0-i]的最长公共子串 + int[] next = getNext(needle); + int index=0; + for (int i = 0; i < m; i++) { + while (index>0&&haystack.charAt(i)!=needle.charAt(index)){ + index=next[index-1];//匹配不上就找上一个字母在哪重复的,看看之前重复的地方能不能匹配上 + } + if(haystack.charAt(i)==needle.charAt(index)){ + index++;//匹配上就试试下一个能不能匹配上 + } + if(index==n){ + //匹配到最末尾了 + return i-index+1; + } + } + return -1; + } + + public int[] getNext(String needle) { + int index = 0; + int[] next = new int[needle.length()]; + next[0] = 0; + char[] chars = needle.toCharArray(); + + + for (int i = 1; i < next.length; i++) { + //如果匹配不上了 + while (index != 0 && chars[i] != chars[index]){ + //有趣的是:index既是最长公共子串长度,又是上一次匹配上的索引,所以能这样 + index=next[index-1];//匹配不上就找上一个字母在哪重复的,看看之前重复的地方能不能匹配上 + } + if(chars[i]==chars[index]){ + index++; + } + next[i]=index; + } + return next; + + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/string/second/T07_RepeatedSubstringPattern.java b/Leecode/src/main/java/com/markilue/leecode/string/second/T07_RepeatedSubstringPattern.java new file mode 100644 index 0000000..b9b3669 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/string/second/T07_RepeatedSubstringPattern.java @@ -0,0 +1,107 @@ +package com.markilue.leecode.string.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.string.second + *@Author: dingjiawen + *@CreateTime: 2023-01-02 10:33 + *@Description: + * TODO 二刷力扣459题 重复的子字符串: + * 给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。 + *@Version: 1.0 + */ +public class T07_RepeatedSubstringPattern { + + @Test + public void test() { + String s = "abcabcdbc"; + System.out.println(repeatedSubstringPattern(s)); + } + + @Test + public void test1() { + String s = "aba"; + System.out.println(repeatedSubstringPattern(s)); + } + + @Test + public void test2() { + String s = "aabaaba"; + System.out.println(repeatedSubstringPattern(s)); + } + + + /** + * 思路:重复子字符串,可以从其next数组进行判断,如果可以重复,其next数组最后一位长度就是最后的长度 + * 如果不做前面的StrStr可能还是想不到 + * 速度击败76.23%,内存击败69.88% 9ms + * @param s + * @return + */ + public boolean repeatedSubstringPattern(String s) { + + int[] next = getNext(s); + int len = next.length; + if (next[len - 1] != 0 && len % (len - next[len - 1]) == 0) { + //刚好能整除 + return true; + } else { + return false; + } + + } + + public int[] getNext(String c) { + int index = 0; + int[] next = new int[c.length()]; + next[0] = 0; + for (int i = 1; i < c.length(); i++) { + while (index > 0 && c.charAt(i) != c.charAt(index)) { + //如果当前两数不相等,寻找 上次的相同前缀的位置next[index-1] 的下一个字符是否相等 + index = next[index - 1]; + } + + if (c.charAt(i) == c.charAt(index)) { + index++; + } + + next[i] = index; + } + + return next; + } + + + /** + * 官方最快:在字符串前面加一个空格 + * 速度击败90.73%,内存击败14.30% 5ms + * @param s + * @return + */ + public boolean repeatedSubstringPattern1(String s) { + //一个用contain, 一个用KMP + int len = s.length(); + s = ' ' + s; + char[] ch = s.toCharArray(); + int[] next = new int[len + 1]; + for(int i = 2, j = 0; i <= len; i++){ + while(j > 0 && ch[i] != ch[j + 1]){ + j = next[j]; + } + if(ch[i] == ch[j + 1]){ + j++; + } + next[i] = j; + } + + if(next[len] > 0 && len % (len - next[len]) == 0){ + return true; + } + return false; + + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNodeII.java b/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNodeII.java index 52fe779..e65b222 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNodeII.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNodeII.java @@ -4,7 +4,7 @@ import org.junit.Test; import java.util.List; -import static com.markilue.leecode.tree.InorderTraversal.inorderTraversal1; +import static com.markilue.leecode.tree.T03_InorderTraversal.inorderTraversal1; /** * @BelongsProject: Leecode diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/PreOrderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/T01_PreOrderTraversal.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/PreOrderTraversal.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T01_PreOrderTraversal.java index 6bffeb5..a1cbc54 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/PreOrderTraversal.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T01_PreOrderTraversal.java @@ -18,7 +18,7 @@ import java.util.Stack; * 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 * @Version: 1.0 */ -public class PreOrderTraversal { +public class T01_PreOrderTraversal { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/PostOrderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/T02_PostOrderTraversal.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/PostOrderTraversal.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T02_PostOrderTraversal.java index 41bef86..864141e 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/PostOrderTraversal.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T02_PostOrderTraversal.java @@ -13,7 +13,7 @@ import java.util.*; * 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 * @Version: 1.0 */ -public class PostOrderTraversal { +public class T02_PostOrderTraversal { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/InorderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/T03_InorderTraversal.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/InorderTraversal.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T03_InorderTraversal.java index e9ec1a8..7160d4f 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/InorderTraversal.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T03_InorderTraversal.java @@ -8,7 +8,7 @@ import java.util.Stack; /** * 中序遍历二叉排序树 */ -public class InorderTraversal { +public class T03_InorderTraversal { public static void main(String[] args) { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/LevelOrder.java b/Leecode/src/main/java/com/markilue/leecode/tree/T04_LevelOrder.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/LevelOrder.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T04_LevelOrder.java index 60ac632..f30c27c 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/LevelOrder.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T04_LevelOrder.java @@ -14,7 +14,7 @@ import java.util.*; * 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 * @Version: 1.0 */ -public class LevelOrder { +public class T04_LevelOrder { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/InvertTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/T05_InvertTree.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/InvertTree.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T05_InvertTree.java index 768ae4e..faa9991 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/InvertTree.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T05_InvertTree.java @@ -16,7 +16,7 @@ import java.util.Queue; * @Version: 1.0 */ -public class InvertTree { +public class T05_InvertTree { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/IsSymmetric.java b/Leecode/src/main/java/com/markilue/leecode/tree/T06_IsSymmetric.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/IsSymmetric.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T06_IsSymmetric.java index 6dd2993..5327bde 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/IsSymmetric.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T06_IsSymmetric.java @@ -16,7 +16,7 @@ import java.util.Queue; * 给你一个二叉树的根节点 root , 检查它是否轴对称。 * @Version: 1.0 */ -public class IsSymmetric { +public class T06_IsSymmetric { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/T07_CountNodes.java b/Leecode/src/main/java/com/markilue/leecode/tree/T07_CountNodes.java new file mode 100644 index 0000000..3a91bc2 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T07_CountNodes.java @@ -0,0 +1,103 @@ +package com.markilue.leecode.tree; + +import java.util.LinkedList; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree + *@Author: dingjiawen + *@CreateTime: 2023-01-09 10:56 + *@Description: + * TODO 力扣222题 完全二叉树的节点个数: + * 给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。 + * 完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外, + * 其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。 + *@Version: 1.0 + */ +public class T07_CountNodes { + + /** + * 思路:递归法,尝试DFS + * 速度击败100%,内存击败85.46% + * @param root + * @return + */ + public int countNodes(TreeNode root) { + count(root); + return count; + } + int count=0; + public void count(TreeNode node) { + if(node==null){ + return; + } + count+=1; + count(node.left); + count(node.right); + } + + /** + * 迭代法:尝试BFS + * 速度击败19.63%,内存击败6.91% + * @param root + * @return + */ + public int countNodes1(TreeNode root) { + int count=0; + if(root==null){ + return count; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + count++; + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + } + return count; + + } + + /** + * 代码随想录针对普通二叉树的解法 + * + * + * 速度击败100%,内存击败31.23% + */ + public int countNodes3(TreeNode root) { + if (root == null) return 0; + return countNodes3(root.left) + countNodes3(root.right) + 1; + } + + + /** + * 代码随想录针对完全二叉树的解法 + * + * 满二叉树的结点数为:2^depth - 1 + * 速度击败100%,内存击败89.89% + */ + public int countNodes2(TreeNode root) { + if (root == null) return 0; + TreeNode left = root.left; + TreeNode right = root.right; + int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便 + while (left != null) { // 求左子树深度 + left = left.left; + leftDepth++; + } + while (right != null) { // 求右子树深度 + right = right.right; + rightDepth++; + } + if (leftDepth == rightDepth) {//左右深度相等,那么就是满二叉树 + return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0 + } + return countNodes2(root.left) + countNodes2(root.right) + 1; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/IsBalance.java b/Leecode/src/main/java/com/markilue/leecode/tree/T08_IsBalance.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/IsBalance.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T08_IsBalance.java index b037f60..1f6edd7 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/IsBalance.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T08_IsBalance.java @@ -17,7 +17,7 @@ import java.util.List; * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 * @Version: 1.0 */ -public class IsBalance { +public class T08_IsBalance { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/BinaryTreePaths.java b/Leecode/src/main/java/com/markilue/leecode/tree/T09_BinaryTreePaths.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/BinaryTreePaths.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T09_BinaryTreePaths.java index 303a8de..53ed1c9 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/BinaryTreePaths.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T09_BinaryTreePaths.java @@ -17,7 +17,7 @@ import java.util.Queue; * 叶子节点 是指没有子节点的节点。 * @Version: 1.0 */ -public class BinaryTreePaths { +public class T09_BinaryTreePaths { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/T10_SumOfLeftLeaves.java b/Leecode/src/main/java/com/markilue/leecode/tree/T10_SumOfLeftLeaves.java new file mode 100644 index 0000000..75c3cbb --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T10_SumOfLeftLeaves.java @@ -0,0 +1,133 @@ +package com.markilue.leecode.tree; + +import org.junit.Test; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-10 10:12 + *@Description: + * TODO 力扣404 左子树之和: + * 给定二叉树的根节点 root ,返回所有左叶子之和。 + *@Version: 1.0 + */ +public class T10_SumOfLeftLeaves { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3, 9, 20, null, null, 15, 7)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(sumOfLeftLeaves(root)); + + } + + @Test + public void test1() { + ArrayList list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(sumOfLeftLeaves(root)); + + } + + /** + * 思路:左叶子节点(叶子节点:左右没有节点,且是左节点) 迭代法中序遍历不处理中节点即可 + * 速度击败10.5%,内存击败80.37% + * @param root + * @return + */ + public int sumOfLeftLeaves(TreeNode root) { + int sum = 0; + if (root == null) { + return sum; + } + Stack stack = new Stack<>(); + TreeNode cur = root; + TreeNode jin = root;//不让他加入的节点 + while (cur != null || !stack.isEmpty()) { + if (cur != null) { + if (cur != jin && cur.left == null && cur.right == null) { + sum += cur.val; + } + stack.push(cur); + cur = cur.left; + } else { + cur = stack.pop(); + cur = cur.right; + jin = cur;//刚刚进入右节点则不能让他加 + } + } + return sum; + + } + + /** + * 思路:左叶子节点(叶子节点:左右没有节点,且是左节点) 递归法中序遍历不处理中节点即可 + * 速度击败100%,内存击败84.12% + * @param root + * @return + */ + public int sumOfLeftLeaves1(TreeNode root) { + if (root == null) { + return 0; + } + return travelLeftLeaves1(root, null); + } + + /** + * 遍历,看看node是不是pre的左节点来判断 + * @param node + * @param pre + * @return + */ + public int travelLeftLeaves1(TreeNode node, TreeNode pre) { + if (node == null) {//节点为null不用继续遍历 + return 0; + } + if (pre != null && pre.left == node && node.left == null && node.right == null) { + return node.val;//左叶子节点,加上这个值 + } + return travelLeftLeaves1(node.left, node) + travelLeftLeaves1(node.right, node); + } + + + /** + * 官方BFS:之前都是dfs这里试试BFS法,核心在于不是处理当前节点,而是在父节点判断左节点是不是叶子结点 + * 速度击败100%,内存击败47.69% + * @param root + * @return + */ + public int sumOfLeftLeaves3(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList(); + queue.offer(root); + int ans = 0; + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node.left != null) { + if (isLeafNode(node.left)) { + ans += node.left.val; + } else { + queue.offer(node.left); + } + } + if (node.right != null) { + if (!isLeafNode(node.right)) { + queue.offer(node.right); + } + } + } + return ans; + } + + public boolean isLeafNode(TreeNode node) { + return node.left == null && node.right == null; + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/T11_FindBottomLeftValue.java b/Leecode/src/main/java/com/markilue/leecode/tree/T11_FindBottomLeftValue.java new file mode 100644 index 0000000..87e1203 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T11_FindBottomLeftValue.java @@ -0,0 +1,75 @@ +package com.markilue.leecode.tree; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree + *@Author: dingjiawen + *@CreateTime: 2023-01-10 10:53 + *@Description: + * TODO 力扣513题 找左下角的值: + * 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 + * 假设二叉树中至少有一个节点。 + *@Version: 1.0 + */ +public class T11_FindBottomLeftValue { + + /** + * 思路:不仅要最底层,还要最左;事实上层序遍历就是一个很好地选择 + * 速度击败13.85%,内存击败45.94% 2ms + * @param root + * @return + */ + public int findBottomLeftValue(TreeNode root) { + int value = 0; + if (root == null) { + return value; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (i == 0) { + //每层的最左边 + value = node.val; + } + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + } + return value; + + } + + + /** + * 思路:不仅要最底层,还要最左;事实上层序遍历就是一个很好地选择,尝试递归DFS + * 速度击败100%,内存击败65.25% 0ms + * @param root + * @return + */ + public int findBottomLeftValue1(TreeNode root) { + find(root,0); + return result.get(result.size()-1); + + } + + //记录每层最右 + List result=new ArrayList<>(); + public void find(TreeNode node, int deep) { + if (node == null) { + return; + } + if(result.size()==deep){ + result.add(node.val); + } + find(node.left,deep+1); + find(node.right,deep+1); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/HasPathSum.java b/Leecode/src/main/java/com/markilue/leecode/tree/T12_HasPathSum.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/HasPathSum.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T12_HasPathSum.java index 937151e..19b6420 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/HasPathSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T12_HasPathSum.java @@ -18,7 +18,7 @@ import java.util.Stack; * 叶子节点 是指没有子节点的节点。 * @Version: 1.0 */ -public class HasPathSum { +public class T12_HasPathSum { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/PathSum.java b/Leecode/src/main/java/com/markilue/leecode/tree/T13_PathSum.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/PathSum.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T13_PathSum.java index f58d50a..9803ef2 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/PathSum.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T13_PathSum.java @@ -14,7 +14,7 @@ import java.util.*; * 给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 * @Version: 1.0 */ -public class PathSum { +public class T13_PathSum { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/BuildTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/T14_0_BuildTree.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/BuildTree.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T14_0_BuildTree.java index 8f9c5e5..73f668e 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/BuildTree.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T14_0_BuildTree.java @@ -13,7 +13,7 @@ import java.util.*; * 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗二叉树。 * @Version: 1.0 */ -public class BuildTree { +public class T14_0_BuildTree { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/BuildTreeII.java b/Leecode/src/main/java/com/markilue/leecode/tree/T14_1_BuildTreeII.java similarity index 97% rename from Leecode/src/main/java/com/markilue/leecode/tree/BuildTreeII.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T14_1_BuildTreeII.java index 166a003..137b50b 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/BuildTreeII.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T14_1_BuildTreeII.java @@ -13,12 +13,12 @@ import java.util.*; * 给定两个整数数组preorder 和 inorder,其中preorder 是二叉树的先序遍历, inorder是同一棵树的中序遍历,请构造二叉树并返回其根节点。 * @Version: 1.0 */ -public class BuildTreeII { +public class T14_1_BuildTreeII { @Test public void test() { int[] preorder = {3, 9, 20, 15, 7}, inorder = {9, 3, 15, 20, 7}; - TreeNode node = buildTree(preorder, inorder); + TreeNode node = buildTree1(preorder, inorder); System.out.println(levelOrder(node)); } diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/T14_2_ConstructMaximumBinaryTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/T14_2_ConstructMaximumBinaryTree.java new file mode 100644 index 0000000..5d6cd77 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T14_2_ConstructMaximumBinaryTree.java @@ -0,0 +1,154 @@ +package com.markilue.leecode.tree; + +import com.sun.scenario.effect.Brightpass; + +import java.util.*; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree + *@Author: dingjiawen + *@CreateTime: 2023-01-11 11:14 + *@Description: + * TODO 力扣654 最大二叉树: + * 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: + * 创建一个根节点,其值为 nums 中的最大值。 + * 递归地在最大值 左边 的 子数组前缀上 构建左子树。 + * 递归地在最大值 右边 的 子数组后缀上 构建右子树。 + * 返回 nums 构建的 最大二叉树 。 + *@Version: 1.0 + */ +public class T14_2_ConstructMaximumBinaryTree { + + /** + * 思路:这题实际上是给了一个中序遍历,然后root的值由max确定 + * 速度击败81.34%,内存击败68.74% 2ms 时间复杂度O(n^2) + * @param nums + * @return + */ + public TreeNode constructMaximumBinaryTree(int[] nums) { + return constructTree(nums, 0, nums.length); + + } + + public TreeNode constructTree(int[] nums, int left, int right) { + if (left == right) { + return null; + } + int maxIndex = findMaxIndex(nums, left, right); + TreeNode root = new TreeNode(nums[maxIndex]); + root.left = constructTree(nums, left, maxIndex); + root.right = constructTree(nums, maxIndex + 1, right); + return root; + } + + public int findMaxIndex(int[] nums, int left, int right) { + int maxIndex = left; + for (int i = left; i < right; i++) { + if (nums[maxIndex] < nums[i]) { + maxIndex = i; + } + } + return maxIndex; + } + + + /** + * 官方单调栈解法: + * 时间复杂度O(N) + * 速度击败5.7%,内存击败82.36% 20ms + * @param nums + * @return + */ + public TreeNode constructMaximumBinaryTree1(int[] nums) { + int n = nums.length; + List stack = new ArrayList(); + TreeNode[] tree = new TreeNode[n]; + for (int i = 0; i < n; ++i) { + tree[i] = new TreeNode(nums[i]); + while (!stack.isEmpty() && nums[i] > nums[stack.get(stack.size() - 1)]) { + tree[i].left = tree[stack.get(stack.size() - 1)]; + stack.remove(stack.size() - 1); + } + if (!stack.isEmpty()) { + tree[stack.get(stack.size() - 1)].right = tree[i]; + } + stack.add(i); + } + return tree[stack.get(0)]; + } + + + /** + * 官方单调栈容易理解版: + * TODO + * 核心在于维护一个单调栈,这个栈单调递增; + * 1)如果来的数大于栈里面的数,又因为来的数的索引i一定大于栈的数的索引;所以栈里面的数的右边界应该是来的这个数(所以栈里数应该是来数的左子树);对应于while内容 + * 2)如果来的数小于栈里面的数,又因为来的数的索引i一定大于栈的数的索引;所以来的这个数的左边界应该是栈里面这个数(所以是栈里数的左子树);对应于if内容 + * @param nums + * @return + */ + public TreeNode constructMaximumBinaryTree2(int[] nums) { + int n = nums.length; + Deque stack = new ArrayDeque(); + int[] left = new int[n]; + int[] right = new int[n]; + Arrays.fill(left, -1); + Arrays.fill(right, -1); + TreeNode[] tree = new TreeNode[n]; + for (int i = 0; i < n; ++i) { + tree[i] = new TreeNode(nums[i]); + while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) { + right[stack.pop()] = i; + } + if (!stack.isEmpty()) { + left[i] = stack.peek(); + } + stack.push(i); + } + + TreeNode root = null; + for (int i = 0; i < n; ++i) { + if (left[i] == -1 && right[i] == -1) { + //最大的那个数,一定一直在栈里面所以left和right一定没有赴过值 + root = tree[i]; + } else if (right[i] == -1 || (left[i] != -1 && nums[left[i]] < nums[right[i]])) { + tree[left[i]].right = tree[i];//这个数的左边界的右子树应该是这个数 + } else { + tree[right[i]].left = tree[i]; + } + } + return root; + } + + /** + * 官方最快:与本人思路一致 + * @param nums + * @return + */ + public TreeNode constructMaximumBinaryTree3(int[] nums) { + return constructTree1(nums, 0, nums.length); + } + + public TreeNode constructTree1(int[] nums, int leftIndex, int rightIndex) { + if (rightIndex - leftIndex < 1) { + return null; + } + if (rightIndex - leftIndex == 1) { + return new TreeNode(nums[leftIndex]); + } + int maxIndex = leftIndex; + int maxVal = nums[maxIndex]; + for (int i = leftIndex + 1; i < rightIndex; i++) { + if (nums[i] > maxVal) { + maxVal = nums[i]; + maxIndex = i; + } + } + TreeNode root = new TreeNode(maxVal); + root.left = constructTree1(nums, leftIndex, maxIndex); + root.right = constructTree1(nums, maxIndex + 1, rightIndex); + return root; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/MergeTrees.java b/Leecode/src/main/java/com/markilue/leecode/tree/T15_MergeTrees.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/MergeTrees.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T15_MergeTrees.java index 8230948..34a201c 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/MergeTrees.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T15_MergeTrees.java @@ -18,7 +18,7 @@ import java.util.*; * 3)返回合并后的二叉树。 * @Version: 1.0 */ -public class MergeTrees { +public class T15_MergeTrees { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/SearchBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T16_SearchBST.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/tree/SearchBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T16_SearchBST.java index 11fa7cb..dcb3632 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/SearchBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T16_SearchBST.java @@ -12,7 +12,7 @@ import org.junit.Test; * 你需要在 BST 中找到节点值等于val的节点。 返回以该节点为根的子树。 如果节点不存在,则返回null。 * @Version: 1.0 */ -public class SearchBST { +public class T16_SearchBST { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/IsValidBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T17_IsValidBST.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/IsValidBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T17_IsValidBST.java index 352330b..7d87ade 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/IsValidBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T17_IsValidBST.java @@ -17,7 +17,7 @@ import java.util.Stack; * 3.所有左子树和右子树自身必须也是二叉搜索树。 * @Version: 1.0 */ -public class IsValidBST { +public class T17_IsValidBST { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/GetMinimumDifference.java b/Leecode/src/main/java/com/markilue/leecode/tree/T18_GetMinimumDifference.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/GetMinimumDifference.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T18_GetMinimumDifference.java index eec3896..cabf5ec 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/GetMinimumDifference.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T18_GetMinimumDifference.java @@ -14,7 +14,7 @@ import java.util.Stack; * 差值是一个正数,其数值等于两值之差的绝对值。 * @Version: 1.0 */ -public class GetMinimumDifference { +public class T18_GetMinimumDifference { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/FindMode.java b/Leecode/src/main/java/com/markilue/leecode/tree/T19_FindMode.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/FindMode.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T19_FindMode.java index 714598b..e50eec0 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/FindMode.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T19_FindMode.java @@ -19,7 +19,7 @@ import java.util.*; * 左子树和右子树都是二叉搜索树 * @Version: 1.0 */ -public class FindMode { +public class T19_FindMode { @Test diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestor.java b/Leecode/src/main/java/com/markilue/leecode/tree/T20_LowestCommonAncestor.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestor.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T20_LowestCommonAncestor.java index 8847c3c..4b54c55 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestor.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T20_LowestCommonAncestor.java @@ -18,7 +18,7 @@ import java.util.Set; * “对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” * @Version: 1.0 */ -public class LowestCommonAncestor { +public class T20_LowestCommonAncestor { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestorII.java b/Leecode/src/main/java/com/markilue/leecode/tree/T21_LowestCommonAncestorII.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestorII.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T21_LowestCommonAncestorII.java index b6be9fb..0051edb 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/LowestCommonAncestorII.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T21_LowestCommonAncestorII.java @@ -13,7 +13,7 @@ import org.junit.Test; * “对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” * @Version: 1.0 */ -public class LowestCommonAncestorII { +public class T21_LowestCommonAncestorII { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/InsertIntoBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T22_InsertIntoBST.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/InsertIntoBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T22_InsertIntoBST.java index 25c4384..f7da432 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/InsertIntoBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T22_InsertIntoBST.java @@ -17,7 +17,7 @@ import java.util.List; * * @Version: 1.0 */ -public class InsertIntoBST { +public class T22_InsertIntoBST { @Test public void test(){ diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNode.java b/Leecode/src/main/java/com/markilue/leecode/tree/T23_DeleteNode.java similarity index 95% rename from Leecode/src/main/java/com/markilue/leecode/tree/DeleteNode.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T23_DeleteNode.java index c347f45..5e6a2ee 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/DeleteNode.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T23_DeleteNode.java @@ -4,12 +4,12 @@ package com.markilue.leecode.tree; import java.util.List; -import static com.markilue.leecode.tree.InorderTraversal.inorderTraversal1; +import static com.markilue.leecode.tree.T03_InorderTraversal.inorderTraversal1; /** * 删除二叉排序树的某个节点 */ -public class DeleteNode { +public class T23_DeleteNode { public static void main(String[] args) { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/TrimBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T24_TrimBST.java similarity index 96% rename from Leecode/src/main/java/com/markilue/leecode/tree/TrimBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T24_TrimBST.java index b9c24e1..61530e6 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/TrimBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T24_TrimBST.java @@ -2,11 +2,9 @@ package com.markilue.leecode.tree; import org.junit.Test; -import java.util.HashMap; import java.util.List; -import java.util.Stack; -import static com.markilue.leecode.tree.InorderTraversal.inorderTraversal1; +import static com.markilue.leecode.tree.T03_InorderTraversal.inorderTraversal1; /** * @BelongsProject: Leecode @@ -19,7 +17,7 @@ import static com.markilue.leecode.tree.InorderTraversal.inorderTraversal1; * 所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。 * @Version: 1.0 */ -public class TrimBST { +public class T24_TrimBST { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/SortedArrayToBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T25_SortedArrayToBST.java similarity index 98% rename from Leecode/src/main/java/com/markilue/leecode/tree/SortedArrayToBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T25_SortedArrayToBST.java index b9eaa5b..b781ae6 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/SortedArrayToBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T25_SortedArrayToBST.java @@ -16,7 +16,7 @@ import java.util.Stack; * 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。 * @Version: 1.0 */ -public class SortedArrayToBST { +public class T25_SortedArrayToBST { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/ConvertBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/T26_ConvertBST.java similarity index 99% rename from Leecode/src/main/java/com/markilue/leecode/tree/ConvertBST.java rename to Leecode/src/main/java/com/markilue/leecode/tree/T26_ConvertBST.java index faece70..8c5e6d9 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/ConvertBST.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/T26_ConvertBST.java @@ -20,7 +20,7 @@ import java.util.Stack; * 左右子树也必须是二叉搜索树。 * @Version: 1.0 */ -public class ConvertBST { +public class T26_ConvertBST { @Test public void test() { diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/TreeNode.java b/Leecode/src/main/java/com/markilue/leecode/tree/TreeNode.java index aad506b..718af22 100644 --- a/Leecode/src/main/java/com/markilue/leecode/tree/TreeNode.java +++ b/Leecode/src/main/java/com/markilue/leecode/tree/TreeNode.java @@ -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 { '}'; } + + } diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/TreeUtils.java b/Leecode/src/main/java/com/markilue/leecode/tree/TreeUtils.java new file mode 100644 index 0000000..25c3275 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/TreeUtils.java @@ -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 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 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(); + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/NNode.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/NNode.java new file mode 100644 index 0000000..f820b4a --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/NNode.java @@ -0,0 +1,27 @@ +package com.markilue.leecode.tree.second; + +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 10:55 + *@Description: TODO N叉树节点 + *@Version: 1.0 + */ +public class NNode { + public int val; + public List children; + + public NNode() {} + + public NNode(int _val) { + val = _val; + } + + public NNode(int _val, List _children) { + val = _val; + children = _children; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/Node.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/Node.java new file mode 100644 index 0000000..295c206 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/Node.java @@ -0,0 +1,30 @@ +package com.markilue.leecode.tree.second; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 11:36 + *@Description: TODO 完美二叉树 + *@Version: 1.0 + */ +public class Node { + + public int val; + public Node left; + public Node right; + public Node next; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, Node _left, Node _right, Node _next) { + val = _val; + left = _left; + right = _right; + next = _next; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_0_PreOrderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_0_PreOrderTraversal.java new file mode 100644 index 0000000..c612464 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_0_PreOrderTraversal.java @@ -0,0 +1,178 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-04 12:40 + *@Description: + * TODO 二刷leecode 144 二叉树的前序: + * 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 + *@Version: 1.0 + */ +public class T01_0_PreOrderTraversal { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,1,2,5,6,7,8,10)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(preorderTraversal2(root)); + + } + + /** + * 思路:递归法 + * 速度击败100%,内存击败86.86% + */ + List result = new ArrayList<>();//记录答案 + public List preorderTraversal(TreeNode root) { + if (root == null) { + return result; + } + result.add(root.val); + preorderTraversal(root.left); + preorderTraversal(root.right); + return result; + } + + + /** + * 思路:stack迭代法 + * 速度击败100%,内存击败75.97% + * @param root + * @return + */ + public List preorderTraversal1(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + Stack stack = new Stack<>(); + stack.push(root); + + while(!stack.isEmpty()){ + TreeNode node = stack.pop(); + result.add(node.val); + if(node.right!=null){ + stack.push(node.right); + } + if(node.left!=null){ + stack.push(node.left); + } + } + return result; + } + + + /** + * 官方stack迭代法 + * @param root + * @return + */ + public List preorderTraversal3(TreeNode root) { + List res = new ArrayList(); + if (root == null) { + return res; + } + + Deque stack = new LinkedList(); + TreeNode node = root; + while (!stack.isEmpty() || node != null) { + while (node != null) { + res.add(node.val); + stack.push(node); + node = node.left; + } + node = stack.pop(); + node = node.right; + } + return res; + } + + + + + /** + * 思路:Morris遍历 + * 速度击败100%,内存击败75.97% + * @param root + * @return + */ + public List preorderTraversal2(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + while (root!=null){ + //找到上一个连接root的点(左子节点的最右节点),然后放心吧root左移 + if(root.left!=null){ + TreeNode temp=root.left; + while (temp.right!=root&&temp.right!=null){ + temp=temp.right; + } + //判断是从什么条件出来的 + if(temp.right==null){ + //第一次到这,将这与root连接 + temp.right=root; + result.add(root.val);//将其加入后就右移 + //放心将root右移 + root=root.left; + } else if(temp.right==root){ + //第二次来这,之前的都已经遍历完了 + root=root.right; + temp.right=null;//将right置空,防止下次还遍历 + } + }else { + //左边遍历完了,将它右移 + result.add(root.val); + root=root.right; + } + } + + return result; + } + + /** + * 官方morris写法 + * @param root + * @return + */ + public List preorderTraversal4(TreeNode root) { + List res = new ArrayList(); + if (root == null) { + return res; + } + + TreeNode p1 = root, p2 = null; + + while (p1 != null) { + p2 = p1.left; + if (p2 != null) { + while (p2.right != null && p2.right != p1) { + p2 = p2.right; + } + if (p2.right == null) { + res.add(p1.val); + p2.right = p1; + p1 = p1.left; + continue;//这里continue以后,后面就可以把p1 = p1.right; + } else { + p2.right = null; + } + } else { + res.add(p1.val); + } + p1 = p1.right; + } + return res; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_1_Preorder.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_1_Preorder.java new file mode 100644 index 0000000..5852e4a --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T01_1_Preorder.java @@ -0,0 +1,71 @@ +package com.markilue.leecode.tree.second; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-08 11:36 + *@Description: + * TODO 力扣589题 N叉树的前序遍历: + * 给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。 + * n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。 + *@Version: 1.0 + */ +public class T01_1_Preorder { + + /** + * 思路:与前序遍历一致,只是从遍历left和right变成了children,迭代法 + * 速度击败17.14%,内存击败39.29% + * @param root + * @return + */ + public List preorder(NNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + Stack stack = new Stack<>(); + stack.push(root); + while (!stack.isEmpty()){ + NNode node = stack.pop(); + result.add(node.val); + for (int i = node.children.size()-1; i >=0 ; i--) { + NNode child = node.children.get(i); + if(child!=null){ + stack.push(child); + } + } + } + return result; + + } + + + /** + * 思路:递归法 + * 速度击败100%,内存击败17.14% + * @param root + * @return + */ + List result = new ArrayList<>(); + public List preorder1(NNode root) { + travel(root); + return result; + + } + + public void travel(NNode root) { + if(root==null){ + return; + } + result.add(root.val); + for (NNode child : root.children) { + travel(child); + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_0_PostOrderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_0_PostOrderTraversal.java new file mode 100644 index 0000000..a254d11 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_0_PostOrderTraversal.java @@ -0,0 +1,220 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-05 10:25 + *@Description: + * TODO 二刷leecode145题 二叉树的后序遍历: + * 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 + *@Version: 1.0 + */ +public class T02_0_PostOrderTraversal { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,1,2,5,6,7,8,10)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(postorderTraversal3(root)); + + } + + /** + * 思路:递归法 + * @param root + * @return + */ + List result; + public List postorderTraversal(TreeNode root) { + result=new ArrayList<>(); + postTraversal(root); + return result; + + } + public void postTraversal(TreeNode root) { + if(root==null){ + return; + } + + postTraversal(root.left); + postTraversal(root.right); + result.add(root.val); + + } + + + /** + * 思路:stack迭代法:中右左——>反序:左右中 + * @param root + * @return + */ + public List postorderTraversal1(TreeNode root) { + List result = new ArrayList<>(); + + if(root==null){ + return result; + } + + Stack stack = new Stack<>(); + stack.push(root); + + while(!stack.isEmpty()){ + + TreeNode node = stack.pop(); + result.add(node.val); + if(node.left!=null){ + stack.push(node.left); + } + if(node.right!=null){ + stack.push(node.right); + } + + } + + int size = result.size(); + List result1 = new ArrayList<>(); + while (size>0){ + result1.add(result.get(--size)); + } + return result1; + + } + + + /** + * 思路:优化stack迭代法:改变树结构 + * @param root + * @return + */ + public List postorderTraversal2(TreeNode root) { + List result = new ArrayList<>(); + + if(root==null){ + return result; + } + + Stack stack = new Stack<>(); + TreeNode cur=root; + + while(cur!=null||!stack.isEmpty()){ + + if(cur!=null){ + stack.push(cur); + cur=cur.left; + }else { + TreeNode node = stack.peek(); + if(node.right!=null){ + cur=node.right; + node.right=null;//核心是把right置空,防止下次在遍历到,但是会改变原本的树结构 + }else { + //右边没有了,安心处理node + result.add(node.val); + stack.pop(); + } + } + + } + + + return result; + + } + + /** + * 官方不改变树结构的stack法,使用一个pre记录之前的node,如果之前的node=root.right就说明右边的已经遍历完了 + * @param root + * @return + */ + public List postorderTraversal4(TreeNode root) { + List res = new ArrayList(); + if (root == null) { + return res; + } + + Deque stack = new LinkedList(); + TreeNode prev = null; + while (root != null || !stack.isEmpty()) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + if (root.right == null || root.right == prev) { + //核心在于prev指针记录之前是否遍历他的右边 + res.add(root.val); + prev = root; + root = null; + } else { + stack.push(root); + root = root.right; + } + } + return res; + } + + + + + /** + * 思路:Morris遍历:在不改变结构的情况下直接的Morris遍历是不行的,还是得中右左然后反向 + * @param root + * @return + */ + public List postorderTraversal3(TreeNode root) { + List result = new ArrayList<>(); + + if(root==null){ + return result; + } + + TreeNode cur=root; + + while (cur!=null){ + TreeNode node=cur.right; + + if(node!=null){ + //存在右节点,寻找右子节点的最左节点 + while (node.left!=cur&&node.left!=null){ + node=node.left; + } + //判断是从啥条件出来的 + if(node.left==null){ + //第一次遍历到这 + node.left=cur;//将其连接 + result.add(cur.val); + //放心将节点右移 + cur=cur.right; + }else if(node.left==cur){ + //第二次遍历到这 + cur=cur.left; + node.left=null; + } + }else { + //右边为空了,遍历左节点去 + result.add(cur.val); + cur=cur.left; + } + } + + + int size = result.size(); + List result1 = new ArrayList<>(); + while (size>0){ + result1.add(result.get(--size)); + } + return result1; + + + + + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_1_Postorder.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_1_Postorder.java new file mode 100644 index 0000000..5a4182b --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T02_1_Postorder.java @@ -0,0 +1,76 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-05 10:25 + *@Description: + * TODO 二刷leecode145题 二叉树的后序遍历: + * 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 + *@Version: 1.0 + */ +public class T02_1_Postorder { + + + /** + * 思路:递归法 + * 速度击败100%,内存击败49.35% + * @param root + * @return + */ + List result; + public List postorderTraversal(NNode root) { + result=new ArrayList<>(); + postTraversal(root); + return result; + + } + public void postTraversal(NNode root) { + if(root==null){ + return; + } + for (NNode child : root.children) { + postTraversal(child); + } + result.add(root.val); + + } + + + /** + * 思路:stack迭代法:中后前——>反序前后中 + * 速度击败26.37% 内存击败50.42% + * @param root + * @return + */ + public List postorderTraversal1(NNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + Stack stack = new Stack<>(); + stack.push(root); + while (!stack.isEmpty()){ + NNode node = stack.pop(); + result.add(node.val); + for (NNode child : node.children) { + stack.push(child); + } + } + Collections.reverse(result); + return result; + + + } + + + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T03_InorderTraversal.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T03_InorderTraversal.java new file mode 100644 index 0000000..91c1e87 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T03_InorderTraversal.java @@ -0,0 +1,119 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import com.markilue.leecode.tree.TreeUtils; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-05 11:35 + *@Description: + * TODO 二刷力扣94题 二叉树的中序遍历: + * 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 + *@Version: 1.0 + */ +public class T03_InorderTraversal { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,1,2,5,6,7,8,10)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(inorderTraversal2(root)); + + } + + /** + * 思路:递归法 + * @param root + * @return + */ + List result; + public List inorderTraversal(TreeNode root) { + result=new ArrayList<>(); + midTraversal(root); + return result; + } + public void midTraversal(TreeNode root) { + if(root==null){ + return; + } + midTraversal(root.left); + result.add(root.val); + midTraversal(root.right); + } + + + /** + * 思路:stack非递归法 + * @param root + * @return + */ + public List inorderTraversal1(TreeNode root) { + ArrayList result = new ArrayList<>(); + if(root==null){ + return result; + } + Stack stack = new Stack<>(); + TreeNode cur=root; + + while (cur!=null||!stack.isEmpty()){ + if(cur!=null){ + stack.push(cur); + cur=cur.left; + }else { + TreeNode node = stack.pop(); + result.add(node.val); + cur=node.right;//看看他的右边 + } + } + + return result; + } + + + /** + * 思路:Morris遍历 + * @param root + * @return + */ + public List inorderTraversal2(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + TreeNode cur=root; + while (cur!=null){ + TreeNode node=cur.left; + if(node!=null){ + while(node.right!=cur&&node.right!=null){ + node=node.right; + } + + if(node.right==null){ + node.right=cur; + cur=cur.left; + }else { + //左边节点遍历完了 + node.right=null; + result.add(cur.val); + cur=cur.right; + } + }else { + result.add(cur.val);//左边节点空了 + cur=cur.right; + } + } + + + return result; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_0_LevelOrder.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_0_LevelOrder.java new file mode 100644 index 0000000..74c3be7 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_0_LevelOrder.java @@ -0,0 +1,78 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-05 11:58 + *@Description: + * TODO 二刷力扣102题 二叉树的层序遍历: + * 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 + *@Version: 1.0 + */ +public class T04_0_LevelOrder { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,9,20,null,null,15,7)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(levelOrder(root)); + + } + + /** + * 思路:队列法 + * @param root + * @return + */ + public List> levelOrder(TreeNode root) { + List> result = new ArrayList<>(); + if(root==null){ + return result; + } + + Deque queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + int size = queue.size(); + List cur = new ArrayList<>();//每层result + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + cur.add(node.val); + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + result.add(cur); + } + return result; + + } + + /** + * 官方最快:递归法,虽然是深度优先,但是他通过ans.get(depth)获取到第deep层的数列 + */ + List> ans = new ArrayList<>(); + public List> levelOrder2(TreeNode root) { + dfs(root, 0); + return ans; + } + + public void dfs(TreeNode node, int depth) { + if (node == null) { + return; + } + if (ans.size() == depth) { + ans.add(new ArrayList<>()); + } + ans.get(depth).add(node.val); + dfs(node.left, depth + 1); + dfs(node.right, depth + 1); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_1_LevelOrderBottom.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_1_LevelOrderBottom.java new file mode 100644 index 0000000..85bd410 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_1_LevelOrderBottom.java @@ -0,0 +1,118 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-05 12:15 + *@Description: + * TODO 力扣107题 二叉树的层序遍历 II: + * 给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 + * (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) + *@Version: 1.0 + */ +public class T04_1_LevelOrderBottom { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,9,20,null,null,15,7)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(levelOrderBottom1(root)); + + } + + /** + * 思路:queue先层序后反向 + * 速度击败91.28%,内存击败6.48% + * @param root + * @return + */ + public List> levelOrderBottom(TreeNode root) { + List> result = new ArrayList<>(); + if(root==null){ + return result; + } + + Deque queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + ArrayList cur = new ArrayList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + cur.add(node.val); + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + result.add(cur); + } + Collections.reverse(result); + return result; + } + + + /** + * 思路:dfs深度优先递归 + * @param root + * @return + */ + List> result = new ArrayList<>(); + int maxDepth=0; + public List> levelOrderBottom1(TreeNode root) { + + dfs(root,0); + Collections.reverse(result); + return result; + } + public void dfs(TreeNode root,int deep) { + if(root==null){ + return; + } + if(result.size()==deep){ + result.add(new ArrayList<>()); + } + dfs(root.left,deep+1); + dfs(root.right,deep+1); + result.get(deep).add(root.val); + } + + + /** + * 官方直接加在头部法,避免反向 + * @param root + * @return + */ + public List> levelOrderBottom2(TreeNode root) { + List> levelOrder = new LinkedList>(); + if (root == null) { + return levelOrder; + } + Queue queue = new LinkedList(); + queue.offer(root); + while (!queue.isEmpty()) { + List level = new ArrayList(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + level.add(node.val); + TreeNode left = node.left, right = node.right; + if (left != null) { + queue.offer(left); + } + if (right != null) { + queue.offer(right); + } + } + levelOrder.add(0, level); + } + return levelOrder; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_2_RightSideView.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_2_RightSideView.java new file mode 100644 index 0000000..b44c5f1 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_2_RightSideView.java @@ -0,0 +1,77 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 09:55 + *@Description: + * TODO leecode199题 二叉树的右视图: + * 给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 + *@Version: 1.0 + */ +public class T04_2_RightSideView { + + /** + * 思路:就是返回他所有最右侧的节点,层序遍历确实可以解决,返回每一层最后的节点 + * 速度击败82.5%,内存击败35.82% + * @param root + * @return + */ + public List rightSideView(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if(i==size-1){//每层的最右一个,就是最右节点 + result.add(node.val); + } + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + } + + return result; + } + + + /** + * 思路:尝试dfs实现层序遍历,返回每一层最后的节点 + * 速度击败100%,内存击败27.1% + * @param root + * @return + */ + List result = new ArrayList<>(); + public List rightSideView1(TreeNode root) { + + + dfs(root,0); + return result; + } + //核心在于:如何判断他是一层最后一个,官方思路:如果先遍历右子树,那么就是那一层第一个 + public void dfs(TreeNode node,int depth){ + if(node==null){ + return; + } + if(result.size()==depth){ + result.add(node.val); + } + dfs(node.right,depth+1); + dfs(node.left,depth+1); + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_3_averageOfLevels.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_3_averageOfLevels.java new file mode 100644 index 0000000..ad2cc01 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_3_averageOfLevels.java @@ -0,0 +1,130 @@ +package com.markilue.leecode.tree.second; + +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.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 10:23 + *@Description: + * TODO leecode637题 二叉树的层平均值: + * 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 + *@Version: 1.0 + */ +public class T04_3_averageOfLevels { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,9,20,null,null,15,7)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(averageOfLevels1(root)); + + } + + /** + * 思路:BFS层平均值,求出总和之后除以个数;所以需要知道本层所有节点 + * 速度击败93.66%,内存击败52.84% 2ms + * @param root + * @return + */ + public List averageOfLevels(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()){ + int size = queue.size(); + Double sum=0d; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum+=node.val; + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + result.add(sum/size); + } + + return result; + + } + + + /** + * 思路:尝试dfs,获取每层总和和每个节点个数 + * 速度击败93.66%,内存击败21.8% 2ms + * @param root + * @return + */ + List result = new ArrayList<>(); + List depthList = new ArrayList<>();//存储每层个数 + public List averageOfLevels1(TreeNode root) { + + dfs(root,0); + + for (int i = 0; i < result.size(); i++) { + result.set(i,result.get(i)/depthList.get(i));//求平均值 + } + + return result; + + } + public void dfs(TreeNode node,int depth) { + if(node==null){ + return; + } + if(result.size()==depth){ + result.add(0d); + } + if(depthList.size()==depth){ + depthList.add(0); + } + dfs(node.left,depth+1); + dfs(node.right,depth+1); + result.set(depth,result.get(depth)+node.val); + depthList.set(depth,depthList.get(depth)+1); + + } + + + /** + * 官方最快:本质上和本人的dfs无差异,比本人少设一次0值 + * 速度击败100%,内存击败26.25% 1ms + * @param root + * @return + */ + public List averageOfLevels2(TreeNode root) { + List counts = new ArrayList<>(); + List sum = new ArrayList<>(); + dfs2(root, 0, counts, sum); + List result = new ArrayList<>(); + for (int i = 0; i < counts.size(); i++) { + result.add(sum.get(i) / counts.get(i)); + } + return result; + } + + private void dfs2(TreeNode root, int level,List counts, List sum) { + if (root == null) { + return; + } + + if (level < counts.size()) { + counts.set(level, counts.get(level) + 1); + sum.set(level, sum.get(level) + root.val); + } else { + counts.add(1); + sum.add(1.0 * root.val); + } + + dfs2(root.left, level + 1, counts, sum); + dfs2(root.right, level +1, counts, sum); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_4_LevelOrder.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_4_LevelOrder.java new file mode 100644 index 0000000..450b6f4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_4_LevelOrder.java @@ -0,0 +1,79 @@ +package com.markilue.leecode.tree.second; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 10:54 + *@Description: + * TODO 力扣429题 N 叉树的层序遍历: + * 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。 + * 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。 + *@Version: 1.0 + */ +public class T04_4_LevelOrder { + + /** + * 思路:N叉树的层序遍历本质上和二叉树无异BFS层序遍历版 + * 速度击败84.2%,内存击败10.36% 3ms + * @param root + * @return + */ + public List> levelOrder(NNode root) { + List> result = new ArrayList<>(); + if(root==null){ + return result; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()){ + int size = queue.size(); + List cur = new ArrayList<>(); + for (int i = 0; i < size; i++) { + NNode NNode = queue.poll(); + cur.add(NNode.val); + for (NNode child : NNode.children) { + if(child!=null){ + queue.offer(child); + } + } + } + result.add(cur); + } + return result; + + } + + + /** + * 思路:N叉树的层序遍历 尝试dfs版 + * 速度击败100%,内存击败27.66% 0ms + * @param root + * @return + */ + List> result = new ArrayList<>(); + public List> levelOrder1(NNode root) { + + dfs(root,0); + return result; + + } + + public void dfs(NNode NNode, int depth) { + if(NNode ==null){ + return; + } + if(result.size()==depth){ + result.add(new ArrayList()); + } + for (NNode child : NNode.children) { + dfs(child,depth+1); + } + result.get(depth).add(NNode.val); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_5_LargestValues.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_5_LargestValues.java new file mode 100644 index 0000000..66ed190 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_5_LargestValues.java @@ -0,0 +1,83 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 11:18 + *@Description: + * TODO 力扣515题 在每个树行中找最大值: + * 给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。 + *@Version: 1.0 + */ +public class T04_5_LargestValues { + + + /** + * 思路:仍然是每行的最大值,层序遍历的题 BFS + * 速度击败84.54%,内存击败72.7% 2ms + * @param root + * @return + */ + public List largestValues(TreeNode root) { + List result = new ArrayList<>(); + if(root==null){ + return result; + } + + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()){ + int size = queue.size(); + int max=Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if(max result = new ArrayList<>(); + public List largestValues1(TreeNode root) { + + dfs(root,0); + return result; + + } + + public void dfs(TreeNode node,int depth){ + if(node==null){ + return; + } + if(result.size()==depth){ + result.add(node.val); + }else { + Integer now = result.get(depth); + if(now queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + int size = queue.size(); + Node pre=null; + for (int i = 0; i < size; i++) { + Node node = queue.poll(); + if(pre!=null)pre.next=node; + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + pre=node; + } + } + return root; + } + + + /** + * 思路:本质上还是每层之间彼此连接DFS,使用一个list记录每一层的上一个节点 + * 速度击败100%,内存击败57.66% 0ms + * @param root + * @return + */ + List preList=new ArrayList<>(); + public Node connect1(Node root) { + dfs(root,0); + return root; + } + + public void dfs(Node node,int depth) { + if(node==null){ + return; + } + if(preList.size()==depth){ + preList.add(node); + }else { + Node pre = preList.get(depth); + pre.next=node; + preList.set(depth,node); + } + dfs(node.left,depth+1); + dfs(node.right,depth+1); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_7_Connect.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_7_Connect.java new file mode 100644 index 0000000..bb976aa --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_7_Connect.java @@ -0,0 +1,132 @@ +package com.markilue.leecode.tree.second; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 11:31 + *@Description: + * TODO 力扣116题 填充每个节点的下一个右侧节点指针: + * 给定一个二叉树 二叉树定义如下: + * struct Node { + * int val; + * Node *left; + * Node *right; + * Node *next; + * } + * 填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 + * 初始状态下,所有 next 指针都被设置为 NULL。 + * 与T46似乎完全一致,所以代码完全没有改动 + *@Version: 1.0 + */ +public class T04_7_Connect { + + + /** + * 思路:本质上还是每层之间彼此连接BFS + * 速度击败66.57%,内存击败58.33% 1ms + * @param root + * @return + */ + public Node connect(Node root) { + if(root==null){ + return root; + } + + Deque queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()){ + int size = queue.size(); + Node pre=null; + for (int i = 0; i < size; i++) { + Node node = queue.poll(); + if(pre!=null)pre.next=node; + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + pre=node; + } + } + return root; + } + + + /** + * 思路:本质上还是每层之间彼此连接DFS,使用一个list记录每一层的上一个节点 + * 速度击败100%,内存击败67.82% 0ms + * @param root + * @return + */ + List preList=new ArrayList<>(); + public Node connect1(Node root) { + dfs(root,0); + return root; + } + + public void dfs(Node node,int depth) { + if(node==null){ + return; + } + if(preList.size()==depth){ + preList.add(node); + }else { + Node pre = preList.get(depth); + pre.next=node; + preList.set(depth,node); + } + dfs(node.left,depth+1); + dfs(node.right,depth+1); + } + + /** + * 评论区针对第一种queue思路的优化: + * TODO + * 上面运行效率并不是很高,这是因为我们把节点不同的入队然后再不停的出队, + * 其实可以不需要队列,每一行都可以看成一个链表比如第一行就是只有一个节点的链表, + * 第二行是只有两个节点的链表(假如根节点的左右两个子节点都不为空)…… + * 速度击败100%,内存击败65.71% + * @param root + * @return + */ + public Node connect2(Node root) { + if (root == null) + return root; + //cur我们可以把它看做是每一层的链表的头结点 + Node cur = root; + while (cur != null) { + //遍历当前层的时候,为了方便操作在下一 + //层前面添加一个哑结点(注意这里是访问 + //当前层的节点,然后把下一层的节点串起来) + Node dummy = new Node(0); + //pre表示访下一层节点的前一个节点 + Node pre = dummy; + //然后开始遍历当前层的链表 + while (cur != null) { + if (cur.left != null) { + //如果当前节点的左子节点不为空,就让pre节点 + //的next指向他,也就是把它串起来 + pre.next = cur.left; + //然后再更新pre + pre = pre.next; + } + //同理参照左子树 + if (cur.right != null) { + pre.next = cur.right; + pre = pre.next; + } + //继续访问这一行的下一个节点 + cur = cur.next; + } + //把下一层串联成一个链表之后,让他赋值给cur, + //后续继续循环,直到cur为空为止 + cur = dummy.next; + } + return root; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_8_MaxDepth.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_8_MaxDepth.java new file mode 100644 index 0000000..e525dde --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T04_8_MaxDepth.java @@ -0,0 +1,72 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.LinkedList; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-06 12:22 + *@Description: + * TODO 力扣104题 二叉树的最大深度: + * 给定一个二叉树,找出其最大深度。 + * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 + * 说明: 叶子节点是指没有子节点的节点。 + *@Version: 1.0 + */ +public class T04_8_MaxDepth { + + /** + * 思路:深度本质上就是要遍历到最底层 + * 速度击败19.79%,内存击败56.29% + * @param root + * @return + */ + public int maxDepth(TreeNode root) { + int depth=0; + if(root==null){ + return depth; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()){ + int size = queue.size(); + depth++; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if(node.left!=null)queue.offer(node.left); + if(node.right!=null)queue.offer(node.right); + } + } + return depth; + } + + /** + * 思路:深度本质上就是要遍历到最底层,DFS法 + * 速度击败100%,内存击败10.99% + * @param root + * @return + */ + int maxDepth=0; + public int maxDepth1(TreeNode root) { + dfs(root,1); + return maxDepth; + } + public void dfs(TreeNode node,int depth){ + if(node==null){ + return; + } + if(maxDepth list = new ArrayList<>(Arrays.asList(1,2,3,4,5)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(minDepth1(root)); + + } + + /** + * 思路: + * 1)如果有左右结点:分别计算左右节点深度取最小; + * 2)如果只有左或右节点,返回另一边深度 + * 3)如果两边有没有了,那么就是叶子结点,返回1 + * 速度击败34.68%,内存击败5.87% 9ms + * 慢在哪里?找到第一个叶子节点了,还是不能返回,得判断另一端 + * @param root + * @return + */ + public int minDepth(TreeNode root) { + if(root==null){ + return 0; + } + if(root.left==null&&root.right==null){ + return 1; + }else if(root.left==null){ + return minDepth(root.right)+1; + }else if(root.right==null){ + return minDepth(root.left)+1; + } + + return Math.min(minDepth(root.left),minDepth(root.right))+1; + + } + + /** + * 思路:尝试使用层序遍历优化,核心在于找到第一个叶子节点就返回 + * 速度击败100%,内存击败73.53% 0ms + * @param root + * @return + */ + public int minDepth1(TreeNode root) { + int depth=0; + if(root==null){ + return depth; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()){ + int size = queue.size(); + depth++; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if(node.left==null&&node.right==null){ + return depth; + } + if(node.left!=null){ + queue.offer(node.left); + } + if(node.right!=null){ + queue.offer(node.right); + } + } + } + + return depth; + + } + + + /** + * 思路:评论区对于需要一直找左右的优化,实际上没有优化 + * 速度击败16.1%,内存击败39.79% + * @param root + * @return + */ + public int minDepth2(TreeNode root) { + if(root==null){ + return 0; + } + if(root.left==null&&root.right==null){ + return 1; + } + //2.如果左孩子和由孩子其中一个为空,那么需要返回比较大的那个孩子的深度 + int m1 = minDepth2(root.left); + int m2 = minDepth2(root.right); + //这里其中一个节点为空,说明m1和m2有一个必然为0,所以可以返回m1 + m2 + 1; + if(root.left == null || root.right == null) return m1 + m2 + 1; + + //3.最后一种情况,也就是左右孩子都不为空,返回最小深度+1即可 + return Math.min(m1,m2) + 1; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T05_InvertTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T05_InvertTree.java new file mode 100644 index 0000000..60d7e79 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T05_InvertTree.java @@ -0,0 +1,67 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-08 11:04 + *@Description: + * TODO 翻转二叉树: + * 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 + *@Version: 1.0 + */ +public class T05_InvertTree { + + /** + * 思路:递归法 + * 速度击败100%,内存击败5.28% + * @param root + * @return + */ + public TreeNode invertTree(TreeNode root) { + if(root==null||(root.left==null&&root.right==null)){ + return root; + } + TreeNode temp=root.left; + root.left=invertTree(root.right); + root.right=invertTree(temp); + return root; + } + + + /** + * 思路:迭代法:前序遍历;层序遍历也可 + * 速度击败100%,内存击败5.28% + * @param root + * @return + */ + public TreeNode invertTree1(TreeNode root) { + if(root==null||(root.left==null&&root.right==null)){ + return root; + } + + Stack stack = new Stack<>(); + TreeNode cur=root; + while (cur!=null||!stack.isEmpty()){ + if(cur!=null){ + if(root.left!=null||root.right!=null){ + TreeNode temp=cur.left; + cur.left=cur.right; + cur.right=temp; + } + stack.push(cur); + cur=cur.left; + }else { + TreeNode node = stack.pop(); + cur=node.right; + } + } + return root; + + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T06_IsSymmetric.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T06_IsSymmetric.java new file mode 100644 index 0000000..dc2f612 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T06_IsSymmetric.java @@ -0,0 +1,114 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Queue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-08 12:23 + *@Description: + * TODO 二刷力扣101 对称二叉树: + * 给你一个二叉树的根节点 root , 检查它是否轴对称。 + *@Version: 1.0 + */ +public class T06_IsSymmetric { + + /** + * 递归法 + * 速度击败100%,内存击败26.75% + * @param root + * @return + */ + public boolean isSymmetric(TreeNode root) { + if (root.left == null && root.right == null) { + return true; + } else { + return find(root.left, root.right); + } + + } + + public boolean find(TreeNode node1, TreeNode node2) { + if (node1 == null && node2 == null) { + return true; + } else { + if (node1 != null && node2 != null && node1.val == node2.val) { + return find(node1.left, node2.right) && find(node1.right, node2.left); + } else { + return false; + } + + } + } + + + /** + * 迭代法:理论上来说只能层序遍历,需要频繁地队列出入栈,比较慢 + * 速度击败22.81%,内存击败59.3% + * @param root + * @return + */ + public boolean isSymmetric1(TreeNode root) { + + Deque queue = new LinkedList<>(); + queue.offer(root.left); + queue.offer(root.right); + + while (!queue.isEmpty()){ + TreeNode node1 = queue.removeFirst(); + TreeNode node2 = queue.removeLast(); + if((node1==null&&node2==null)){ + continue; + } + if(node1!=null&&node2!=null&&node1.val==node2.val){ + queue.addFirst(node1.left); + queue.addLast(node2.right); + queue.addFirst(node1.right); + queue.addLast(node2.left); + }else{ + return false; + } + } + return true; + + } + + + /** + * 官方的迭代法:事实上不需要双端队列加在头尾,只需要成对取即可 + * @param root + * @return + */ + public boolean isSymmetric2(TreeNode root) { + return check(root, root); + } + + public boolean check(TreeNode u, TreeNode v) { + Queue q = new LinkedList(); + q.offer(u); + q.offer(v); + while (!q.isEmpty()) { + u = q.poll(); + v = q.poll(); + if (u == null && v == null) { + continue; + } + if ((u == null || v == null) || (u.val != v.val)) { + return false; + } + + q.offer(u.left); + q.offer(v.right); + + q.offer(u.right); + q.offer(v.left); + } + return true; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T08_IsBalance.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T08_IsBalance.java new file mode 100644 index 0000000..1ec77d5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T08_IsBalance.java @@ -0,0 +1,74 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-09 11:15 + *@Description: + * TODO 二刷力扣110题 平衡二叉树: + * 给定一个二叉树,判断它是否是高度平衡的二叉树。 + * 本题中,一棵高度平衡二叉树定义为: + * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 + *@Version: 1.0 + */ +public class T08_IsBalance { + + /** + * 思路:递归法:如果两边差大于1了直接返回-1;当-1了就快速返回 + * 速度击败100%,内存击败57.39% + * @param root + * @return + */ + public boolean isBalanced(TreeNode root) { + if(root==null){ + return true; + } + int leftHeight=nodeHeight(root.left); + if(leftHeight==-1){ + return false; + } + int rightHeight=nodeHeight(root.right); + if(rightHeight==-1||Math.abs(leftHeight-rightHeight)>1){ + return false; + } + return true; + + } + + /** + * 代码随想录递归 + * @param root + * @return + */ + public boolean isBalanced1(TreeNode root) { + return nodeHeight(root) == -1 ? false : true; + } + + public int nodeHeight(TreeNode node) { + if(node==null){ + return 0; + } + int leftHeight=nodeHeight(node.left); + if(leftHeight==-1){ + return -1; + } + int rightHeight=nodeHeight(node.right); + if(rightHeight==-1||Math.abs(leftHeight-rightHeight)>1){ + return -1; + } + return Math.max(leftHeight,rightHeight)+1; + } + + /** + * 思路:迭代法,后序遍历是一个比较好的方式 + * @param root + * @return + */ + public boolean isBalanced2(TreeNode root) { + + return false; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_0_BinaryTreePaths.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_0_BinaryTreePaths.java new file mode 100644 index 0000000..1f21f31 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_0_BinaryTreePaths.java @@ -0,0 +1,116 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import com.markilue.leecode.tree.TreeUtils; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-09 11:45 + *@Description: + * TODO 二刷力扣257题 二叉树的所有路径: + * 给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。 + * 叶子节点 是指没有子节点的节点。 + *@Version: 1.0 + */ +public class T09_0_BinaryTreePaths { + @Test + public void test(){ + ArrayList list = new ArrayList<>(Arrays.asList(1,-2,3,null,5)); + TreeNode root = TreeUtils.structureTree(list, 0); + System.out.println(binaryTreePaths(root)); + } + + /** + * 思路:经典前序遍历递归回溯问题 + * 速度击败100%,内存击败71.19%。回溯的删除路径是这题比较麻烦的点,因为他添加的数字位数无法确定 + * @param root + * @return + */ + public List binaryTreePaths(TreeNode root) { + String curPath=""; + backtracking1(root,curPath); + return totalPath; + } + //curPath记录当前路径 + List totalPath=new ArrayList<>();//记录全部路径 + public void backtracking(TreeNode node,StringBuilder curPath){ + curPath.append(node.val); + + if(node.left==null&&node.right==null){ + totalPath.add(new String(curPath)); + return; + } + + if(node.left!=null){ + backtracking(node.left,curPath.append("->"));//否则有下一个节点,所以先拼上-> + curPath.delete(curPath.length()-2,curPath.length()); + } + if(node.right!=null){ + backtracking(node.right,curPath.append("->")); + curPath.delete(curPath.length()-2,curPath.length()); + } + curPath.delete(curPath.lastIndexOf(node.val+""),curPath.length()); + + + } + + public void backtracking1(TreeNode node,String curPath){ + StringBuilder builder = new StringBuilder(curPath); + builder.append(node.val); + if(node.left==null&&node.right==null){ + totalPath.add(new String(builder)); + return; + } + + int length2 = builder.length(); + if(node.left!=null){ + backtracking1(node.left,new String(builder.append("->")));//否则有下一个节点,所以先拼上-> + builder.delete(length2,builder.length()); + } + int length1 = builder.length(); + if(node.right!=null){ + backtracking1(node.right,new String(builder.append("->"))); + builder.delete(length1,builder.length()); + } + +// builder.delete(length,length1); + + + } + + /** + * 官方比较漂亮的写法 + * @param root + * @return + */ + public List binaryTreePaths1(TreeNode root) { + List result = new ArrayList<>(); + constructPaths(root,"",result); + return result; + } + //这里使用StringBuilder,因为他快是因为他适用于同一个string不可变字符串的时候,这里每一个只添加一次 + //这里处理node的左右节点,而不是处理他本身,因为涉及到要不要新new上一个List + public void constructPaths(TreeNode node,String path, List result) { + + if(node!=null){ + StringBuilder pathDS = new StringBuilder(path); + pathDS.append(node.val); + //遍历到了叶子结点,可以把结果加入 + if(node.left==null&&node.right==null){ + result.add(pathDS.toString()); + }else{ + //不是叶子节点,需要继续遍历 + pathDS.append("->"); + constructPaths(node.left,pathDS.toString(),result); + constructPaths(node.right,pathDS.toString(),result); + } + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_1_IsSameTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_1_IsSameTree.java new file mode 100644 index 0000000..0657850 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_1_IsSameTree.java @@ -0,0 +1,76 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-09 12:46 + *@Description: + * TODO 力扣100题 相同的树: + * 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 + * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 + *@Version: 1.0 + */ +public class T09_1_IsSameTree { + + /** + * 思路:即完全相同,值相同子树不相同也可以,实际上可以依次遍历个节点。递归法DFS前序遍历 + * 速度击败100%,内存击败72.54% + * @param p + * @param q + * @return + */ + public boolean isSameTree(TreeNode p, TreeNode q) { + if(p==null&&q==null){ + return true; + } + if(p==null||q==null)return false;//其中一个为null + return p.val==q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);//都不为null + + } + + /** + * 思路:即完全相同,值相同子树不相同也可以,实际上可以依次遍历个节点。迭代法BFS + * 速度击败100%,内存击败20.92% + * @param p + * @param q + * @return + */ + public boolean isSameTree1(TreeNode p, TreeNode q) { + if(p==null&&q==null){ + return true; + } + if(p==null||q==null)return false;//其中一个为null + Stack stack = new Stack<>(); + stack.push(p); + stack.push(q);//一直同进同出即可 + while (!stack.isEmpty()){ + int size = stack.size(); + for (int i = 0; i < size; i+=2) { + TreeNode node1 = stack.pop(); + TreeNode node2 = stack.pop(); + if(node1==null&&node2==null){ + continue; + } + if(node1==null||node2==null){ + return false;//其中一个为null + }else { + //全不为null + if(node1.val!=node2.val){ + return false; + } + stack.push(node1.left); + stack.push(node2.left); + stack.push(node1.right); + stack.push(node2.right); + } + } + } + return true;//都不为null + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_2_IsSubtree.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_2_IsSubtree.java new file mode 100644 index 0000000..6930f54 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T09_2_IsSubtree.java @@ -0,0 +1,108 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import com.markilue.leecode.tree.TreeUtils; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-09 13:00 + *@Description: + * TODO 力扣572题 另一颗子树: + * 给你两棵二叉树 root 和 subRoot 。 + * 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。 + * 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。 + * + *@Version: 1.0 + */ +public class T09_2_IsSubtree { + + @Test + public void test() { + ArrayList list = new ArrayList<>(Arrays.asList(3,4,5,1,2)); + ArrayList list1 = new ArrayList<>(Arrays.asList(4,1,2)); + TreeNode root = TreeUtils.structureTree(list, 0); + TreeNode subRoot = TreeUtils.structureTree(list1, 0); + System.out.println(isSubtree(root,subRoot)); + + } + + /** + * 思路:T09_1的浅进阶,找到相同的root后,开始比较子树 + * 速度击败92.32%,内存击败70.43% + * @param root + * @param subRoot + * @return + */ + public boolean isSubtree(TreeNode root, TreeNode subRoot) { + if(root==null&&subRoot==null){ + return true; + } + if(root==null||subRoot==null){ + return false;//其中一个为null + } + boolean flag=false; + boolean flag1=false; + if(root.val==subRoot.val){ + //现在相同子树对象 + flag=isSameTree(root.left,subRoot.left)&&isSameTree(root.right,subRoot.right); + } + if(flag==false){//在开始比较两个子树的过程中就不能进这个了 + flag=isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);//只要有子树一样即可 + } + return flag; + + + } + /** + * 思路:即完全相同,值相同子树不相同也可以,实际上可以依次遍历个节点。递归法DFS前序遍历 + * 速度击败100%,内存击败72.54% + * @param p + * @param q + * @return + */ + public boolean isSameTree(TreeNode p, TreeNode q) { + if(p==null&&q==null){ + return true; + } + if(p==null||q==null)return false;//其中一个为null + return p.val==q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);//都不为null + + } + + + /** + * 官方最快 + * 速度击败100%,内存击败5.4% + * @param root + * @param subRoot + * @return + */ + public boolean isSubtree1(TreeNode root, TreeNode subRoot) { + if(root==null&&subRoot!=null) return false; + if(root!=null&&subRoot==null) return false; + if(root==null&&subRoot==null) return true; + if(root.val==subRoot.val){ + boolean res=isSametree1(root,subRoot); + if(res==true) return res; + } + boolean leftIsSubtree=isSubtree(root.left,subRoot); + boolean rightIsSubtree=isSubtree(root.right,subRoot); + return leftIsSubtree||rightIsSubtree; + + } + public boolean isSametree1(TreeNode root, TreeNode subRoot) { + if(root==null&&subRoot!=null) return false; + if(root!=null&&subRoot==null) return false; + if(root==null&&subRoot==null) return true; + if(root.val!=subRoot.val) return false; + boolean leftIsSametree=isSametree1(root.left,subRoot.left); + boolean rightIsSametree=isSubtree1(root.right,subRoot.right); + return leftIsSametree&&rightIsSametree; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T12_HasPathSum.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T12_HasPathSum.java new file mode 100644 index 0000000..7bef889 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T12_HasPathSum.java @@ -0,0 +1,83 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import org.junit.Test; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-10 11:33 + *@Description: + * TODO 力扣112题 二刷路径总和: + * 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。 + * 判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。 + * 如果存在,返回 true ;否则,返回 false 。 + * 叶子节点 是指没有子节点的节点。 + *@Version: 1.0 + */ +public class T12_HasPathSum { + @Test + public void test(){ + Integer sum=1000; + int i = sum.intValue(); + System.out.println(i); + } + + /** + * 思路:核心就在于在叶子结点时就判断targetSum;DFS + * @param root + * @param targetSum + * @return + */ + public boolean hasPathSum(TreeNode root, int targetSum) { + if (root == null) {//避免开始就是空的情况 + return false; + } + if (root.left == null && root.right == null) {//在叶子结点就处理逻辑 + return root.val == targetSum; + } + int nextTarget = targetSum - root.val; + return hasPathSum(root.left, nextTarget) || hasPathSum(root.right, nextTarget); + + } + + /** + * 思路:非递归法DFS + * 速度击败6.83%,内存击败58.93% + * @param root + * @param targetSum + * @return + */ + public boolean hasPathSum1(TreeNode root, int targetSum) { + if (root == null) {//避免开始就是空的情况 + return false; + } + Stack stack = new Stack<>(); + Stack stackSum = new Stack<>(); + stack.push(root); + stackSum.push(0); + while (!stack.isEmpty()){ + TreeNode node = stack.pop(); + Integer sum = stackSum.pop(); + sum+=node.val; + if(node.left==null&&node.right==null){ + if(sum.intValue()==targetSum){ + return true; + } + } + if(node.left!=null){ + stack.push(node.left); + stackSum.push(sum); + } + if(node.right!=null){ + stack.push(node.right); + stackSum.push(sum); + } + } + return false; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T13_PathSum.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T13_PathSum.java new file mode 100644 index 0000000..d3df09a --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T13_PathSum.java @@ -0,0 +1,103 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-10 11:52 + *@Description: + * TODO 二刷力扣113题 路径总和II: + * 给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 + * 叶子节点 是指没有子节点的节点。 + *@Version: 1.0 + */ +public class T13_PathSum { + + List cur=new ArrayList<>(); + List> result=new ArrayList<>(); + + /** + * 递归法: + * 速度击败99.98%,内存击败88.17% + * @param root + * @param targetSum + * @return + */ + public List> pathSum(TreeNode root, int targetSum) { + travel(root,targetSum); + return result; + + } + public void travel(TreeNode node, int targetSum){ + if(node==null){ + return; + } + cur.add(node.val); + if(node.left==null&&node.right==null){ + //到达叶子节点 + if(node.val==targetSum){ + result.add(new ArrayList<>(cur)); + } + } + travel(node.left,targetSum- node.val); + travel(node.right,targetSum- node.val); + cur.remove(cur.size()-1); + } + + + /** + * 官方最快:思路一样,可能快在使用数组进行增减 + */ + private List> ans = new ArrayList<>(); + + private void dfs(TreeNode root, int targetSum, int current, int[] node, int index) { + if( root == null ) { + return; + } + + node[index] = root.val; + current += root.val; + if( root.left == null && root.right == null ) { + if( current == targetSum ) { + List result = new ArrayList<>(); + for( int i = 0; i <= index; i++ ) { + result.add(node[i]); + } + ans.add(result); + } + return; + } + + + dfs(root.left, targetSum, current, node, index + 1); + dfs(root.right, targetSum, current, node, index + 1); + + } + + private int getDepth(TreeNode root) { + if( root == null ) { + return 0; + } + if( root.left == null && root.right == null ) { + return 1; + } + + return Math.max(getDepth(root.left), getDepth(root.right)) + 1; + } + + public List> pathSum1(TreeNode root, int targetSum) { + int depth = getDepth(root); + if( root == null ) { + return ans; + } + + int[] node = new int[depth]; + dfs(root, targetSum, 0, node, 0); + return ans; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_0_BuildTree.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_0_BuildTree.java new file mode 100644 index 0000000..6a4a880 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_0_BuildTree.java @@ -0,0 +1,83 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-10 12:18 + *@Description: + * TODO 二刷力扣106题 从中序与后序遍历序列构造二叉树: + * 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, + * postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 + *@Version: 1.0 + */ +public class T14_0_BuildTree { + + @Test + public void test(){ + int[] inorder = {9, 3, 15, 20, 7}, postorder = {9, 15, 7, 20, 3}; + System.out.println(buildTree1(inorder,postorder)); + } + + /** + * 思路:后续遍历的最后一个就是根节点,以此划分左右树, + * 自己被卡在就算划分了左右但是不知道inorder和postOrder应该怎么划分; + * 事实上两个个order一定一样长,可以根据中序定边界,从而划分两边 + * 下属写法是最容易理解的写法,还有一些较为难理解但是传递索引而不是复制的写法来处理的写法,会比这个快 + * 速度击败13.81%,内存击败15.72% 6ms + * @param inorder + * @param postorder + * @return + */ + public TreeNode buildTree(int[] inorder, int[] postorder) { + int length = postorder.length; + if(length==0){ + return null; + } + TreeNode root = new TreeNode(postorder[length - 1]); + int boundary=0; + for (; boundary < inorder.length; boundary++) { + if(inorder[boundary]==postorder[length-1]){ + break; + } + } + int[] leftInorder = Arrays.copyOfRange(inorder, 0, boundary); + int[] rightInorder = Arrays.copyOfRange(inorder, boundary+1, length); + int[] leftPostorder = Arrays.copyOfRange(postorder, 0, boundary); + int[] rightPostorder = Arrays.copyOfRange(postorder, boundary, length - 1); + root.left=buildTree(leftInorder,leftPostorder); + root.right=buildTree(rightInorder,rightPostorder); + + return root; + + } + + + /** + * 官方最快 + * 速度击败100%,内存击败72.96% + */ + int post,in; + public TreeNode buildTree1(int[] inorder, int[] postorder) { + post=inorder.length-1; + in=inorder.length-1; + return buildTreeHelper(inorder,postorder,Integer.MAX_VALUE); + } + public TreeNode buildTreeHelper(int[] inorder, int[] postorder, int stop) { + if(post==-1)return null; + if(inorder[in]==stop){//stop是right边界,如果触及了右边界就那边就没有值了 + in--; + return null;//找到了右边的边界 + } + int root_val=postorder[post--]; + TreeNode root = new TreeNode(root_val); + root.right=buildTreeHelper(inorder,postorder,root_val); + root.left=buildTreeHelper(inorder,postorder,stop); + return root; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_1_BuildTreeII.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_1_BuildTreeII.java new file mode 100644 index 0000000..aa2d90e --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T14_1_BuildTreeII.java @@ -0,0 +1,92 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-11 10:03 + *@Description: + * TODO 力扣105题 从前序与中序遍历序列构造二叉树: + * 给定两个整数数组 preorder 和 inorder , + * 其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历, + * 请构造二叉树并返回其根节点。 + *@Version: 1.0 + */ +public class T14_1_BuildTreeII { + + @Test + public void test() { + int[] inorder = {9, 3, 15, 20, 7}, preorder = {3, 9, 20, 15, 7}; + System.out.println(buildTree(preorder, inorder)); + } + + /** + * 思路:T14_0类似,只不过这里是从头开始找root + * 速度击败43.48%,内存击败80.45% 3ms + * @param preorder + * @param inorder + * @return + */ + public TreeNode buildTree(int[] preorder, int[] inorder) { + return build(preorder, inorder, 0, preorder.length, 0, inorder.length); + } + + public TreeNode build(int[] preorder, int[] inorder, int preLeft, int preRight, int inLeft, int inRight) { + int length = preRight - preLeft; + if (length == 0) { + return null; + } + TreeNode root = new TreeNode(preorder[preLeft]); + int boundary = inLeft; + for (; boundary < inRight; boundary++) { + if (inorder[boundary] == root.val) { + break; + } + } + boundary = boundary - inLeft; + root.left = build(preorder, inorder, preLeft + 1, preLeft + 1 + boundary, inLeft, inLeft + boundary); + root.right = build(preorder, inorder, preLeft + 1 + boundary, preRight, inLeft + boundary + 1, inRight); + return root; + + } + + + /** + * 官方最快:思路类似,可能快在不传pre的,直接通过i++来获取 + * 速度击败100%,内存击败53.29% 0ms + */ + public int i = 0; + public TreeNode buildTree1(int[] preorder, int[] inorder) { + TreeNode root = buildTreeChild(preorder, inorder, 0, inorder.length - 1); + return root; + + } + + public TreeNode buildTreeChild(int[] preorder, int[] inorder, + int inbegin, int inend) { + if (inbegin > inend) { + return null; + } + TreeNode root = new TreeNode(preorder[i]); + //找到当前根,在中序遍历的位置 + int rootIndex = findIndex(inorder, inbegin, inend, preorder[i]); + i++; + root.left = buildTreeChild(preorder, inorder, inbegin, rootIndex - 1); + root.right = buildTreeChild(preorder, inorder, rootIndex + 1, inend); + return root; + } + + private int findIndex(int[] inorder, int inbegin, int end, int key) { + int rootIndex = 0; + while (end >= inbegin) { + if (inorder[end] == key) { + return end; + } + end--; + } + return -1; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T15_MergeTrees.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T15_MergeTrees.java new file mode 100644 index 0000000..23625b4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T15_MergeTrees.java @@ -0,0 +1,69 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-11 12:11 + *@Description: + * TODO 力扣617题 合并二叉树: + * 给你两棵二叉树: root1 和 root2 。 + * 想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。 + * 你需要将这两棵树合并成一棵新二叉树。合并的规则是: + * 如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值; + * 否则,不为 null 的节点将直接作为新二叉树的节点。 + * 返回合并后的二叉树。 + * 注意: 合并过程必须从两个树的根节点开始。 + *@Version: 1.0 + */ +public class T15_MergeTrees { + + /** + * 思路:递归法 + * 速度击败100%,内存击败35.5% + * @param root1 + * @param root2 + * @return + */ + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + TreeNode root; + if (root1 == null && root2 == null) { + return null; + } else if (root1 == null) { + root= new TreeNode(root2.val); + root.left=mergeTrees(null,root2.left); + root.right=mergeTrees(null,root2.right); + } else if (root2 == null) { + root = new TreeNode(root1.val); + root.left=mergeTrees(root1.left,null); + root.right=mergeTrees(root1.right,null); + } else { + root = new TreeNode(root1.val+root2.val); + root.left=mergeTrees(root1.left,root2.left); + root.right=mergeTrees(root1.right,root2.right); + } + + return root; + } + + /** + * 自己以前写的优秀写法 + * @param root1 + * @param root2 + * @return + */ + public TreeNode mergeTrees1(TreeNode root1, TreeNode root2) { + if (root1 == null) { + return root2; + } else { + if (root2 != null) { + root1.val = root1.val + root2.val; + root1.left=mergeTrees1(root1.left,root2.left); + root1.right=mergeTrees1(root1.right,root2.right); + } + return root1; + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T16_SearchBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T16_SearchBST.java new file mode 100644 index 0000000..bc172df --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T16_SearchBST.java @@ -0,0 +1,66 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-12 12:11 + *@Description: + * TODO 二刷题700 二叉搜索树中的搜索: + * 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 + * 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。 + *@Version: 1.0 + */ +public class T16_SearchBST { + + + /** + * 思路:利用二叉搜索树的特性 + * 递归法 速度击败100%,内存击败16.67% + * @param root + * @param val + * @return + */ + public TreeNode searchBST(TreeNode root, int val) { + if(root==null){ + return null; + } + if(root.val==val){ + return root; + }else if(root.valval){ + cur=cur.left; + }else { + cur=cur.right; + } + } + return cur; + + + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T17_IsValidBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T17_IsValidBST.java new file mode 100644 index 0000000..df29a99 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T17_IsValidBST.java @@ -0,0 +1,86 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import org.junit.Test; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-12 12:27 + *@Description: + * TODO 二刷力扣98题 验证二叉搜索树: + * 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 + * 有效 二叉搜索树定义如下: + * 节点的左子树只包含 小于 当前节点的数。 + * 节点的右子树只包含 大于 当前节点的数。 + * 所有左子树和右子树自身必须也是二叉搜索树。 + *@Version: 1.0 + */ +public class T17_IsValidBST { + + @Test + public void test(){ + System.out.println(Integer.MAX_VALUE); + } + + /** + * 思路:需要注意不是左右子树满足就行,而是整个左右子树都需要满足 + * 速度击败100%,内存击败85.63% + * @param root + * @return + */ + public boolean isValidBST(TreeNode root) { + return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE); + } + + /** + * 当前节点需要满足左右边界 + * @param root + * @param leftThreshold + * @param rightThreshold + * @return + */ + public boolean isValid(TreeNode root, long leftThreshold, long rightThreshold) { + if (root == null) { + return true; + } + if (root.val <= leftThreshold || root.val >= rightThreshold) { + return false; + } + return isValid(root.left, leftThreshold, root.val) && isValid(root.right, root.val, rightThreshold); + + } + + + /** + * 递归法中序遍历即可:前面的节点一定要小于当前的节点即可 + * 速度击败19.10%,内存击败13.11% + * @param root + * @return + */ + public boolean isValidBST1(TreeNode root) { + if (root == null) { + return false; + } + + Stack stack = new Stack<>(); + TreeNode pre = null;//记录前一个节点 + TreeNode cur = root; + while (!stack.isEmpty() || cur != null) { + if (cur != null) { + stack.push(cur); + cur=cur.left; + } else { + cur = stack.pop(); + if (pre != null && cur.val <= pre.val) return false; + pre = cur;//保存访问的前一个节点 + cur = cur.right; + } + } + return true; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T18_GetMinimumDifference.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T18_GetMinimumDifference.java new file mode 100644 index 0000000..46954a1 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T18_GetMinimumDifference.java @@ -0,0 +1,69 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.Stack; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-12 12:46 + *@Description: + * TODO 力扣530 二叉搜索树的最小绝对差: + * 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。 + * 差值是一个正数,其数值等于两值之差的绝对值。 + *@Version: 1.0 + */ +public class T18_GetMinimumDifference { + + /** + * 思路:二叉搜索树特性决定了,最小绝对差一定是两个连续中序遍历节点的差值 + * 速度击败100%,内存击败77.8% + * @param root + * @return + */ + int min=Integer.MAX_VALUE; + TreeNode pre=null; + public int getMinimumDifference(TreeNode root) { + travel(root); + return min; + } + public void travel(TreeNode root) { + if(root==null){ + return; + } + travel(root.left); + if(pre!=null&&min>root.val-pre.val){ + min=root.val-pre.val; + } + pre=root; + travel(root.right); + } + + + /** + * 迭代法,仍然是中序遍历,实际上与上面的递归无差异 + */ + Stack stack; + public int getMinimumDifference1(TreeNode root) { + if (root == null) return 0; + stack = new Stack<>(); + TreeNode cur = root; + int result = Integer.MAX_VALUE; + while (cur != null || !stack.isEmpty()) { + if (cur != null) { + stack.push(cur); // 将访问的节点放进栈 + cur = cur.left; // 左 + }else { + cur = stack.pop(); + if (pre != null) { // 中 + result = Math.min(result, cur.val - pre.val); + } + pre = cur; + cur = cur.right; // 右 + } + } + return result; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T19_FindMode.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T19_FindMode.java new file mode 100644 index 0000000..4f032c5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T19_FindMode.java @@ -0,0 +1,77 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +import java.util.ArrayList; +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-12 13:05 + *@Description: + * TODO 力扣501 二叉搜索树中的众数: + * 给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。 + * 如果树中有不止一个众数,可以按 任意顺序 返回。 + * 假定 BST 满足如下定义: + * 结点左子树中所含节点的值 小于等于 当前节点的值 + * 结点右子树中所含节点的值 大于等于 当前节点的值 + * 左子树和右子树都是二叉搜索树 + *@Version: 1.0 + */ +public class T19_FindMode { + + /** + * 思路:由于二叉搜索树的中序遍历的特性,相同的数应该是连在一起的 + * 速度击败100%,内存击败85.48% + * @param root + * @return + */ + TreeNode pre = null; + int maxTime = 0; + int curTime = 0; + List result = new ArrayList<>(); + + public int[] findMode(TreeNode root) { + find(root); + //最后一次如果相等就还是不会进 + if (maxTime == curTime) { + result.add(pre.val); + } else if (maxTime < curTime) { + maxTime = curTime; + result.clear(); + result.add(pre.val); + } + int[] nums = new int[result.size()]; + for (int i = 0; i < nums.length; i++) { + nums[i]=result.get(i); + } + return nums; + + } + + public void find(TreeNode root) { + if (root == null) { + return; + } + find(root.left); + if (pre == null || pre.val == root.val) { + curTime++; + }else { + if (maxTime == curTime) { + result.add(pre.val); + } else if (maxTime < curTime) { + maxTime = curTime; + result.clear(); + result.add(pre.val); + } + curTime = 1; + } + + + + pre=root; + find(root.right); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T20_LowestCommonAncestor.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T20_LowestCommonAncestor.java new file mode 100644 index 0000000..d9c8093 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T20_LowestCommonAncestor.java @@ -0,0 +1,66 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-29 10:08 + *@Description: + * TODO 二刷力扣236题 二叉树的最近公共祖先: + * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + * 百度百科中最近公共祖先的定义为: + * “对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x, + * 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” + * + *@Version: 1.0 + */ +public class T20_LowestCommonAncestor { + + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) { + return root; + } + //这里之所以可以直接返回,而不怕其子树中有另一个节点, + // 是因为后面如果都不满足会直接返回root,如果root里面有子树,那么root也是最近祖先 + if(root.val==p.val||root.val==q.val){ + return root; + } + TreeNode node = lowestCommonAncestor(root.left, p, q); + TreeNode node1 = lowestCommonAncestor(root.right, p, q); + if (node == null) { + return node1; + } else if (node1 == null) { + return node; + } else if (node.val == p.val && node1.val == q.val || node.val == q.val && node1.val == p.val) { + //这个判断条件可以优化为left和right都不等于null,就可以返回root + return root; + } + + return root; + + + } + + + /** + * 官方最快 + * 5ms + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || root == p || root == q) return root; + TreeNode left = lowestCommonAncestor1(root.left, p, q); + TreeNode right = lowestCommonAncestor1(root.right, p, q); + if (left != null && right != null) {return root;} + return left != null ? left : right; + + } + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T21_LowestCommonAncestorII.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T21_LowestCommonAncestorII.java new file mode 100644 index 0000000..f112898 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T21_LowestCommonAncestorII.java @@ -0,0 +1,113 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-29 10:52 + *@Description: + * TODO 二刷力扣235题 二叉搜索树的最近公共祖先: + * 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 + * 百度百科中最近公共祖先的定义为: + * “对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” + * 例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5] + *@Version: 1.0 + */ +public class T21_LowestCommonAncestorII { + + /** + * 思路:理论上来说,适用于普通二叉树的方式,也适用于二叉搜索树 + * 速度击败35.59%,内存击败19.97% + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if(root==null|| root.val==p.val||root.val==q.val)return root; + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + if(left!=null&&right!=null)return root; + return left==null?right:left; + } + + /** + * 思路:从上往下找,找到第一个在p和q中间的值,就一定是他们的公共祖先 + * 速度击败99.96%,内存击败93.11% 5ms + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) { + if(root==null)return root; + if(p.val>q.val){ + TreeNode temp=p; + p=q; + q=temp; + } + if(root.val<=q.val&&root.val>=p.val){ + return root; + }else if(root.val Math.max(pv,qv)) { + if (rv < Math.min(pv, qv)) { + root = root.right; + rv = root.val; + } + else { + root = root.left; + rv = root.val; + } + } + return root; + + } + + /** + * 针对最快的改进,一直计算最大最小太麻烦了,直接存起来 + * 速度击败99.96%,内存击败59.90% 5ms + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor3(TreeNode root, TreeNode p, TreeNode q) { + int min = Math.min(p.val, q.val); + int max = Math.max(p.val, q.val); + int rv = root.val; + while ( rv < min || rv >max ) { + if (rv < min) { + root = root.right; + rv = root.val; + } + else { + root = root.left; + rv = root.val; + } + } + return root; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T22_InsertIntoBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T22_InsertIntoBST.java new file mode 100644 index 0000000..6f5283e --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T22_InsertIntoBST.java @@ -0,0 +1,63 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-29 11:19 + *@Description: + * TODO 二刷力扣701 二叉搜索树中的插入操作: + * 给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 + * 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。 + * 注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。 + *@Version: 1.0 + */ +public class T22_InsertIntoBST { + + /** + * 思路:找到合适的位置插在叶子结点 + * 速度击败100%,内存击败11.89% + * @param root + * @param val + * @return + */ + public TreeNode insertIntoBST(TreeNode root, int val) { + if (root == null) { + return new TreeNode(val); + } + TreeNode temp = root; + insert(root, val); + return root; + + } + + public void insert(TreeNode root, int val) { + if (root.val < val) { + if (root.right == null) root.right = new TreeNode(val); + else insertIntoBST(root.right, val); + } else { + if (root.left == null) root.left = new TreeNode(val); + else insertIntoBST(root.left, val); + } + } + + /** + * 代码随想录优秀写法 + * @param root + * @param val + * @return + */ + public TreeNode insertIntoBST1(TreeNode root, int val) { + if (root == null) // 如果当前节点为空,也就意味着val找到了合适的位置,此时创建节点直接返回。 + return new TreeNode(val); + + if (root.val < val){ + root.right = insertIntoBST(root.right, val); // 递归创建右子树 + }else if (root.val > val){ + root.left = insertIntoBST(root.left, val); // 递归创建左子树 + } + return root; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T23_DeleteNode.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T23_DeleteNode.java new file mode 100644 index 0000000..821602f --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T23_DeleteNode.java @@ -0,0 +1,80 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-29 11:53 + *@Description: + * TODO 二刷力扣450题 删除二叉搜索树中的节点: + * 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 + * 一般来说,删除节点可分为两个步骤: + * 首先找到需要删除的节点; + * 如果找到了,删除它。 + *@Version: 1.0 + */ +public class T23_DeleteNode { + + /** + * 核心思路:找到需要删除的节点,然后寻找其左子树的最右节点或右子树的最左节点,将其赋值给当前节点; + * 然后变为删除这个最左右节点(该节点最多只有一个子树) + * 速度击败100%,内存击败41.24% + * @param root + * @param key + * @return + */ + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) { + return root; + } + TreeNode temp = root; + + + TreeNode pre = null;//父节点 + //寻找需要删除的节点 + while (temp != null && temp.val != key) { + pre = temp; + if (temp.val > key) { + temp = temp.left; + } else { + temp = temp.right; + } + } + if(temp==null){ + //没找到 + return root; + } + + //寻找要删除节点其右子树的最左节点 + TreeNode need=null; + if (temp.right != null) { + pre = temp; + need = temp.right; + while (need.left != null) { + pre = need; + need = need.left; + } + temp.val = need.val; + }else { + need=temp; + } + + //后续删除need节点;need最多仅有一个节点 + TreeNode child = need.left == null ? need.right : need.left; + if (pre == null) { + //删除的是root节点 + return child; + } + + + if(pre.left==need){ + pre.left=child; + }else { + pre.right=child; + } + + return root; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T24_TrimBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T24_TrimBST.java new file mode 100644 index 0000000..5d38481 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T24_TrimBST.java @@ -0,0 +1,101 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import com.markilue.leecode.tree.TreeUtils; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-30 10:59 + *@Description: + * TODO 二刷力扣669题 修剪二叉搜索树: + * 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。 + * 通过修剪二叉搜索树,使得所有节点的值在[low, high]中。 + * 修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 + * 可以证明,存在 唯一的答案 。 + * 所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。 + *@Version: 1.0 + */ +public class T24_TrimBST { + @Test + public void test() { + TreeNode root = TreeUtils.structureTree(Arrays.asList(3, 1, 4, null, 2), 0); + trimBST(root, 3, 4); + TreeUtils.printTreeByLevel(root); + } + + /** + * 思路:找到一个符合范围的就删除,然后再次寻找可以,但是效率太慢了,使用下面这种方式 + * 速度击败100%,内存击败31.92% + * @param root + * @param low + * @param high + * @return + */ + public TreeNode trimBST(TreeNode root, int low, int high) { + if (root == null) { + return null; + } else if (root.val < low) { + return trimBST(root.right, low, high); + } else if (root.val > high) { + return trimBST(root.left, low, high); + } + root.left = trimBST(root.left, low, high); + root.right = trimBST(root.right, low, high); + return root; + + } + + + /** + * 代码随想录提供的一种非递归法思路,将步骤可以总结为: + * 1.找到第一个在区间范围内的 + * 2.处理其左节点范围之外的 + * 3.处理其右节点范围之外的 + * 之所以可以这么做,是因为root规定了左子树的右极限;和右子树的左极限 + * @param root + * @param low + * @param high + * @return + */ + public TreeNode trimBST1(TreeNode root, int low, int high) { + if(root==null)return null; + + + //处理头结点,让root移动到一个在区间内的数 + while (root!=null&&(root.valhigh)){ + if(root.valhigh){ + cur.right=cur.right.left; + } + //处理完毕,安心将cur右移,继续判断其左节点会不会超过 + cur=cur.right; + //这里不需要判断左节点,因为root在区间内,那么右子树最小也就是root,所以不会超过其左界 + } + //全部处理完毕,安心返回root + return root; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/tree/second/T25_SortedArrayToBST.java b/Leecode/src/main/java/com/markilue/leecode/tree/second/T25_SortedArrayToBST.java new file mode 100644 index 0000000..1453eea --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/tree/second/T25_SortedArrayToBST.java @@ -0,0 +1,65 @@ +package com.markilue.leecode.tree.second; + +import com.markilue.leecode.tree.TreeNode; +import com.markilue.leecode.tree.TreeUtils; +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.tree.second + *@Author: dingjiawen + *@CreateTime: 2023-01-30 11:36 + *@Description: + * TODO 二刷力扣108题 将有序数组转换为二叉搜索树: + * 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。 + * 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。 + *@Version: 1.0 + */ +public class T25_SortedArrayToBST { + + @Test + public void test() { + int[] nums = {-10, -3, 0, 5, 9}; + TreeUtils.printTreeByLevel(sortedArrayToBST(nums)); + } + + public TreeNode sortedArrayToBST(int[] nums) { + return build(nums, 0, nums.length-1); + } + + public TreeNode build(int[] nums, int start, int end) { + if(start>end)return null; + if (start == end) return new TreeNode(nums[start]); + int mid = start + ((end - start) >> 1); + TreeNode root = new TreeNode(nums[mid]); + root.left = build(nums, start, mid - 1); + root.right = build(nums, mid + 1, end); + return root; + } + + + /** + * 这是一种保持左闭右开的方式:由于(right - left) / 2是奇数一定会舍 + * 所以left + (right - left) / 2 stack = new LinkedList<>(); + int pre = 0; + TreeNode cur = root; + while (cur != null || !stack.isEmpty()) { + if (cur != null) { + stack.push(cur); + cur = cur.right; + } else { + TreeNode node = stack.pop(); + node.val += pre; + pre = node.val; + cur = node.left; + } + } + return root; + + } + + /** + * Morris遍历法 + * @param root + * @return + */ + public TreeNode convertBST2(TreeNode root) { + if (root == null) { + return root; + } + + TreeNode cur = root; + TreeNode pre; + int sum=0; + while (cur != null) { + //寻找其右子树的最左节点 + pre = cur.right; + if (pre != null) { + while (pre.left != null && pre.left != cur) { + pre = pre.left; + } + //判断是从什么条件出来的 + if (pre.left == null) { + //第一遍历到 + pre.left = cur; + //放心将其右移 + cur = cur.right; + continue; + } + else if (pre.left == cur) { + //第二次遍历到 + cur.val+=sum; + sum=cur.val; + cur=cur.left; + pre.left=null; + } + }else { + cur.val+=sum; + sum=cur.val; + cur=cur.left; + } + + + } + + return root; + + + } + + +} diff --git a/TensorFlow_eaxmple/Model_train_test/condition_monitoring/plot/test_plot.py b/TensorFlow_eaxmple/Model_train_test/condition_monitoring/plot/test_plot.py index 8d15dda..5530cd3 100644 --- a/TensorFlow_eaxmple/Model_train_test/condition_monitoring/plot/test_plot.py +++ b/TensorFlow_eaxmple/Model_train_test/condition_monitoring/plot/test_plot.py @@ -7,10 +7,10 @@ import numpy as np import random import pandas as pd import seaborn as sns -from condition_monitoring.data_deal.loadData import read_data -from model.Joint_Monitoring.Joint_Monitoring_banda import Joint_Monitoring -from model.Joint_Monitoring.compare.RNet_L import Joint_Monitoring as Joint_Monitoring_L -from model.Joint_Monitoring.compare.RNet_S import Joint_Monitoring as Joint_Monitoring_SE +# from condition_monitoring.data_deal.loadData import read_data +# from model.Joint_Monitoring.Joint_Monitoring_banda import Joint_Monitoring +# from model.Joint_Monitoring.compare.RNet_L import Joint_Monitoring as Joint_Monitoring_L +# from model.Joint_Monitoring.compare.RNet_S import Joint_Monitoring as Joint_Monitoring_SE import tensorflow as tf import tensorflow.keras from mpl_toolkits.axes_grid1.inset_locator import mark_inset @@ -467,16 +467,27 @@ def plot_FNR2(y_data): x_width = range(0, len(y_data)) # x2_width = [i + 0.3 for i in x_width] - plt.bar(x_width[0], y_data[0], lw=1, color=['#FAF4E1'], width=0.5 * 2 / 3, label="ResNet-18", edgecolor='black') - plt.bar(x_width[1], y_data[1], lw=1, color=['#F5E3C4'], width=0.5 * 2 / 3, label="RNet-1", edgecolor='black') - plt.bar(x_width[2], y_data[2], lw=1, color=['#EBC99D'], width=0.5 * 2 / 3, label="RNet-2", edgecolor='black') - plt.bar(x_width[3], y_data[3], lw=1, color=['#FFC79C'], width=0.5 * 2 / 3, label="RNet-3", edgecolor='black') - plt.bar(x_width[4], y_data[4], lw=1, color=['#D6E6F2'], width=0.5 * 2 / 3, label="RNet-12", edgecolor='black') - plt.bar(x_width[5], y_data[5], lw=1, color=['#B4D1E9'], width=0.5 * 2 / 3, label="RNet-13", edgecolor='black') - plt.bar(x_width[6], y_data[6], lw=1, color=['#AEB5EE'], width=0.5 * 2 / 3, label="RNet-23", edgecolor='black') + # plt.bar(x_width[0], y_data[0], lw=1, color=['#FAF4E1'], width=0.5 * 2 / 3, label="ResNet-18", edgecolor='black') + # # plt.bar(x_width[1], y_data[1], lw=1, color=['#F5E3C4'], width=0.5 * 2 / 3, label="RNet-1", edgecolor='black') + # # plt.bar(x_width[2], y_data[2], lw=1, color=['#EBC99D'], width=0.5 * 2 / 3, label="RNet-2", edgecolor='black') + # # plt.bar(x_width[3], y_data[3], lw=1, color=['#FFC79C'], width=0.5 * 2 / 3, label="RNet-3", edgecolor='black') + # plt.bar(x_width[4], y_data[4], lw=1, color=['#D6E6F2'], width=0.5 * 2 / 3, label="RNet-12", edgecolor='black') + # # plt.bar(x_width[5], y_data[5], lw=1, color=['#B4D1E9'], width=0.5 * 2 / 3, label="RNet-13", edgecolor='black') + # # plt.bar(x_width[6], y_data[6], lw=1, color=['#AEB5EE'], width=0.5 * 2 / 3, label="RNet-23", edgecolor='black') + # # plt.bar(x_width[7] + 2.0, y_data[10], lw=0.5, color=['#8085e9'], width=1, label="ResNet-18", edgecolor='black') + # # plt.bar(x_width[7], y_data[7], lw=1, color=['#D5A9FF'], width=0.5 * 2 / 3, label="ResNet-C", edgecolor='black') + # plt.bar(x_width[8], y_data[8], lw=1, color=['#E000F5'], width=0.5 * 2 / 3, label="JMNet", edgecolor='black') + + plt.bar(x_width[0], y_data[0], lw=1, color=['#AEB5EE'], width=0.5 * 2 / 3, label="ResNet-18", edgecolor='black') + plt.bar(x_width[1], y_data[1], color=['#FFFFFF'], label=" ") + plt.bar(x_width[2], y_data[2], color=['#FFFFFF'], label=" ") + plt.bar(x_width[3], y_data[3], color=['#FFFFFF'], label=" ") + plt.bar(x_width[4], y_data[4],lw=1, color=['#D5A9FF'], width=0.5 * 2 / 3, label="RNet-C", edgecolor='black') + plt.bar(x_width[5], y_data[5], color=['#FFFFFF'], label=" ") + plt.bar(x_width[6], y_data[6], color=['#FFFFFF'], label=" ") # plt.bar(x_width[7] + 2.0, y_data[10], lw=0.5, color=['#8085e9'], width=1, label="ResNet-18", edgecolor='black') - plt.bar(x_width[7], y_data[7], lw=1, color=['#D5A9FF'], width=0.5 * 2 / 3, label="ResNet-C", edgecolor='black') - plt.bar(x_width[8], y_data[8], lw=1, color=['#E000F5'], width=0.5 * 2 / 3, label="JMNet", edgecolor='black') + plt.bar(x_width[7], y_data[7], color=['#FFFFFF'], label=" ") + plt.bar(x_width[8], y_data[8],lw=1, color=['#E000F5'], width=0.5 * 2 / 3, label="JMNet", edgecolor='black') # plt.tick_params(bottom=False, top=False, left=True, right=False, direction='in', pad=1) plt.xticks([]) @@ -487,7 +498,8 @@ def plot_FNR2(y_data): plt.xlabel('Methods', fontsize=22) # plt.tight_layout() - num1, num2, num3, num4 = 0.08, 1, 3, 0 + # num1, num2, num3, num4 = 0.08, 1, 3, 0 + num1, num2, num3, num4 = 0.15, 1, 3, 0 plt.legend(bbox_to_anchor=(num1, num2), loc=num3, borderaxespad=num4, ncol=5, frameon=False, handlelength=1, handletextpad=0.45, columnspacing=1) plt.ylim([0, 5]) @@ -785,6 +797,7 @@ if __name__ == '__main__': # plot_FNR1(list) # # list=[3.43,1.99,1.92,2.17,1.63,1.81,1.78,1.8,0.6] + list=[3.43,1.99,1.92,2.17,1.8,1.81,1.78,1.8,0.6] plot_FNR2(list) # 查看网络某一层的权重 diff --git a/TensorFlow_eaxmple/Model_train_test/condition_monitoring/test.py b/TensorFlow_eaxmple/Model_train_test/condition_monitoring/test.py index 8a3d6e5..11e2a91 100644 --- a/TensorFlow_eaxmple/Model_train_test/condition_monitoring/test.py +++ b/TensorFlow_eaxmple/Model_train_test/condition_monitoring/test.py @@ -6,7 +6,8 @@ import numpy as np import random import pandas as pd import seaborn as sns -from condition_monitoring.data_deal.loadData import read_data +import scipy.signal + ''' @Author : dingjiawen @@ -193,17 +194,6 @@ def test_mse(mse_file_name:str=mse_file_name,max_file_name:str=max_file_name): - -def test_corr(file_name=source_path,N=10): - needed_data, label = read_data(file_name=file_name, isNew=False) - print(needed_data) - print(needed_data.shape) - # plot_original_data(needed_data) - person = plot_Corr(needed_data, label) - person = np.array(person) - pass - - def plot_raw_data(): # data, label = read_data(file_name='G:\data\SCADA数据\jb4q_8_delete_total_zero.csv', isNew=False) # data = np.loadtxt('G:\data\SCADA数据/normalization.csv',delimiter=',') @@ -218,7 +208,14 @@ if __name__ == '__main__': # test_mse() # test_result() # test_corr() - plot_raw_data() + # plot_raw_data() + data=np.load("H:\data\predict_data\\HI_merge_data.npy") + print(data) + data=data[:1250,1] + plt.plot(data) + data_smooth=scipy.signal.savgol_filter(data,53,3) + plt.plot(data_smooth) + plt.show() pass diff --git a/TensorFlow_eaxmple/Model_train_test/spider/qidian.py b/TensorFlow_eaxmple/Model_train_test/spider/qidian.py new file mode 100644 index 0000000..aae8114 --- /dev/null +++ b/TensorFlow_eaxmple/Model_train_test/spider/qidian.py @@ -0,0 +1,21 @@ +import scrapy +import requests + + +class qidianSpider(scrapy.Spider): + name = 'qidian' + + start_urls =['https://vipreader.qidian.com/chapter/1031940621/742792391'] + + def parse(self, response, **kwargs): + divs=response.xpath('//div[@class="read-content j_readContent"]') + print(divs) + + + +if __name__ == '__main__': + url = "https://vipreader.qidian.com/chapter/1031940621/742792391/" + + response = requests.get(url=url) + + qidianSpider().parse(response=response) \ No newline at end of file diff --git a/TensorFlow_eaxmple/Model_train_test/spider/test.py b/TensorFlow_eaxmple/Model_train_test/spider/test.py new file mode 100644 index 0000000..17bcc53 --- /dev/null +++ b/TensorFlow_eaxmple/Model_train_test/spider/test.py @@ -0,0 +1,23 @@ +import requests +from lxml import etree +import scrapy + + +url="https://vipreader.qidian.com/chapter/1031940621/742792391/" + +response=requests.get(url=url) +# print(response.text) + +html=etree.HTML(response.text) +total=html.xpath('//*[@id="j_742792391"]/p') +# total=html.xpath('//*[@id="j_742792391"]/p[15]/text()') +total=html.xpath('//div[@class="read-content j_readContent"]/p') +print(total) +textList=[] +for i in range(10): + # print(p) + textList.append(html.xpath('//*[@id="j_742792391"]/p[{0}]/text()'.format(i))) + # textList.append(html.xpath('//*[@id="j_742792391"]/p[1]/text()')) +# print(text1) +# print(text2) +print(textList) \ No newline at end of file