This commit is contained in:
markilue 2023-02-08 10:44:04 +08:00
commit ad8da1d5ee
199 changed files with 13362 additions and 166 deletions

View File

@ -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,来了老弟
}
}

View File

@ -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();
/*
---发送方启动中---
服务器收到了
*/
}
}

View File

@ -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);
}
}

View File

@ -5,7 +5,7 @@ package com.markilue.leecode.array;
* 描述:给定一个n个元素有序的升序整型数组nums 和一个目标值target 写一个函数搜索nums中的 target如果目标值存在返回下标否则返回 -1 * 描述:给定一个n个元素有序的升序整型数组nums 和一个目标值target 写一个函数搜索nums中的 target如果目标值存在返回下标否则返回 -1
* *
*/ */
public class BinarySearch { public class T01_BinarySearch {
public static void main(String[] args) { public static void main(String[] args) {
int[] nums = {-1,0,3,5,9,12}; int[] nums = {-1,0,3,5,9,12};

View File

@ -11,7 +11,7 @@ import java.util.Arrays;
* 不要使用额外的数组空间你必须仅使用 O(1) 额外空间并 原地 修改输入数组 * 不要使用额外的数组空间你必须仅使用 O(1) 额外空间并 原地 修改输入数组
* 元素的顺序可以改变你不需要考虑数组中超出新长度后面的元素 * 元素的顺序可以改变你不需要考虑数组中超出新长度后面的元素
*/ */
public class RemoveElement { public class T02_RemoveElement {
@Test @Test

View File

@ -7,7 +7,7 @@ import javax.management.remote.rmi._RMIConnection_Stub;
/** /**
* 寻找长度最小的子数组 * 寻找长度最小的子数组
*/ */
public class MinSubArrayLen { public class T03_MinSubArrayLen {
@Test @Test

View File

@ -8,7 +8,7 @@ import java.util.Arrays;
* 螺旋矩阵: * 螺旋矩阵:
* 给你一个正整数 n 生成一个包含 1 n2 所有元素且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix * 给你一个正整数 n 生成一个包含 1 n2 所有元素且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix
*/ */
public class generateMatrix { public class T04_generateMatrix {
@Test @Test

View File

@ -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;
}
}

View File

@ -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){
left=mid+1;
}else 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -16,7 +16,7 @@ import java.util.List;
* 你可以按 任何顺序 返回答案 * 你可以按 任何顺序 返回答案
* @Version: 1.0 * @Version: 1.0
*/ */
public class Combine { public class T01_Combine {
@Test @Test

View File

@ -17,7 +17,7 @@ import java.util.List;
* 返回 所有可能的有效组合的列表 该列表不能包含相同的组合两次组合可以以任何顺序返回 * 返回 所有可能的有效组合的列表 该列表不能包含相同的组合两次组合可以以任何顺序返回
* @Version: 1.0 * @Version: 1.0
*/ */
public class combinationSum3 { public class T02_CombinationSum3 {
@Test @Test

View File

@ -1,6 +1,5 @@
package com.markilue.leecode.backtrace; package com.markilue.leecode.backtrace;
import com.markilue.leecode.stackAndDeque.EvalRPN;
import org.junit.Test; import org.junit.Test;
import java.util.*; import java.util.*;
@ -15,7 +14,7 @@ import java.util.*;
* 给出数字到字母的映射如下与电话按键相同注意 1 不对应任何字母 * 给出数字到字母的映射如下与电话按键相同注意 1 不对应任何字母
* @Version: 1.0 * @Version: 1.0
*/ */
public class IetterCombinations { public class T03_LetterCombinations {
@Test @Test
public void test() { public void test() {

View File

@ -17,7 +17,7 @@ import java.util.List;
* 对于给定的输入保证和为 target 的不同组合数少于 150 * 对于给定的输入保证和为 target 的不同组合数少于 150
* @Version: 1.0 * @Version: 1.0
*/ */
public class combinationSum { public class T04_CombinationSum {
@Test @Test
public void test() { public void test() {

View File

@ -15,7 +15,7 @@ import java.util.*;
* 注意解集不能包含重复的组合 * 注意解集不能包含重复的组合
* @Version: 1.0 * @Version: 1.0
*/ */
public class combinationSum2 { public class T05_CombinationSum2 {
@Test @Test
public void test(){ public void test(){

View File

@ -15,7 +15,7 @@ import java.util.List;
* 回文串 是正着读和反着读都一样的字符串 * 回文串 是正着读和反着读都一样的字符串
* @Version: 1.0 * @Version: 1.0
*/ */
public class partition { public class T06_Partition {
@Test @Test
public void test() { public void test() {

View File

@ -17,7 +17,7 @@ import java.util.List;
* 给定一个只包含数字的字符串 s 用以表示一个 IP 地址返回所有可能的有效 IP 地址这些地址可以通过在 s 中插入 '.' 来形成 不能 重新排序或删除 s 中的任何数字你可以按 任何 顺序返回答案 * 给定一个只包含数字的字符串 s 用以表示一个 IP 地址返回所有可能的有效 IP 地址这些地址可以通过在 s 中插入 '.' 来形成 不能 重新排序或删除 s 中的任何数字你可以按 任何 顺序返回答案
* @Version: 1.0 * @Version: 1.0
*/ */
public class RestoreIpAddresses { public class T07_RestoreIpAddresses {
@Test @Test
public void test1() { public void test1() {

View File

@ -16,7 +16,7 @@ import java.util.List;
* 解集 不能 包含重复的子集你可以按 任意顺序 返回解集 * 解集 不能 包含重复的子集你可以按 任意顺序 返回解集
* @Version: 1.0 * @Version: 1.0
*/ */
public class Subsets { public class T08_Subsets {
@Test @Test
public void test(){ public void test(){

View File

@ -1,9 +1,7 @@
package com.markilue.leecode.backtrace; package com.markilue.leecode.backtrace;
import com.markilue.leecode.stackAndDeque.EvalRPN;
import org.junit.Test; import org.junit.Test;
import javax.print.DocFlavor;
import java.util.*; import java.util.*;
/** /**
@ -16,7 +14,7 @@ import java.util.*;
* 解集 不能 包含重复的子集返回的解集中子集可以按 任意顺序 排列 * 解集 不能 包含重复的子集返回的解集中子集可以按 任意顺序 排列
* @Version: 1.0 * @Version: 1.0
*/ */
public class SubsetsWithDup { public class T09_SubsetsWithDup {
@Test @Test
public void test() { public void test() {

View File

@ -17,7 +17,7 @@ import java.util.Set;
* 数组中可能含有重复元素如出现两个整数相等也可以视作递增序列的一种特殊情况 * 数组中可能含有重复元素如出现两个整数相等也可以视作递增序列的一种特殊情况
* @Version: 1.0 * @Version: 1.0
*/ */
public class FindSubsequences { public class T10_FindSubsequences {
@Test @Test

View File

@ -15,7 +15,7 @@ import java.util.List;
* 给定一个不含重复数字的数组 nums 返回其 所有可能的全排列 你可以 按任意顺序 返回答案 * 给定一个不含重复数字的数组 nums 返回其 所有可能的全排列 你可以 按任意顺序 返回答案
* @Version: 1.0 * @Version: 1.0
*/ */
public class Permute { public class T11_Permute {
@Test @Test
public void test(){ public void test(){

View File

@ -16,7 +16,7 @@ import java.util.List;
* 给定一个可包含重复数字的序列 nums 按任意顺序 返回所有不重复的全排列 * 给定一个可包含重复数字的序列 nums 按任意顺序 返回所有不重复的全排列
* @Version: 1.0 * @Version: 1.0
*/ */
public class PermuteUnique { public class T12_PermuteUnique {
@Test @Test
public void test() { public void test() {

View File

@ -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<List<String>> 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<List<String>> 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<List<String>> 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<String> cur = new ArrayList<>();
List<String> result = new ArrayList<>();
List<List<String>> result1 = new ArrayList<>();
public List<String> findItinerary(List<List<String>> tickets) {
Collections.sort(tickets, new Comparator<List<String>>() {
@Override
public int compare(List<String> o1, List<String> o2) {
return o1.get(1).compareTo(o2.get(0));
}
});
backtracking(tickets, 0, "", new boolean[tickets.size()]);
return result;
}
public void backtracking(List<List<String>> 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<String> 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<String> findItinerary1(List<List<String>> 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<List<String>> 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<String, PriorityQueue<String>> map = new HashMap<String, PriorityQueue<String>>();
List<String> itinerary = new LinkedList<String>();
public List<String> findItinerary2(List<List<String>> tickets) {
for (List<String> ticket : tickets) {
String src = ticket.get(0), dst = ticket.get(1);
if (!map.containsKey(src)) {
map.put(src, new PriorityQueue<String>());
}
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<String> resList = new LinkedList<>();
public List<String> findItinerary3(List<List<String>> tickets) {
for (List<String> ticket : tickets) {
String src = ticket.get(0);
String dst = ticket.get(1);
if (!map.containsKey(src)) {
PriorityQueue<String> pq = new PriorityQueue<>();
map.put(src, pq);
}
map.get(src).add(dst);
}
dfs1("JFK");
return resList;
}
private void dfs1(String src) {
PriorityQueue<String> pq = map.get(src);
while (pq != null && !pq.isEmpty()){
dfs1(pq.poll());
}
((LinkedList<String>) resList).addFirst(src);
}
}

View File

@ -17,7 +17,7 @@ import java.util.*;
* 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案该方案中 'Q' '.' 分别代表了皇后和空位 * 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案该方案中 'Q' '.' 分别代表了皇后和空位
* @Version: 1.0 * @Version: 1.0
*/ */
public class SolveNQueens { public class T13_1_SolveNQueens {
@Test @Test
public void test() { public void test() {

View File

@ -20,7 +20,7 @@ import java.util.List;
* 数独部分空格内已填入了数字空白格用 '.' 表示 * 数独部分空格内已填入了数字空白格用 '.' 表示
* @Version: 1.0 * @Version: 1.0
*/ */
public class SolveSudoku { public class T14_SolveSudoku {
@Test @Test
public void test() { public void test() {

View File

@ -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<List<Integer>> result = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
public List<List<Integer>> 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<Integer> 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);
}
}
}

View File

@ -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<List<Integer>> result = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
public List<List<Integer>> 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);
}
}
}

View File

@ -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<String> result = new ArrayList<>();
StringBuilder builder = new StringBuilder();
Map<Character, char[]> map = new HashMap<Character, char[]>() {{
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<String> 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);
}
}
}

View File

@ -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<List<Integer>> result = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
public List<List<Integer>> 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<List<Integer>> con,int target,int[] form,List<Integer> cur,int sum,int start){
//dfs
if(sum==target) {
con.add(new ArrayList<Integer>(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<List<Integer>> combinationSum2(int[] candidates, int target) {
int[] form=new int[51];
for(int i=0;i<candidates.length;++i){
++form[candidates[i]];
}
List<List<Integer>> con=new ArrayList<List<Integer>>();
deal(con,target,form,new ArrayList<Integer>(),0,1);
return con;
}
}

View File

@ -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<Integer> cur=new ArrayList<>();
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> 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);
}
}
}

View File

@ -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<List<String>> result = new ArrayList<>();
List<String> cur = new ArrayList<>();
boolean[][] flag;
public List<List<String>> 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+1j-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 <s.length(); j++) {
if(i==j) flag[i][j]=true;
else if(j==i+1) flag[i][j]=chars[i]==chars[j];//相差1的时候只需要判断这两个数是否相等
else flag[i][j] = chars[i] == chars[j] && flag[i + 1][j - 1];
}
}
}
/**
* 根据一维dp优化
* @param s
*/
public void computePalindrome2(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 <s.length(); j++) {
if(i==j) flag[i][j]=true;
else if(j==i+1) flag[i][j]=chars[i]==chars[j];//相差1的时候只需要判断这两个数是否相等
else flag[i][j] = chars[i] == chars[j] && flag[i + 1][j - 1];
}
}
}
}

View File

@ -0,0 +1,130 @@
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-01 12:36
*@Description:
* TODO 二刷力扣93题 复原IP地址:
* 有效 IP 地址 正好由四个整数每个整数位于 0 255 之间组成且不能含有前导 0整数之间用 '.' 分隔
* 例如"0.1.2.201" "192.168.1.1" 有效 IP 地址但是 "0.011.255.245""192.168.1.312" "192.168@1.1" 无效 IP 地址
* 给定一个只包含数字的字符串 s 用以表示一个 IP 地址返回所有可能的有效 IP 地址这些地址可以通过在 s 中插入 '.' 来形成 不能 重新排序或删除 s 中的任何数字你可以按 任何 顺序返回答案
*@Version: 1.0
*/
public class T07_RestoreIpAddresses {
@Test
public void test() {
String s = "25525511135";
String s1 = "0000";
System.out.println(restoreIpAddresses(s1));
}
StringBuilder builder = new StringBuilder();
List<String> result = new ArrayList<>();
/**
* 本质上类似于切割回文T06核心在于最后一层一定要切完
* @param s
* @return
*/
public List<String> 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<String> res = new ArrayList<>();
int[] segment = new int[4];
public List<String> 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;
}
}
}
}

View File

@ -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<List<Integer>> result = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
public List<List<Integer>> 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);
}
}
}
}

View File

@ -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<Integer, Integer> map = new HashMap<Integer, Integer>();//<num,count>
List<Integer> key = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> 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<List<Integer>> 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);
}
}
}

View File

@ -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<Integer> cur = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
/**
* 与上一题子集II类似都是需要树层去重即可
* @param nums
* @return
*/
public List<List<Integer>> 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<Integer> 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<List<Integer>> findSubsequences1(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
if (nums == null || nums.length == 0) return list;
List<Integer> result = new ArrayList<>();
dfs(0, nums, result, list, -101);
return list;
}
private void dfs(int idx, int[] nums, List<Integer> result, List<List<Integer>> 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;
}
}

View File

@ -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<Integer> cur = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
boolean[] used;
/**
* 全排列的题实际上就是顺序也有关系所以直接不用传startIndex,每次都从0开始就好
* 把used放外面速度击败82.16%内存击败28.47% 1ms
* @param nums
* @return
*/
public List<List<Integer>> 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<List<Integer>> 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;
}
}
}
}

View File

@ -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<Integer> cur = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
/**
* 要做的事似乎还是树层去重
* @param nums
* @return
*/
public List<List<Integer>> 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<List<Integer>> 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;
}
}
}
}

View File

@ -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<List<String>> result = new ArrayList<>();
List<String> cur = new ArrayList<>();
public List<List<String>> 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<List<String>> res = new ArrayList<>();
public List<List<String>> 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<String> 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<List<String>> solveNQueens2(int n) {
List<List<String>> 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<List<String>> ans,int n,int[] queen,int rows,int col,int diagonal1,int diagonal2){
if(rows==n){
List<String> list=new ArrayList<>();
for(int i=0;i<n;i++){
char[] row=new char[n];
Arrays.fill(row,'.');
row[queen[i]]='Q';
list.add(new String(row));
}
ans.add(list);
}else{
int available=((1<<n)-1) & (~(col | diagonal1 | diagonal2));
while(available!=0){
int position=available&(-available);
available=available & (available-1);
queen[rows]=Integer.bitCount(position-1);
backtrack(ans,n,queen,rows+1,position|col,(position|diagonal1)<<1,(position|diagonal2)>>1);
queen[rows]=-1;
}
}
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

@ -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]<min) {
min = prices[i];
continue;
}
max=Math.max(prices[i]-min,max);
}
return max;
}
/**
* 官方贪心法与本人的贪心类似比本人快第一个if,并将Math.max替换为下面的else if
* 速度击败100%内存击败30.17%
* @param prices
* @return
*/
public int maxProfit2(int prices[]) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > 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];
}
}

View File

@ -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;
}
}

View File

@ -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<prices.length;++i){
dp[i] = Math.max(dp[i-1], prices[i]-minVal);
minVal = Math.min(prices[i], minVal);
}
//这个循环本质上是获取第i天后的获得的最大值
for(int i=prices.length-2;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);
}
}

View File

@ -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];
}
}

View File

@ -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]=0dp[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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 rl < 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]<nums[right]){
count++;
if(count>max){
max=count;
}
}else{
count=1;
}
}
return max;
}
}

View File

@ -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<Long> bucketA = new HashSet<Long>();
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<Long> 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;
}
}

View File

@ -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) {
//动态规划
//fn f(n-1)
//f(1) 递推
//确保text1最长
if(text1.length()<text2.length()){
return longestCommonSubsequence4(text2, text1);
}
char []chs1=text1.toCharArray();
char []chs2=text2.toCharArray();//转换字符串直接操作
int m=chs1.length;//记录长度1
int n=chs2.length;//记录长度2
int []dp=new int[n];//动态化,默认都是0
//abcde m
//axe n
//xab n
//abcde
//a a在text1里面dp[0]=1
//x x不在在text1里面dp[0]=0
//abcde m fn f(n-1)
//ax x不在在text1里面 dp[n]=dp[n-1]
//xa a在text1里面 dp[n]=dp[n-1]+1
int pre=0;//记录上一个
int cur=0;//当下一个
for(int i=0;i<m;i++){
char c=chs1[i];//取出第一个字母
if(c==chs2[0]){
dp[0]=1;
}
pre=dp[0];//记录上一个
for(int j=1;j<n;j++){//跳过第一个字符
cur=dp[j];//当前的数据默认0
if(c==chs2[j]){
dp[j]=pre+1;//dp[n]=dp[n-1]+1
}else{
dp[j]=Math.max(cur,dp[j-1]);//保存最大值
}
pre=cur;//记录上一个
}
}
return dp[n-1];//返回最后一个
}
}

View File

@ -0,0 +1,110 @@
package com.markilue.leecode.dynamic;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.dynamic
*@Author: dingjiawen
*@CreateTime: 2022-12-18 10:05
*@Description:
* TODO 力扣1035题 不相交的线:
* 在两条独立的水平线上按给定的顺序写下 nums1 nums2 中的整数
* 现在可以绘制一些连接两个数字 nums1[i] nums2[j] 的直线这些直线需要同时满足满足
* nums1[i] == nums2[j]
* 且绘制的直线不与任何其他连线非水平线相交
* 请注意连线即使在端点也不能相交每个数字只能属于一条连线
* 以这种方法绘制线条并返回可以绘制的最大连线数
*@Version: 1.0
*/
public class T31_MaxUncrossedLines {
@Test
public void test(){
int[] nums1 = {2, 5, 1, 2, 5};
int[] nums2 = {10, 5, 2, 1, 5, 2};
System.out.println(maxUncrossedLines1(nums1,nums2));
}
/**
* 思路本质上还是想让两个数相等但是对两个相等的数有要求,本质上好像就是子序列问题因为子序列可删可不删
* TODO 动态规划法
* (1)dp定义: dp[i][j]表示使用nums1[0-i]和nums2[0-j]的最大连线数
* (2)dp状态转移方程:if nums1[i-1]==nums2[j-1] 则dp[i][j]=dp[i-1][j-1]+1
* else dp[i][j]=max(dp[i][j-1],dp[i-1][j])
* 这里使用nums1[i-1]==nums2[j-1]主要是为了初始化方便
* (3)dp初始化:dp[0][i]=0 dp[i][0]=0;
* (4)dp遍历顺序:两个for可以任意调换
* (5)dp举例推导 以nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]为例
* [n 10 5 2 1 5 2]
* n 0 0 0 0 0 0 0
* 2: 0 0 0 1 1 1 1
* 5: 0 0 1 1 1 2 2
* 1: 0 0 1 1 2 2 2
* 2: 0 0 1 2 2 2 3
* 5: 0 0 1 2 2 3 3
* 速度击败99.45%内存击败34.1%
* @param nums1
* @param nums2
* @return
*/
public int maxUncrossedLines(int[] nums1, int[] nums2) {
if(nums1.length<nums2.length){
return maxUncrossedLines(nums2,nums1);
}
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
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;
}else {
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[nums1.length][nums2.length];
}
/**
* 官方最快题解本质上是一个一维数组优化dp
* 速度击败100% 内存击败97.81%
* 2ms
* @param nums1
* @param nums2
* @return
*/
public int maxUncrossedLines1(int[] nums1, int[] nums2) {
int[] dp = new int[nums2.length];
int rs = 0;
for (int j = 0; j < nums2.length; j++) {
if (nums1[0] == nums2[j]) {
dp[j] = 1;
rs = 1;
}
}
for (int i = 1; i < nums1.length; i++) {
int tmpMax = 0;
for (int j = 0; j < nums2.length; j++) {
int tmp = dp[j];
if (nums1[i] == nums2[j]) {
dp[j] = tmpMax + 1; //这里之所以可以直接tmpMax + 1是因为两个相邻的数最多在增加一条线
}
if (tmp > tmpMax) tmpMax = tmp;//tmp实际上记录的是dp[i-1][j];tmpMax实际上记录的是dp[i][j-1]
if (dp[j] > rs) {
rs = dp[j];
}
}
}
return rs;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -19,7 +19,7 @@ import java.util.Comparator;
* *
* @Version: 1.0 * @Version: 1.0
*/ */
public class FindContentChildren { public class T01_FindContentChildren {
@Test @Test

View File

@ -15,7 +15,7 @@ import org.junit.Test;
* 给你一个整数数组 nums 返回 nums 中作为 摆动序列 最长子序列的长度 * 给你一个整数数组 nums 返回 nums 中作为 摆动序列 最长子序列的长度
* @Version: 1.0 * @Version: 1.0
*/ */
public class WiggleMaxLength { public class T02_WiggleMaxLength {
@Test @Test

View File

@ -12,7 +12,7 @@ import org.junit.Test;
* 子数组 是数组中的一个连续部分 * 子数组 是数组中的一个连续部分
* @Version: 1.0 * @Version: 1.0
*/ */
public class MaxSubArray { public class T03_MaxSubArray {
@Test @Test
public void test(){ public void test(){

View File

@ -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;
}
}

View File

@ -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]<num[i-1]+1)
* 3)dp初始化:dp[0][0]=1;dp[0][1]=1;
* 4)dp遍历顺序:从前往后
* 5)dp举例推导: nums = [1,17,5,10,13,15,10,5,16,8]为例
* [0 1]
* 1: 1 1
* 17: 2 1
* 5: 2 3
* 10: 4 3
* 13: 4 3
* 15: 4 3
* 10: 4 4
* 5: 4 5
* 16: 6 5
* 8: 6 7
* @param nums
* @return
*/
public int wiggleMaxLength2(int[] nums) {
int n = nums.length;
if (n < 2) {
return n;
}
int[] up = new int[n];
int[] down = new int[n];
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[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]);
}
}

View File

@ -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);
}
}

View File

@ -14,7 +14,7 @@ import java.util.HashMap;
* 注意若s t中每个字符出现的次数都相同则称s t互为字母异位词 * 注意若s t中每个字符出现的次数都相同则称s t互为字母异位词
* @Version: 1.0 * @Version: 1.0
*/ */
public class IsAnagram { public class T01_IsAnagram {
@Test @Test

View File

@ -17,7 +17,7 @@ import java.util.Set;
* 给定两个数组 nums1 nums2 返回 它们的交集 输出结果中的每个元素一定是 唯一 我们可以 不考虑输出结果的顺序 * 给定两个数组 nums1 nums2 返回 它们的交集 输出结果中的每个元素一定是 唯一 我们可以 不考虑输出结果的顺序
* @Version: 1.0 * @Version: 1.0
*/ */
public class Intersection { public class T02_Intersection {
@Test @Test
public void test(){ public void test(){

View File

@ -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<Integer> 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<Integer> 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;
}
}

View File

@ -19,7 +19,7 @@ import java.util.HashMap;
* @Version: 1.0 * @Version: 1.0
*/ */
public class TwoSum { public class T04_TwoSum {
@Test @Test
public void test(){ public void test(){

View File

@ -19,7 +19,7 @@ import java.util.HashSet;
* 2) nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0 * 2) nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
* @Version: 1.0 * @Version: 1.0
*/ */
public class FourSumCount { public class T05_FourSumCount {
@Test @Test

View File

@ -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;
}
}

View File

@ -17,7 +17,7 @@ import java.util.List;
* 注意答案中不可以包含重复的三元组 * 注意答案中不可以包含重复的三元组
* @Version: 1.0 * @Version: 1.0
*/ */
public class ThreeSum { public class T07_ThreeSum {
/** /**
* 这道题使用哈希法的思路: * 这道题使用哈希法的思路:

View File

@ -17,7 +17,7 @@ import java.util.*;
* 你可以按 任意顺序 返回答案 * 你可以按 任意顺序 返回答案
* @Version: 1.0 * @Version: 1.0
*/ */
public class FourSum { public class T08_FourSum {
@Test @Test
public void test() { public void test() {

View File

@ -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;
}
}

View File

@ -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<Integer> 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<Integer> 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<integers.size();i++){
ints[i] = integers.get(i);
}
return ints;
}
}

View File

@ -0,0 +1,68 @@
package com.markilue.leecode.hashtable.second;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.hashtable.second
*@Author: dingjiawen
*@CreateTime: 2022-12-27 11:36
*@Description:
* TODO 二刷1题 两数之和:
* 给定一个整数数组 nums 和一个整数目标值 target请你在该数组中找出 和为目标值 target 的那 两个 整数并返回它们的数组下标
* 你可以假设每种输入只会对应一个答案但是数组中同一个元素在答案里不能重复出现
* 你可以按任意顺序返回答案
*@Version: 1.0
*/
public class T04_TwoSum {
@Test
public void test(){
int[] nums = {2, 7, 11, 15};
int target = 9;
System.out.println(Arrays.toString(twoSum(nums,target)));
}
@Test
public void test1(){
int[] nums = {3,2,4};
int target = 6;
System.out.println(Arrays.toString(twoSum(nums,target)));
}
@Test
public void test2(){
int[] nums = {3,3};
int target = 6;
System.out.println(Arrays.toString(twoSum(nums,target)));
}
/**
* 思路这题使用动归法可以本质上就是一个target为9的背包问题但是动归法只能知道能不能凑成target不能知道过程是怎么凑出来的
* 由于只有一种答案可以使用hash法<target-num,num对应的i>
* 速度击败78.74%内存击败14.5%
* @param nums
* @param target
* @return
*/
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> 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];
}
}

View File

@ -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
* 给你四个整数数组 nums1nums2nums3 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<sum,count>
* 速度击败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<Integer, Integer> map = new HashMap<>();
for (int i : nums1) {
for (int i1 : nums2) {
map.put(i+i1,map.getOrDefault(i+i1,0)+1);
}
}
HashMap<Integer, Integer> 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<Integer, Integer> countAB = new HashMap<Integer, Integer>();
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<Integer, Integer> 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;
}
}

View File

@ -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 != ji != 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<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> 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<right&&nums[left-1]==nums[left])left++;
while (left<right&&nums[right]==nums[right+1])right--;
}
}
}
return result;
}
/**
* 官方最快9ms
* 但是与本人几乎完全一致所以经过测试是电脑原因
* @param nums
* @return
*/
public List<List<Integer>> threeSum1(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(nums.length < 3) return res;
Arrays.sort(nums);
for(int i=0;i<nums.length-2;i++){
if (i == 0 || (i > 0 && nums[i] != nums[i-1])) {
int j = i+1;
int k = nums.length-1;
while(j<k){
if(nums[i]+nums[j]+nums[k]==0){
List<Integer> cur = new ArrayList<Integer>();
cur.add(nums[i]);
cur.add(nums[j]);
cur.add(nums[k]);
res.add(cur);
while(j<k && nums[j]==nums[j+1])j++;
while(j<k && nums[k]==nums[k-1])k--;
j++;
k--;
}else if(nums[i]+nums[j]+nums[k] > 0) k--;
else j++;
}
}
}
return res;
}
}

View File

@ -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
* abc 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<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> 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<List<Integer>> fourSum1(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> 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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -6,7 +6,7 @@ import org.junit.Test;
* 移除链表元素: * 移除链表元素:
* 给你一个链表的头节点 head 和一个整数 val 请你删除链表中所有满足 Node.val == val 的节点并返回 新的头节点 * 给你一个链表的头节点 head 和一个整数 val 请你删除链表中所有满足 Node.val == val 的节点并返回 新的头节点
*/ */
public class RemoveElement { public class T01_RemoveElement {
@Test @Test

View File

@ -22,11 +22,11 @@ import org.junit.Test;
* obj.addAtIndex(index,val); * obj.addAtIndex(index,val);
* obj.deleteAtIndex(index); * obj.deleteAtIndex(index);
*/ */
public class MyLinkedList { public class T02_MyLinkedList {
public ListNode head; public ListNode head;
public MyLinkedList() { public T02_MyLinkedList() {
} }
@ -163,7 +163,7 @@ public class MyLinkedList {
@Test @Test
public void test(){ public void test(){
MyLinkedList linkedList = new MyLinkedList(); T02_MyLinkedList linkedList = new T02_MyLinkedList();
linkedList.addAtHead(1); linkedList.addAtHead(1);
printList(linkedList.head); printList(linkedList.head);
System.out.println(); System.out.println();
@ -185,7 +185,7 @@ public class MyLinkedList {
} }
@Test @Test
public void test1(){ public void test1(){
MyLinkedList linkedList = new MyLinkedList(); T02_MyLinkedList linkedList = new T02_MyLinkedList();
linkedList.addAtHead(7); linkedList.addAtHead(7);
printList(linkedList.head); printList(linkedList.head);
@ -231,7 +231,7 @@ public class MyLinkedList {
} }
@Test @Test
public void test2(){ public void test2(){
MyLinkedList linkedList = new MyLinkedList(); T02_MyLinkedList linkedList = new T02_MyLinkedList();
linkedList.addAtHead(2); linkedList.addAtHead(2);
printList(linkedList.head); printList(linkedList.head);
@ -269,7 +269,7 @@ public class MyLinkedList {
@Test @Test
public void test3(){ public void test3(){
MyLinkedList linkedList = new MyLinkedList(); T02_MyLinkedList linkedList = new T02_MyLinkedList();
linkedList.addAtHead(1); linkedList.addAtHead(1);
printList(linkedList.head); printList(linkedList.head);

View File

@ -22,13 +22,13 @@ import org.junit.Test;
* obj.addAtIndex(index,val); * obj.addAtIndex(index,val);
* obj.deleteAtIndex(index); * obj.deleteAtIndex(index);
*/ */
public class MyLinkedList1 { public class T02_MyLinkedList1 {
//该head为虚拟节点,不存放任何数据 //该head为虚拟节点,不存放任何数据
public ListNode head; public ListNode head;
public int size; public int size;
public MyLinkedList1() { public T02_MyLinkedList1() {
head = new ListNode(0); head = new ListNode(0);
size = 0; size = 0;
} }
@ -122,7 +122,7 @@ public class MyLinkedList1 {
@Test @Test
public void test() { public void test() {
MyLinkedList1 linkedList = new MyLinkedList1(); T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
linkedList.addAtHead(1); linkedList.addAtHead(1);
printList(linkedList.head); printList(linkedList.head);
System.out.println(); System.out.println();
@ -145,7 +145,7 @@ public class MyLinkedList1 {
@Test @Test
public void test1() { public void test1() {
MyLinkedList1 linkedList = new MyLinkedList1(); T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
linkedList.addAtHead(7); linkedList.addAtHead(7);
printList(linkedList.head); printList(linkedList.head);
@ -192,7 +192,7 @@ public class MyLinkedList1 {
@Test @Test
public void test2() { public void test2() {
MyLinkedList1 linkedList = new MyLinkedList1(); T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
linkedList.addAtHead(2); linkedList.addAtHead(2);
printList(linkedList.head); printList(linkedList.head);
@ -230,7 +230,7 @@ public class MyLinkedList1 {
@Test @Test
public void test3() { public void test3() {
MyLinkedList1 linkedList = new MyLinkedList1(); T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
linkedList.addAtHead(1); linkedList.addAtHead(1);
printList(linkedList.head); printList(linkedList.head);
@ -254,7 +254,7 @@ public class MyLinkedList1 {
@Test @Test
public void test4() { public void test4() {
MyLinkedList1 linkedList = new MyLinkedList1(); T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
linkedList.addAtHead(4); linkedList.addAtHead(4);
printList(linkedList.head); 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;
}
}

View File

@ -10,7 +10,7 @@ import org.junit.Test;
* @Description: TODO leecode第206题翻转链表给你单链表的头节点 head ,请你反转链表并返回反转后的链表 * @Description: TODO leecode第206题翻转链表给你单链表的头节点 head ,请你反转链表并返回反转后的链表
* @Version: 1.0 * @Version: 1.0
*/ */
public class ReverseList { public class T03_ReverseList {
@Test @Test

View File

@ -2,7 +2,7 @@ package com.markilue.leecode.listnode;
public class swapPairs { public class T04_swapPairs {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -3,7 +3,7 @@ package com.markilue.leecode.listnode;
/** /**
* 删除链表的倒数第N个节点 * 删除链表的倒数第N个节点
*/ */
public class removeNthFromEnd { public class T05_removeNthFromEnd {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -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<ListNode> 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;
}
}

View File

@ -19,7 +19,7 @@ import java.util.HashSet;
* @Version: 1.0 * @Version: 1.0
*/ */
public class DetectCycle { public class T07_DetectCycle {
@Test @Test

View File

@ -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;
}
}

View File

@ -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);
*/

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -1,6 +1,6 @@
package com.markilue.leecode.listnode.selftry; package com.markilue.leecode.listnode.selftry;
import com.markilue.leecode.listnode.swapPairs; import com.markilue.leecode.listnode.T04_swapPairs;
public class reverseKGroup { public class reverseKGroup {
@ -16,7 +16,7 @@ public class reverseKGroup {
} }
public static class ListNode { public static class ListNode {
int val; int val;
swapPairs.ListNode next; T04_swapPairs.ListNode next;
ListNode() { ListNode() {
} }
@ -25,7 +25,7 @@ public class reverseKGroup {
this.val = val; this.val = val;
} }
ListNode(int val, swapPairs.ListNode next) { ListNode(int val, T04_swapPairs.ListNode next) {
this.val = val; this.val = val;
this.next = next; this.next = next;
} }

View File

@ -1,7 +1,6 @@
package com.markilue.leecode.stackAndDeque; package com.markilue.leecode.stackAndDeque;
import org.junit.Test; import org.junit.Test;
import org.omg.CORBA.PUBLIC_MEMBER;
import java.util.Stack; import java.util.Stack;
@ -27,12 +26,12 @@ import java.util.Stack;
* @Version: 1.0 * @Version: 1.0
*/ */
public class MyQueue { public class T01_MyQueue {
public Stack<Integer> stack1; public Stack<Integer> stack1;
public Stack<Integer> stack2; public Stack<Integer> stack2;
public MyQueue() { public T01_MyQueue() {
stack1=new Stack<>(); stack1=new Stack<>();
stack2=new Stack<>(); stack2=new Stack<>();
@ -88,7 +87,7 @@ public class MyQueue {
@Test @Test
public void test(){ public void test(){
MyQueue myQueue = new MyQueue(); T01_MyQueue myQueue = new T01_MyQueue();
myQueue.push(1); // queue is: [1] myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
System.out.println(myQueue.peek());// return 1 System.out.println(myQueue.peek());// return 1

View File

@ -26,12 +26,12 @@ import java.util.Stack;
* @Version: 1.0 * @Version: 1.0
*/ */
public class MyQueue1 { public class T01_MyQueue1 {
public Stack<Integer> stack1; public Stack<Integer> stack1;
public Stack<Integer> stack2; public Stack<Integer> stack2;
public MyQueue1() { public T01_MyQueue1() {
stack1=new Stack<>(); stack1=new Stack<>();
stack2=new Stack<>(); stack2=new Stack<>();
@ -76,7 +76,7 @@ public class MyQueue1 {
@Test @Test
public void test(){ public void test(){
MyQueue1 myQueue = new MyQueue1(); T01_MyQueue1 myQueue = new T01_MyQueue1();
myQueue.push(1); // queue is: [1] myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
System.out.println(myQueue.peek());// return 1 System.out.println(myQueue.peek());// return 1

Some files were not shown because too many files have changed in this diff Show More