Merge branch 'master' of https://gitee.com/dingjiawen/self_example
This commit is contained in:
commit
ad8da1d5ee
|
|
@ -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,来了老弟
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
/*
|
||||
---发送方启动中---
|
||||
服务器收到了
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ package com.markilue.leecode.array;
|
|||
* 描述:给定一个n个元素有序的(升序)整型数组nums 和一个目标值target ,写一个函数搜索nums中的 target,如果目标值存在返回下标,否则返回 -1。
|
||||
*
|
||||
*/
|
||||
public class BinarySearch {
|
||||
public class T01_BinarySearch {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int[] nums = {-1,0,3,5,9,12};
|
||||
|
|
@ -11,7 +11,7 @@ import java.util.Arrays;
|
|||
* 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
|
||||
* 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
|
||||
*/
|
||||
public class RemoveElement {
|
||||
public class T02_RemoveElement {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -7,7 +7,7 @@ import javax.management.remote.rmi._RMIConnection_Stub;
|
|||
/**
|
||||
* 寻找长度最小的子数组
|
||||
*/
|
||||
public class MinSubArrayLen {
|
||||
public class T03_MinSubArrayLen {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -8,7 +8,7 @@ import java.util.Arrays;
|
|||
* 螺旋矩阵:
|
||||
* 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix
|
||||
*/
|
||||
public class generateMatrix {
|
||||
public class T04_generateMatrix {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import java.util.List;
|
|||
* 你可以按 任何顺序 返回答案。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class Combine {
|
||||
public class T01_Combine {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||
* 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class combinationSum3 {
|
||||
public class T02_CombinationSum3 {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.markilue.leecode.backtrace;
|
||||
|
||||
import com.markilue.leecode.stackAndDeque.EvalRPN;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -15,7 +14,7 @@ import java.util.*;
|
|||
* 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class IetterCombinations {
|
||||
public class T03_LetterCombinations {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||
* 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class combinationSum {
|
||||
public class T04_CombinationSum {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -15,7 +15,7 @@ import java.util.*;
|
|||
* 注意:解集不能包含重复的组合。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class combinationSum2 {
|
||||
public class T05_CombinationSum2 {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -15,7 +15,7 @@ import java.util.List;
|
|||
* 回文串 是正着读和反着读都一样的字符串。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class partition {
|
||||
public class T06_Partition {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||
* 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class RestoreIpAddresses {
|
||||
public class T07_RestoreIpAddresses {
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
|
|
@ -16,7 +16,7 @@ import java.util.List;
|
|||
* 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class Subsets {
|
||||
public class T08_Subsets {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
package com.markilue.leecode.backtrace;
|
||||
|
||||
import com.markilue.leecode.stackAndDeque.EvalRPN;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.print.DocFlavor;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +14,7 @@ import java.util.*;
|
|||
* 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class SubsetsWithDup {
|
||||
public class T09_SubsetsWithDup {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.Set;
|
|||
* 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class FindSubsequences {
|
||||
public class T10_FindSubsequences {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -15,7 +15,7 @@ import java.util.List;
|
|||
* 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class Permute {
|
||||
public class T11_Permute {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -16,7 +16,7 @@ import java.util.List;
|
|||
* 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PermuteUnique {
|
||||
public class T12_PermuteUnique {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.*;
|
|||
* 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class SolveNQueens {
|
||||
public class T13_1_SolveNQueens {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -20,7 +20,7 @@ import java.util.List;
|
|||
* 数独部分空格内已填入了数字,空白格用 '.' 表示。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class SolveSudoku {
|
||||
public class T14_SolveSudoku {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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+1,j-1所以需要从下到上,左到右
|
||||
* 4)dp初始化:dp[i][i]=true;dp[i][i>j]=true
|
||||
* 5)dp距离推导: s="aab"
|
||||
* [a a b]
|
||||
* i=0: t t f
|
||||
* i=1: t t f
|
||||
* i=2: t t t
|
||||
*
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public void computePalindrome(String s) {
|
||||
char[] chars = s.toCharArray();
|
||||
//初始化,下三角全为true
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
for (int j = 0; j <= i; j++) {
|
||||
flag[i][j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = s.length() - 2; i >= 0; i--) {
|
||||
for (int j = s.length() - 1; j > i; j--) {
|
||||
flag[i][j] = chars[i] == chars[j] && flag[i + 1][j - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据官方的优化
|
||||
* @param s
|
||||
*/
|
||||
public void computePalindrome1(String s) {
|
||||
char[] chars = s.toCharArray();
|
||||
// //初始化,下三角全为true
|
||||
// for (int i = 0; i < s.length(); i++) {
|
||||
// for (int j = 0; j <= i; j++) {
|
||||
// flag[i][j] = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
for (int i = s.length() - 1; i >= 0; i--) {
|
||||
for (int j = i; j <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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.dynamic
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-14 09:45
|
||||
*@Description:
|
||||
* TODO 力扣309题 最佳买卖股票时机含冷冻期:
|
||||
* 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
|
||||
* 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
|
||||
* 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
|
||||
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T25_MaxProfit {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int[] prices = {1, 2, 3, 0, 2};
|
||||
System.out.println(maxProfit(prices));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:正如题目所说:对应的交易状态有: [买入, 卖出, 冷冻期],由此可以列举状态转移方程
|
||||
* TODO 动归五部曲:
|
||||
* (1)dp定义:dp[i][0]表示第i天处于买入了的状态最多的利润;dp[i][1]表示第i天处于卖出的状态最多的利润
|
||||
* dp[i][2]表示第i天处于冷冻期的状态最多的利润;dp[i][3]表示第i天非冷冻期的状态最多的利润
|
||||
* (2)dp状态转移方程:
|
||||
* 1.dp[i][0]=昨天买入了今天没变化;昨天处于冷冻期今天买入了;除了第一次以外,下次再买一定是冷冻期之后的买
|
||||
* dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i],-prices[i])
|
||||
* 2.dp[i][1]=昨天处于冷冻期今天没变化;昨天买今天卖入了;
|
||||
* dp[i][1]=max(dp[i-1][2],dp[i-1][0]+prices[i],dp[i-1][1])
|
||||
* 2.dp[i][2]=昨天卖了今天处于冷冻期;昨天处于冷冻期今天没变化
|
||||
* dp[i][2]=max(dp[i-1][1],dp[i-1][2])
|
||||
* (3)dp初始化:dp[0][0]=-prices[0];dp[0][1]=0;dp[0][2]=0
|
||||
* (4)dp遍历顺序:从前往后
|
||||
* (5)dp举例推导: 以prices = [1,2,3,0,2]为例
|
||||
* [0 1 2]
|
||||
* i=0 -1 0 0
|
||||
* i=1 -1 1 0
|
||||
* i=2 -1 2 1
|
||||
* i=3 1 2 2
|
||||
* i=4 1 3 2
|
||||
* 速度击败77.78%,内存击败46.23% 1ms
|
||||
* @param prices
|
||||
* @return
|
||||
*/
|
||||
public int maxProfit(int[] prices) {
|
||||
|
||||
int[][] dp = new int[prices.length][3];
|
||||
dp[0][0]=-prices[0];dp[0][1]=0;dp[0][2]=0;
|
||||
|
||||
for (int i = 1; i < prices.length; i++) {
|
||||
dp[i][0]=Math.max(Math.max(dp[i-1][0],dp[i-1][2]-prices[i]),-prices[i]);
|
||||
dp[i][1]=Math.max(Math.max(dp[i-1][2],dp[i-1][0]+prices[i]),dp[i-1][1]);
|
||||
dp[i][2]=Math.max(dp[i-1][1],dp[i-1][2]);
|
||||
}
|
||||
return dp[prices.length-1][1];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 代码随想录动态规划法:具体状态转移推导,见笔记
|
||||
* 把交易的状态分为了四种:
|
||||
* 1.状态一:买入股票状态(今天买入股票,或者是之前就买入了股票然后没有操作)
|
||||
* 2.卖出股票状态,这里就有两种卖出股票状态
|
||||
* 状态二:两天前就卖出了股票,度过了冷冻期,一直没操作,今天保持卖出股票状态
|
||||
* 状态三:今天卖出了股票
|
||||
* 3.状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天
|
||||
* 速度击败77.78%,内存击败27.95%
|
||||
* @param prices
|
||||
* @return
|
||||
*/
|
||||
public int maxProfit1(int[] prices) {
|
||||
int n=prices.length;
|
||||
if (prices == null || n < 2) {
|
||||
return 0;
|
||||
}
|
||||
int[][] dp = new int[n][4];
|
||||
|
||||
// bad case
|
||||
dp[0][0] = -prices[0];
|
||||
dp[0][1] = 0;
|
||||
dp[1][0] = 0;
|
||||
dp[1][1] = 0;
|
||||
|
||||
for (int i = 1; i < prices.length; i++) {
|
||||
// dp公式
|
||||
dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);
|
||||
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
|
||||
dp[i][2] = dp[i - 1][0] + prices[i];
|
||||
dp[i][3] = dp[i - 1][2];
|
||||
}
|
||||
|
||||
return Math.max(dp[n - 1][3],Math.max(dp[n - 1][1], dp[n - 1][2]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 官方三状态法:
|
||||
* 速度击败77.78%,内存击败52.16%
|
||||
* @param prices
|
||||
* @return
|
||||
*/
|
||||
public int maxProfit2(int[] prices) {
|
||||
if (prices.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int n = prices.length;
|
||||
// f[i][0]: 手上持有股票的最大收益
|
||||
// f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益;这里的「处于冷冻期」指的是在第 i 天结束之后的状态
|
||||
// f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益;由于不能在冷冻期,所以今天不能卖股票
|
||||
int[][] f = new int[n][3];
|
||||
f[0][0] = -prices[0];
|
||||
for (int i = 1; i < n; ++i) {
|
||||
f[i][0] = Math.max(f[i - 1][0], f[i - 1][2] - prices[i]);//昨天就持有了,或者没有没持有今天买;
|
||||
f[i][1] = f[i - 1][0] + prices[i]; //必须是今天刚卖;
|
||||
f[i][2] = Math.max(f[i - 1][1], f[i - 1][2]); //昨天处于冷冻期;昨天过了冷冻期但是没操作;
|
||||
}
|
||||
return Math.max(f[n - 1][1], f[n - 1][2]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 官方滚动数组优化:
|
||||
* 速度击败100%,内存击败79.39%
|
||||
* @param prices
|
||||
* @return
|
||||
*/
|
||||
public int maxProfit3(int[] prices) {
|
||||
if (prices.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int n = prices.length;
|
||||
int f0 = -prices[0];
|
||||
int f1 = 0;
|
||||
int f2 = 0;
|
||||
for (int i = 1; i < n; ++i) {
|
||||
int newf0 = Math.max(f0, f2 - prices[i]);
|
||||
int newf1 = f0 + prices[i];
|
||||
int newf2 = Math.max(f1, f2);
|
||||
f0 = newf0;
|
||||
f1 = newf1;
|
||||
f2 = newf2;
|
||||
}
|
||||
|
||||
return Math.max(f1, f2);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.dynamic
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-15 11:58
|
||||
*@Description:
|
||||
* TODO leetcode674题 最长连续递增序列:
|
||||
* 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
|
||||
* 连续递增的子序列 可以由两个下标 l 和 r(l < r)确定
|
||||
* 如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1]
|
||||
* 那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T28_FindLengthOfLCIS {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int[] nums = {1, 3, 5,5,6, 4, 7};
|
||||
System.out.println(findLengthOfLCIS2(nums));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:核心在于连续递增,所以甚至比T27更简单,更好找规律
|
||||
* 这里先直接使用贪心,一次遍历
|
||||
* 速度击败99.96%,内存击败51.38% 1ms
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public int findLengthOfLCIS(int[] nums) {
|
||||
|
||||
int min = nums[0];
|
||||
int nowLength=1;
|
||||
int maxLength = 1;
|
||||
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
if (nums[i] <= min) {
|
||||
nowLength=1;
|
||||
}else {
|
||||
nowLength+=1;
|
||||
maxLength=Math.max(maxLength,nowLength);
|
||||
}
|
||||
min = nums[i];
|
||||
}
|
||||
return maxLength;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:核心在于连续递增,所以甚至比T27更简单,更好找规律
|
||||
* 这里尝试动归,一次遍历
|
||||
* TODO 动归五部曲:
|
||||
* (1)dp定义:dp[i]表示使用num[0-i]得到的最长连续子序列
|
||||
* (2)dp状态转移方程:dp[i]=num[i]>num[i-1]?max(dp[i-1],now+1),max(dp[i-1],now)
|
||||
* (3)dp初始化:dp[0]=1
|
||||
* (4)dp遍历顺序:从前往后
|
||||
* (5)dp举例推导:以{1, 3, 5,5,6, 4, 7}为例:
|
||||
* dp=[1,2,3,3,3,3,3]
|
||||
* 速度击败99.96%,内存击败32.74% 1ms
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public int findLengthOfLCIS1(int[] nums) {
|
||||
|
||||
int nowLength=1;
|
||||
int[] dp = new int[nums.length];
|
||||
dp[0]=1;
|
||||
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
if (nums[i] > nums[i-1]) {
|
||||
nowLength+=1;
|
||||
}else {
|
||||
nowLength=1;
|
||||
}
|
||||
dp[i]=Math.max(dp[i-1],nowLength);
|
||||
}
|
||||
return dp[nums.length-1];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 代码随想录思路:自己的思路还是有点贪心的意思,代码随想录不会
|
||||
*速度击败99.96%,内存击败42.32% 1ms
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public int findLengthOfLCIS2(int[] nums) {
|
||||
|
||||
int[] dp = new int[nums.length];
|
||||
Arrays.fill(dp,1);
|
||||
int result=1;
|
||||
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
if (nums[i] > nums[i-1]) {
|
||||
dp[i]=dp[i-1]+1;
|
||||
}
|
||||
if (dp[i] > result) result = dp[i];
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 思路:动态规划滚动数组优化
|
||||
* TODO 动归五部曲:
|
||||
* (1)dp定义:dp[i]表示以num[i]结尾得到的最长连续子序列
|
||||
* (2)dp状态转移方程:if nums[i + 1] > nums[i] 得到dp[i]=dp[i-1]+1
|
||||
* (3)dp初始化:dp[i]=1
|
||||
* (4)dp遍历顺序:从前往后
|
||||
* (5)dp举例推导:以{1, 3, 5,5,6, 4, 7}为例:
|
||||
* dp=[1,2,3,1,2,1,2]
|
||||
* 速度击败99.96%,内存击败32.74% 1ms
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public int findLengthOfLCIS3(int[] nums) {
|
||||
|
||||
int nowLength=1;
|
||||
int dp0=1;
|
||||
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
if (nums[i] > nums[i-1]) {
|
||||
nowLength+=1;
|
||||
}else {
|
||||
nowLength=1;
|
||||
}
|
||||
dp0=Math.max(dp0,nowLength);
|
||||
}
|
||||
return dp0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 题解中0ms方法:比之前的快在count=1不需要在判断一次if(count>max)
|
||||
* 速度击败100%,内存击败38.73%
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public int findLengthOfLCIS5(int[] nums) {
|
||||
int left=0,right,count=1,max=1;
|
||||
for (right = 1; right < nums.length; right++,left++) {
|
||||
if(nums[left]<nums[right]){
|
||||
count++;
|
||||
if(count>max){
|
||||
max=count;
|
||||
}
|
||||
}else{
|
||||
count=1;
|
||||
}
|
||||
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.dynamic
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-16 12:51
|
||||
*@Description:
|
||||
* TODO 力扣1143题 最长公共子序列:
|
||||
* 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
|
||||
* 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
|
||||
* 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
|
||||
* 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T30_LongestCommonSubsequence {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
String text2 = "abcacde";
|
||||
String text1 = "acfe";
|
||||
System.out.println(longestCommonSubsequence1(text1,text2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1(){
|
||||
String text2 = "abcba";
|
||||
String text1 = "abcbcba";
|
||||
System.out.println(longestCommonSubsequence1(text1,text2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:与T29类似,只是将连续条件去掉了,变得可以不连续:
|
||||
* TODO 动态规划五部曲:
|
||||
* (1)dp定义:dp[i][j]表示以i-1结尾的text1和以j-1结尾的text2的最长公共子序列
|
||||
* (2)dp状态转移方程: for j in (0,len-(i-1) ) if(num[i-1]==num[i-1+j:]) dp[i][j]=max(dp[i-1])+1
|
||||
* (3)dp初始化:dp[0]=0
|
||||
* (4)dp遍历顺序:两个for可以随意交换顺序
|
||||
* (5)dp举例推导:以 text2 = "abcacde", text1 = "acfe" 为例
|
||||
* text2: a b c a c d e
|
||||
* text1: 0 0 0 0 0 0 0 0
|
||||
* a: 0 1 1 1 1 1 1 1
|
||||
* c: 0 1 1 2 2 2 2 2
|
||||
* f: 0 1 1 2 2 2 2 2
|
||||
* e: 0 1 1 2 2 2 2 3
|
||||
* 速度击败39.62%,内存击败47.86% 11ms
|
||||
* @param text1
|
||||
* @param text2
|
||||
* @return
|
||||
*/
|
||||
public int longestCommonSubsequence(String text1, String text2) {
|
||||
|
||||
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
|
||||
|
||||
for (int i = 1; i < text1.length()+1; i++) {
|
||||
char char1 = text1.charAt(i-1);
|
||||
for (int j = 1; j < text2.length() + 1; j++) {
|
||||
if(char1==text2.charAt(j-1)){
|
||||
//不要text1[i-1]大,还是不要text2[j-1]大,还是都要大
|
||||
dp[i][j]=Math.max(dp[i][j-1],Math.max(dp[i-1][j],dp[i-1][j-1]+1));
|
||||
}else {
|
||||
//不要text1[i-1]大,还是不要text2[j-1]大
|
||||
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[text1.length()][text2.length()];
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于自己的浅浅优化一下:不需要判断不要text2[j-1]大,
|
||||
* 因为他事实上取决于它的[j-2]和[i-1][j-2]谁大,所以他一定比[i-1]+1小
|
||||
* 速度击败65.48%,内存击败34.92% 10ms
|
||||
* @param text1
|
||||
* @param text2
|
||||
* @return
|
||||
*/
|
||||
public int longestCommonSubsequence1(String text1, String text2) {
|
||||
|
||||
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
|
||||
|
||||
for (int i = 1; i < text1.length()+1; i++) {
|
||||
char char1 = text1.charAt(i-1);
|
||||
for (int j = 1; j < text2.length() + 1; j++) {
|
||||
if(char1==text2.charAt(j-1)){
|
||||
//不要text1[i-1]大,还是都要大
|
||||
dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1);
|
||||
}else {
|
||||
//不要text1[i-1]大,还是不要text2[j-1]大
|
||||
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[text1.length()][text2.length()];
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于代码随想录进一步优化一下:不需要判断dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1);
|
||||
* 因为他事实上dp[i-1][j-1]+1一定比dp[i][j-1]大
|
||||
* 速度击败77.38%,内存击败34.92% 9ms
|
||||
* @param text1
|
||||
* @param text2
|
||||
* @return
|
||||
*/
|
||||
public int longestCommonSubsequence2(String text1, String text2) {
|
||||
|
||||
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
|
||||
|
||||
for (int i = 1; i < text1.length()+1; i++) {
|
||||
char char1 = text1.charAt(i-1);
|
||||
for (int j = 1; j < text2.length() + 1; j++) {
|
||||
if(char1==text2.charAt(j-1)){
|
||||
//不要text1[i-1]大,还是都要大
|
||||
dp[i][j]=dp[i-1][j-1]+1;
|
||||
}else {
|
||||
//不要text1[i-1]大,还是不要text2[j-1]大
|
||||
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[text1.length()][text2.length()];
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 一维dp数组优化
|
||||
* 速度击败77.38%,内存击败97.17%
|
||||
* @param text1
|
||||
* @param text2
|
||||
* @return
|
||||
*/
|
||||
public int longestCommonSubsequence3(String text1, String text2) {
|
||||
int n1 = text1.length();
|
||||
int n2 = text2.length();
|
||||
|
||||
// 多从二维dp数组过程分析
|
||||
// 关键在于 如果记录 dp[i - 1][j - 1]
|
||||
// 因为 dp[i - 1][j - 1] <!=> dp[j - 1] <=> dp[i][j - 1]
|
||||
int [] dp = new int[n2 + 1];
|
||||
|
||||
for(int i = 1; i <= n1; i++){
|
||||
|
||||
// 这里pre相当于 dp[i - 1][j - 1]
|
||||
int pre = dp[0];
|
||||
for(int j = 1; j <= n2; j++){
|
||||
|
||||
//用于给pre赋值
|
||||
int cur = dp[j];
|
||||
if(text1.charAt(i - 1) == text2.charAt(j - 1)){
|
||||
//这里pre相当于dp[i - 1][j - 1] 千万不能用dp[j - 1] !!
|
||||
//或者倒序遍历也行
|
||||
dp[j] = pre + 1;
|
||||
} else{
|
||||
// dp[j] 相当于 dp[i - 1][j]
|
||||
// dp[j - 1] 相当于 dp[i][j - 1]
|
||||
dp[j] = Math.max(dp[j], dp[j - 1]);
|
||||
}
|
||||
|
||||
//更新dp[i - 1][j - 1], 为下次使用做准备
|
||||
pre = cur;
|
||||
}
|
||||
}
|
||||
|
||||
return dp[n2];
|
||||
}
|
||||
|
||||
/**
|
||||
* 官方题解中合理且最快的方法:本质上还是动态规划,
|
||||
* 但是优化在选取text长度最小的作为内层for,并将string变成数组后进行判断
|
||||
* 速度击败99.96%,内存击败98.16% 3ms
|
||||
* @param text1
|
||||
* @param text2
|
||||
* @return
|
||||
*/
|
||||
public int longestCommonSubsequence4(String text1, String text2) {
|
||||
//动态规划
|
||||
//f(n) f(n-1)
|
||||
//f(1) 递推
|
||||
//确保text1最长
|
||||
if(text1.length()<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 f(n) 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];//返回最后一个
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.Comparator;
|
|||
*
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class FindContentChildren {
|
||||
public class T01_FindContentChildren {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -15,7 +15,7 @@ import org.junit.Test;
|
|||
* 给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class WiggleMaxLength {
|
||||
public class T02_WiggleMaxLength {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -12,7 +12,7 @@ import org.junit.Test;
|
|||
* 子数组 是数组中的一个连续部分。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class MaxSubArray {
|
||||
public class T03_MaxSubArray {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import java.util.HashMap;
|
|||
* 注意:若s 和 t中每个字符出现的次数都相同,则称s 和 t互为字母异位词。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class IsAnagram {
|
||||
public class T01_IsAnagram {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.Set;
|
|||
* 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class Intersection {
|
||||
public class T02_Intersection {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.HashMap;
|
|||
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class TwoSum {
|
||||
public class T04_TwoSum {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.HashSet;
|
|||
* 2) nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class FourSumCount {
|
||||
public class T05_FourSumCount {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||
* 注意:答案中不可以包含重复的三元组。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class ThreeSum {
|
||||
public class T07_ThreeSum {
|
||||
|
||||
/**
|
||||
* 这道题使用哈希法的思路:
|
||||
|
|
@ -17,7 +17,7 @@ import java.util.*;
|
|||
* 你可以按 任意顺序 返回答案 。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class FourSum {
|
||||
public class T08_FourSum {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package com.markilue.leecode.hashtable.second;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.hashtable.second
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-27 12:10
|
||||
*@Description:
|
||||
* TODO 二刷454题 四数相加 II:
|
||||
* 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
|
||||
* 0 <= i, j, k, l < n
|
||||
* nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T05_FourSumCount {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int[] nums1 = {1, 2}, nums2 = {-2, -1}, nums3 = {-1, 2}, nums4 = {0, 2};
|
||||
System.out.println(fourSumCount(nums1,nums2,nums3,nums4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1(){
|
||||
int[] nums1 = {0}, nums2 = {0}, nums3 = {0}, nums4 = {0};
|
||||
System.out.println(fourSumCount(nums1,nums2,nums3,nums4));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:这题求得是组合数,而不要求里面到底是那四个index,
|
||||
* 考虑两个nums两两组成map<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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.markilue.leecode.hashtable.second;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.hashtable.second
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-28 10:30
|
||||
*@Description:
|
||||
* TODO 二刷15题 三数之和:
|
||||
* 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,
|
||||
* 同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
|
||||
* 你返回所有和为 0 且不重复的三元组。
|
||||
* 注意:答案中不可以包含重复的三元组。
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T07_ThreeSum {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int[] nums = {-1, 0, 1, 2, -1, -4};
|
||||
System.out.println(threeSum(nums));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:这题本质上是一个O(N^3)的一个暴力解法,考虑一层for放外面,里面用hashMap记录将复杂度变为O(N^2)
|
||||
* 由于对于结果不考虑index只考虑具体的数,因此考虑先对数组进行排序
|
||||
* 速度击败47.14%,内存击败84.92% 22ms
|
||||
* 增加第一个数大于0,则一定不可能了的判断以后 速度击败75.68%,内存击败80.11% 20ms
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
public List<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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
package com.markilue.leecode.hashtable.second;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*@BelongsProject: Leecode
|
||||
*@BelongsPackage: com.markilue.leecode.hashtable.second
|
||||
*@Author: dingjiawen
|
||||
*@CreateTime: 2022-12-28 11:16
|
||||
*@Description:
|
||||
* TODO 二刷力扣18题 四数之和:
|
||||
* 给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。
|
||||
* 请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
|
||||
* (若两个四元组元素一一对应,则认为两个四元组重复):
|
||||
* 0 <= a, b, c, d < n
|
||||
* a、b、c 和 d 互不相同
|
||||
* nums[a] + nums[b] + nums[c] + nums[d] == target
|
||||
* 你可以按 任意顺序 返回答案 。
|
||||
*@Version: 1.0
|
||||
*/
|
||||
public class T08_FourSum {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
int[] nums = {1, 0, -1, 0, -2, 2};
|
||||
int target = 0;
|
||||
System.out.println(fourSum(nums, target));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
int[] nums = {2, 2, 2, 2, 2};
|
||||
int target = 8;
|
||||
System.out.println(fourSum(nums, target));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
int[] nums = {1, -2, -5, -4, -3, 3, 3, 5};
|
||||
int target = -11;
|
||||
System.out.println(fourSum(nums, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:在三数之和外面再加上一层for,时间复杂度O(N^3)
|
||||
* 熟读击败49.87%,内存击败71.72%
|
||||
* @param nums
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public List<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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import org.junit.Test;
|
|||
* 移除链表元素:
|
||||
* 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
|
||||
*/
|
||||
public class RemoveElement {
|
||||
public class T01_RemoveElement {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -22,11 +22,11 @@ import org.junit.Test;
|
|||
* obj.addAtIndex(index,val);
|
||||
* obj.deleteAtIndex(index);
|
||||
*/
|
||||
public class MyLinkedList {
|
||||
public class T02_MyLinkedList {
|
||||
|
||||
public ListNode head;
|
||||
|
||||
public MyLinkedList() {
|
||||
public T02_MyLinkedList() {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ public class MyLinkedList {
|
|||
|
||||
@Test
|
||||
public void test(){
|
||||
MyLinkedList linkedList = new MyLinkedList();
|
||||
T02_MyLinkedList linkedList = new T02_MyLinkedList();
|
||||
linkedList.addAtHead(1);
|
||||
printList(linkedList.head);
|
||||
System.out.println();
|
||||
|
|
@ -185,7 +185,7 @@ public class MyLinkedList {
|
|||
}
|
||||
@Test
|
||||
public void test1(){
|
||||
MyLinkedList linkedList = new MyLinkedList();
|
||||
T02_MyLinkedList linkedList = new T02_MyLinkedList();
|
||||
linkedList.addAtHead(7);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -231,7 +231,7 @@ public class MyLinkedList {
|
|||
}
|
||||
@Test
|
||||
public void test2(){
|
||||
MyLinkedList linkedList = new MyLinkedList();
|
||||
T02_MyLinkedList linkedList = new T02_MyLinkedList();
|
||||
linkedList.addAtHead(2);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ public class MyLinkedList {
|
|||
|
||||
@Test
|
||||
public void test3(){
|
||||
MyLinkedList linkedList = new MyLinkedList();
|
||||
T02_MyLinkedList linkedList = new T02_MyLinkedList();
|
||||
linkedList.addAtHead(1);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -22,13 +22,13 @@ import org.junit.Test;
|
|||
* obj.addAtIndex(index,val);
|
||||
* obj.deleteAtIndex(index);
|
||||
*/
|
||||
public class MyLinkedList1 {
|
||||
public class T02_MyLinkedList1 {
|
||||
|
||||
//该head为虚拟节点,不存放任何数据
|
||||
public ListNode head;
|
||||
public int size;
|
||||
|
||||
public MyLinkedList1() {
|
||||
public T02_MyLinkedList1() {
|
||||
head = new ListNode(0);
|
||||
size = 0;
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ public class MyLinkedList1 {
|
|||
|
||||
@Test
|
||||
public void test() {
|
||||
MyLinkedList1 linkedList = new MyLinkedList1();
|
||||
T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
|
||||
linkedList.addAtHead(1);
|
||||
printList(linkedList.head);
|
||||
System.out.println();
|
||||
|
|
@ -145,7 +145,7 @@ public class MyLinkedList1 {
|
|||
|
||||
@Test
|
||||
public void test1() {
|
||||
MyLinkedList1 linkedList = new MyLinkedList1();
|
||||
T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
|
||||
linkedList.addAtHead(7);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ public class MyLinkedList1 {
|
|||
|
||||
@Test
|
||||
public void test2() {
|
||||
MyLinkedList1 linkedList = new MyLinkedList1();
|
||||
T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
|
||||
linkedList.addAtHead(2);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ public class MyLinkedList1 {
|
|||
|
||||
@Test
|
||||
public void test3() {
|
||||
MyLinkedList1 linkedList = new MyLinkedList1();
|
||||
T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
|
||||
linkedList.addAtHead(1);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ public class MyLinkedList1 {
|
|||
|
||||
@Test
|
||||
public void test4() {
|
||||
MyLinkedList1 linkedList = new MyLinkedList1();
|
||||
T02_MyLinkedList1 linkedList = new T02_MyLinkedList1();
|
||||
linkedList.addAtHead(4);
|
||||
printList(linkedList.head);
|
||||
|
||||
|
|
@ -295,19 +295,3 @@ public class MyLinkedList1 {
|
|||
*/
|
||||
}
|
||||
|
||||
class ListNode {
|
||||
public int val;
|
||||
public ListNode next;
|
||||
|
||||
public ListNode() {
|
||||
}
|
||||
|
||||
public ListNode(int val) {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public ListNode(int val, ListNode next) {
|
||||
this.val = val;
|
||||
this.next = next;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import org.junit.Test;
|
|||
* @Description: TODO leecode第206题,翻转链表:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class ReverseList {
|
||||
public class T03_ReverseList {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -2,7 +2,7 @@ package com.markilue.leecode.listnode;
|
|||
|
||||
|
||||
|
||||
public class swapPairs {
|
||||
public class T04_swapPairs {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
|
@ -3,7 +3,7 @@ package com.markilue.leecode.listnode;
|
|||
/**
|
||||
* 删除链表的倒数第N个节点
|
||||
*/
|
||||
public class removeNthFromEnd {
|
||||
public class T05_removeNthFromEnd {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.HashSet;
|
|||
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class DetectCycle {
|
||||
public class T07_DetectCycle {
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
*/
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.markilue.leecode.listnode.selftry;
|
||||
|
||||
import com.markilue.leecode.listnode.swapPairs;
|
||||
import com.markilue.leecode.listnode.T04_swapPairs;
|
||||
|
||||
public class reverseKGroup {
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public class reverseKGroup {
|
|||
}
|
||||
public static class ListNode {
|
||||
int val;
|
||||
swapPairs.ListNode next;
|
||||
T04_swapPairs.ListNode next;
|
||||
|
||||
ListNode() {
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ public class reverseKGroup {
|
|||
this.val = val;
|
||||
}
|
||||
|
||||
ListNode(int val, swapPairs.ListNode next) {
|
||||
ListNode(int val, T04_swapPairs.ListNode next) {
|
||||
this.val = val;
|
||||
this.next = next;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.markilue.leecode.stackAndDeque;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.omg.CORBA.PUBLIC_MEMBER;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
|
|
@ -27,12 +26,12 @@ import java.util.Stack;
|
|||
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class MyQueue {
|
||||
public class T01_MyQueue {
|
||||
|
||||
public Stack<Integer> stack1;
|
||||
public Stack<Integer> stack2;
|
||||
|
||||
public MyQueue() {
|
||||
public T01_MyQueue() {
|
||||
stack1=new Stack<>();
|
||||
stack2=new Stack<>();
|
||||
|
||||
|
|
@ -88,7 +87,7 @@ public class MyQueue {
|
|||
|
||||
@Test
|
||||
public void test(){
|
||||
MyQueue myQueue = new MyQueue();
|
||||
T01_MyQueue myQueue = new T01_MyQueue();
|
||||
myQueue.push(1); // queue is: [1]
|
||||
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
|
||||
System.out.println(myQueue.peek());// return 1
|
||||
|
|
@ -26,12 +26,12 @@ import java.util.Stack;
|
|||
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class MyQueue1 {
|
||||
public class T01_MyQueue1 {
|
||||
|
||||
public Stack<Integer> stack1;
|
||||
public Stack<Integer> stack2;
|
||||
|
||||
public MyQueue1() {
|
||||
public T01_MyQueue1() {
|
||||
stack1=new Stack<>();
|
||||
stack2=new Stack<>();
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class MyQueue1 {
|
|||
|
||||
@Test
|
||||
public void test(){
|
||||
MyQueue1 myQueue = new MyQueue1();
|
||||
T01_MyQueue1 myQueue = new T01_MyQueue1();
|
||||
myQueue.push(1); // queue is: [1]
|
||||
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
|
||||
System.out.println(myQueue.peek());// return 1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue